packet-tracer-skill 0.2.2 → 0.2.3
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.
- package/CHANGELOG.md +32 -1
- package/README.md +221 -51
- package/docs/automation-controller-proof.md +35 -0
- package/docs/curated-donor-registry.md +11 -0
- package/docs/generate-ready-pilot-design.md +30 -0
- package/docs/industrial-programming-proof.md +48 -0
- package/docs/ipv4-routing-management-proof.md +37 -0
- package/docs/l2-resiliency-bgp-proof.md +60 -0
- package/docs/l2-security-qos-proof.md +59 -0
- package/docs/packet-tracer-feature-gap-atlas.md +172 -15
- package/docs/release-checklist.md +13 -8
- package/docs/release-notes-0.2.2.md +1 -1
- package/docs/release-notes-0.2.3.md +59 -0
- package/docs/security-edge-deepening-proof.md +65 -0
- package/docs/voice-collaboration-proof.md +38 -0
- package/docs/wan-security-donor-proof.md +20 -3
- package/package.json +10 -1
- package/references/packettracer-feature-atlas.json +67 -17
- package/scripts/coverage_matrix.py +505 -12
- package/scripts/feature_atlas.py +65 -1
- package/scripts/generate_pkt.py +161 -3
- package/scripts/intent_parser.py +521 -2
- package/scripts/pkt_editor.py +477 -0
- package/scripts/remote_search.py +197 -21
- package/scripts/sample_catalog.py +57 -2
package/scripts/intent_parser.py
CHANGED
|
@@ -38,6 +38,18 @@ CAPABILITY_PATTERNS = {
|
|
|
38
38
|
"ospf": [r"\bospf\b"],
|
|
39
39
|
"eigrp": [r"\beigrp\b"],
|
|
40
40
|
"rip": [r"\brip\b"],
|
|
41
|
+
"ospfv2": [r"\bospfv2\b", r"\bospf v2\b", r"\bsingle-area ospf\b", r"\brouter ospf\b"],
|
|
42
|
+
"eigrp_ipv4": [r"\beigrp ipv4\b", r"\bipv4 eigrp\b", r"\brouter eigrp\b"],
|
|
43
|
+
"ripv2": [r"\bripv2\b", r"\brip v2\b", r"\brip version 2\b", r"\brouter rip\b"],
|
|
44
|
+
"static_route": [r"\bstatic route\b", r"\bstatic-route\b", r"\bip route\b"],
|
|
45
|
+
"default_route": [r"\bdefault route\b", r"\bdefault-route\b", r"\b0\.0\.0\.0/0\b"],
|
|
46
|
+
"dhcp_relay": [r"\bdhcp relay\b", r"\bdhcp-relay\b", r"\bhelper-address\b", r"\bip helper\b"],
|
|
47
|
+
"nat_static": [r"\bstatic nat\b", r"\bnat static\b"],
|
|
48
|
+
"nat_dynamic": [r"\bdynamic nat\b", r"\bnat dynamic\b"],
|
|
49
|
+
"pat": [r"\bpat\b", r"\boverload\b"],
|
|
50
|
+
"ssh_ios": [r"\bssh\b", r"\bip ssh\b", r"\bcrypto key\b"],
|
|
51
|
+
"ntp_ios": [r"\bntp\b", r"\bntp server\b"],
|
|
52
|
+
"syslog_ios": [r"\bsyslog\b", r"\blogging host\b"],
|
|
41
53
|
"ipv6_slaac": [r"\bslaac\b", r"\bipv6\s+slaac\b"],
|
|
42
54
|
"dhcpv6_stateful": [r"\bdhcpv6\b", r"\bstateful dhcpv6\b", r"\bipv6 stateful\b"],
|
|
43
55
|
"dhcpv6_stateless": [r"\bstateless dhcpv6\b", r"\bipv6 stateless\b"],
|
|
@@ -58,9 +70,17 @@ CAPABILITY_PATTERNS = {
|
|
|
58
70
|
"rep": [r"\brep\b"],
|
|
59
71
|
"snmp": [r"\bsnmp\b"],
|
|
60
72
|
"netflow": [r"\bnetflow\b"],
|
|
61
|
-
"span": [r"\bspan\b", r"\brspan\b"],
|
|
73
|
+
"span": [r"\bmonitor\s+session\b", r"\bspan\b", r"\brspan\b"],
|
|
62
74
|
"qos": [r"\bqos policy\b", r"\bqos class\b", r"\bclass-map\b", r"\bpolicy-map\b", r"\bquality of service\b"],
|
|
63
75
|
"port_security": [r"\bport security\b", r"\bport-security\b"],
|
|
76
|
+
"bgp": [r"\bbgp\b", r"\brouter bgp\b", r"\bremote-as\b"],
|
|
77
|
+
"stp": [r"\bstp\b", r"\bspanning tree\b", r"\bspanning-tree\b", r"\bpvst\b"],
|
|
78
|
+
"rstp": [r"\brstp\b", r"\brapid-pvst\b", r"\brapid pvst\b", r"\brapid spanning\b"],
|
|
79
|
+
"etherchannel": [r"\betherchannel\b", r"\bport-channel\b", r"\bchannel-group\b"],
|
|
80
|
+
"lacp": [r"\blacp\b"],
|
|
81
|
+
"pagp": [r"\bpagp\b"],
|
|
82
|
+
"vtp": [r"\bvtp\b"],
|
|
83
|
+
"dtp": [r"\bdtp\b", r"\bdynamic desirable\b", r"\bdynamic auto\b"],
|
|
64
84
|
"vpn": [r"\bvpn\b", r"\bsite-to-site\b", r"\bipsec\b", r"\btunnel\b"],
|
|
65
85
|
"ipsec": [r"\bipsec\b", r"\bike\b", r"\bphase 1\b", r"\bphase 2\b"],
|
|
66
86
|
"gre": [r"\bgre\b", r"\bgre tunnel\b"],
|
|
@@ -160,7 +180,9 @@ NETWORK_STYLE_PATTERNS = {
|
|
|
160
180
|
"campus": [r"\bcampus\b", r"\bkampus\b", r"\bsobeli\b", r"\bdepart", r"\bdepartment\b"],
|
|
161
181
|
"branch": [r"\bbranch\b", r"\bfilial\b"],
|
|
162
182
|
"ipv6_routing": [r"\bipv6\b", r"\bslaac\b", r"\bdhcpv6\b", r"\bospfv3\b", r"\bhsrp\b"],
|
|
183
|
+
"ipv4_routing_management": [r"\bospfv2\b", r"\bospf v2\b", r"\beigrp ipv4\b", r"\bripv2\b", r"\brip version 2\b", r"\bstatic route\b", r"\bdefault route\b", r"\bdhcp relay\b", r"\bnat\b", r"\bpat\b", r"\bip ssh\b", r"\bntp server\b", r"\blogging host\b"],
|
|
163
184
|
"l2_security_monitoring": [r"\bdhcp snooping\b", r"\bdai\b", r"\bdot1x\b", r"\b802\.1x\b", r"\bsnmp\b", r"\bnetflow\b", r"\bspan\b", r"\brspan\b", r"\bquality of service\b", r"\blldp\b", r"\brep\b", r"\bport security\b", r"\bport-security\b"],
|
|
185
|
+
"l2_resiliency_routing": [r"\bbgp\b", r"\bstp\b", r"\brstp\b", r"\bspanning tree\b", r"\bspanning-tree\b", r"\betherchannel\b", r"\bport-channel\b", r"\bchannel-group\b", r"\blacp\b", r"\bpagp\b", r"\bvtp\b", r"\bdtp\b"],
|
|
164
186
|
"wireless_advanced": [r"\bwlc\b", r"\bwireless lan controller\b", r"\bmeraki\b", r"\bbluetooth\b", r"\b5g\b", r"\bcellular\b", r"\bwpa enterprise\b", r"\bwpa2 enterprise\b", r"\bwep\b", r"\bguest wifi\b", r"\bguest wlan\b", r"\bbeamforming\b"],
|
|
165
187
|
"automation_controller": [r"\bnetwork controller\b", r"\bnetcon\b", r"\bblockly\b", r"\bjavascript\b", r"\bpython programming\b", r"\biox\b"],
|
|
166
188
|
"voice_collaboration": [r"\bvoip\b", r"\bip phone\b", r"\bcall manager\b", r"\btelephony\b"],
|
|
@@ -234,7 +256,11 @@ TRANSLITERATION_TABLE = str.maketrans(
|
|
|
234
256
|
|
|
235
257
|
|
|
236
258
|
def _command_segments(prompt: str) -> list[str]:
|
|
237
|
-
parts = re.split(
|
|
259
|
+
parts = re.split(
|
|
260
|
+
r"(?=(?<!-)\b(?:device|connect|link|set|enable|associate|change|update|rename|apply)\b)",
|
|
261
|
+
prompt,
|
|
262
|
+
flags=re.IGNORECASE,
|
|
263
|
+
)
|
|
238
264
|
return [part.strip() for part in parts if part.strip()]
|
|
239
265
|
|
|
240
266
|
|
|
@@ -306,6 +332,7 @@ class IntentPlan:
|
|
|
306
332
|
management_ops: list[dict[str, object]] = field(default_factory=list)
|
|
307
333
|
verification_ops: list[dict[str, object]] = field(default_factory=list)
|
|
308
334
|
iot_ops: list[dict[str, object]] = field(default_factory=list)
|
|
335
|
+
programming_ops: list[dict[str, object]] = field(default_factory=list)
|
|
309
336
|
|
|
310
337
|
def all_operations(self) -> list[dict[str, object]]:
|
|
311
338
|
merged: list[dict[str, object]] = []
|
|
@@ -319,6 +346,7 @@ class IntentPlan:
|
|
|
319
346
|
self.management_ops,
|
|
320
347
|
self.verification_ops,
|
|
321
348
|
self.iot_ops,
|
|
349
|
+
self.programming_ops,
|
|
322
350
|
]:
|
|
323
351
|
merged.extend(bucket)
|
|
324
352
|
return merged
|
|
@@ -586,6 +614,32 @@ def _extract_switch_ops(prompt: str) -> list[dict[str, object]]:
|
|
|
586
614
|
r"set\s+([A-Za-z0-9_-]+)\s+(?:r?span)\s+(\d+)\s+source\s+([A-Za-z0-9/._-]+)\s+destination\s+([A-Za-z0-9/._-]+)",
|
|
587
615
|
flags=re.IGNORECASE,
|
|
588
616
|
)
|
|
617
|
+
dot1x_pattern = re.compile(
|
|
618
|
+
r"set\s+([A-Za-z0-9_-]+)\s+dot1x\s+interface\s+([A-Za-z0-9/._-]+)\s+mode\s+(auto|force-authorized|force-unauthorized)"
|
|
619
|
+
r"(?:\s+radius\s+(\d+\.\d+\.\d+\.\d+)\s+key\s+([A-Za-z0-9._-]+))?",
|
|
620
|
+
flags=re.IGNORECASE,
|
|
621
|
+
)
|
|
622
|
+
qos_pattern = re.compile(
|
|
623
|
+
r"set\s+([A-Za-z0-9_-]+)\s+qos\s+class-map\s+([A-Za-z0-9_-]+)\s+match\s+(.+?)\s+policy-map\s+([A-Za-z0-9_-]+)\s+class\s+\2\s+(priority|bandwidth\s+\d+|police\s+\d+)"
|
|
624
|
+
r"\s+service-policy\s+(input|output)\s+([A-Za-z0-9/._-]+)(?=\s+set\s+|$)",
|
|
625
|
+
flags=re.IGNORECASE,
|
|
626
|
+
)
|
|
627
|
+
stp_pattern = re.compile(
|
|
628
|
+
r"set\s+([A-Za-z0-9_-]+)\s+stp\s+mode\s+([A-Za-z0-9_-]+)(?:\s+vlan\s+(\d+)\s+root\s+(primary|secondary))?",
|
|
629
|
+
flags=re.IGNORECASE,
|
|
630
|
+
)
|
|
631
|
+
etherchannel_pattern = re.compile(
|
|
632
|
+
r"set\s+([A-Za-z0-9_-]+)\s+etherchannel\s+(\d+)\s+mode\s+(active|passive|desirable|auto|on)\s+interfaces\s+(.+?)(?=\s+set\s+|$)",
|
|
633
|
+
flags=re.IGNORECASE,
|
|
634
|
+
)
|
|
635
|
+
vtp_pattern = re.compile(
|
|
636
|
+
r"set\s+([A-Za-z0-9_-]+)\s+vtp\s+domain\s+([A-Za-z0-9_.-]+)\s+mode\s+(server|client|transparent)(?:\s+version\s+(\d+))?",
|
|
637
|
+
flags=re.IGNORECASE,
|
|
638
|
+
)
|
|
639
|
+
dtp_pattern = re.compile(
|
|
640
|
+
r"set\s+([A-Za-z0-9_-]+)\s+dtp\s+interface\s+([A-Za-z0-9/._-]+)\s+mode\s+(dynamic\s+desirable|dynamic\s+auto|trunk|access)",
|
|
641
|
+
flags=re.IGNORECASE,
|
|
642
|
+
)
|
|
589
643
|
for segment in _command_segments(prompt):
|
|
590
644
|
for device, vlan_id, trust_port in dhcp_snooping_pattern.findall(segment):
|
|
591
645
|
ops.append({"op": "set_dhcp_snooping", "device": device, "vlan": int(vlan_id), "trust_port": trust_port or None})
|
|
@@ -607,6 +661,63 @@ def _extract_switch_ops(prompt: str) -> list[dict[str, object]]:
|
|
|
607
661
|
ops.append({"op": "set_rep", "device": device, "segment": int(segment_id), "interface": interface_name})
|
|
608
662
|
for device, session, source, destination in span_pattern.findall(segment):
|
|
609
663
|
ops.append({"op": "set_span", "device": device, "session": int(session), "source": source, "destination": destination})
|
|
664
|
+
for device, interface_name, mode, radius_host, radius_key in dot1x_pattern.findall(segment):
|
|
665
|
+
ops.append(
|
|
666
|
+
{
|
|
667
|
+
"op": "set_dot1x",
|
|
668
|
+
"device": device,
|
|
669
|
+
"interface": interface_name,
|
|
670
|
+
"mode": mode.lower(),
|
|
671
|
+
"radius_host": radius_host or None,
|
|
672
|
+
"radius_key": radius_key or None,
|
|
673
|
+
}
|
|
674
|
+
)
|
|
675
|
+
for device, class_map, match_expr, policy_map, action, direction, interface_name in qos_pattern.findall(segment):
|
|
676
|
+
ops.append(
|
|
677
|
+
{
|
|
678
|
+
"op": "set_qos_policy",
|
|
679
|
+
"device": device,
|
|
680
|
+
"class_map": class_map,
|
|
681
|
+
"match": match_expr.strip(),
|
|
682
|
+
"policy_map": policy_map,
|
|
683
|
+
"action": action.lower(),
|
|
684
|
+
"direction": direction.lower(),
|
|
685
|
+
"interface": interface_name,
|
|
686
|
+
}
|
|
687
|
+
)
|
|
688
|
+
for device, mode, vlan_id, root_priority in stp_pattern.findall(segment):
|
|
689
|
+
ops.append(
|
|
690
|
+
{
|
|
691
|
+
"op": "set_stp",
|
|
692
|
+
"device": device,
|
|
693
|
+
"mode": mode.lower(),
|
|
694
|
+
"vlan": int(vlan_id) if vlan_id else None,
|
|
695
|
+
"root": root_priority.lower() if root_priority else None,
|
|
696
|
+
}
|
|
697
|
+
)
|
|
698
|
+
for device, channel, mode, interfaces_text in etherchannel_pattern.findall(segment):
|
|
699
|
+
interfaces = [value for value in re.findall(r"[A-Za-z]+[A-Za-z]*[0-9/._-]+", interfaces_text) if value.lower() != "set"]
|
|
700
|
+
ops.append(
|
|
701
|
+
{
|
|
702
|
+
"op": "set_etherchannel",
|
|
703
|
+
"device": device,
|
|
704
|
+
"channel": int(channel),
|
|
705
|
+
"mode": mode.lower(),
|
|
706
|
+
"interfaces": interfaces,
|
|
707
|
+
}
|
|
708
|
+
)
|
|
709
|
+
for device, domain, mode, version in vtp_pattern.findall(segment):
|
|
710
|
+
ops.append(
|
|
711
|
+
{
|
|
712
|
+
"op": "set_vtp",
|
|
713
|
+
"device": device,
|
|
714
|
+
"domain": domain,
|
|
715
|
+
"mode": mode.lower(),
|
|
716
|
+
"version": int(version) if version else None,
|
|
717
|
+
}
|
|
718
|
+
)
|
|
719
|
+
for device, interface_name, mode in dtp_pattern.findall(segment):
|
|
720
|
+
ops.append({"op": "set_dtp", "device": device, "interface": interface_name, "mode": mode.lower()})
|
|
610
721
|
return ops
|
|
611
722
|
|
|
612
723
|
|
|
@@ -695,11 +806,101 @@ def _extract_router_ops(prompt: str) -> list[dict[str, object]]:
|
|
|
695
806
|
r"set\s+([A-Za-z0-9_-]+)\s+hsrp\s+(\d+)\s+ipv6\s+([0-9A-Fa-f:]+)\s+interface\s+([A-Za-z0-9/._-]+)(?:\s+priority\s+(\d+))?",
|
|
696
807
|
flags=re.IGNORECASE,
|
|
697
808
|
)
|
|
809
|
+
ospfv2_pattern = re.compile(
|
|
810
|
+
r"set\s+([A-Za-z0-9_-]+)\s+ospfv2\s+(\d+)\s+network\s+(\d+\.\d+\.\d+\.\d+)\s+wildcard\s+(\d+\.\d+\.\d+\.\d+)\s+area\s+(\d+)",
|
|
811
|
+
flags=re.IGNORECASE,
|
|
812
|
+
)
|
|
813
|
+
eigrp_ipv4_pattern = re.compile(
|
|
814
|
+
r"set\s+([A-Za-z0-9_-]+)\s+eigrp\s+ipv4\s+(\d+)\s+network\s+(\d+\.\d+\.\d+\.\d+)\s+wildcard\s+(\d+\.\d+\.\d+\.\d+)(?:\s+(no-auto-summary))?",
|
|
815
|
+
flags=re.IGNORECASE,
|
|
816
|
+
)
|
|
817
|
+
ripv2_pattern = re.compile(
|
|
818
|
+
r"set\s+([A-Za-z0-9_-]+)\s+rip\s+version\s+2\s+network\s+(\d+\.\d+\.\d+\.\d+)(?:\s+(no-auto-summary))?",
|
|
819
|
+
flags=re.IGNORECASE,
|
|
820
|
+
)
|
|
821
|
+
static_route_pattern = re.compile(
|
|
822
|
+
r"set\s+([A-Za-z0-9_-]+)\s+static-route\s+(\d+\.\d+\.\d+\.\d+)/(\d+)\s+via\s+(\d+\.\d+\.\d+\.\d+)",
|
|
823
|
+
flags=re.IGNORECASE,
|
|
824
|
+
)
|
|
825
|
+
dhcp_relay_pattern = re.compile(
|
|
826
|
+
r"set\s+([A-Za-z0-9_-]+)\s+dhcp-relay\s+interface\s+([A-Za-z0-9/._-]+)\s+helper\s+(\d+\.\d+\.\d+\.\d+)",
|
|
827
|
+
flags=re.IGNORECASE,
|
|
828
|
+
)
|
|
829
|
+
nat_inside_outside_pattern = re.compile(
|
|
830
|
+
r"set\s+([A-Za-z0-9_-]+)\s+nat\s+(inside|outside)\s+interface\s+([A-Za-z0-9/._-]+)",
|
|
831
|
+
flags=re.IGNORECASE,
|
|
832
|
+
)
|
|
833
|
+
nat_static_pattern = re.compile(
|
|
834
|
+
r"set\s+([A-Za-z0-9_-]+)\s+nat\s+static\s+(\d+\.\d+\.\d+\.\d+)\s+(\d+\.\d+\.\d+\.\d+)",
|
|
835
|
+
flags=re.IGNORECASE,
|
|
836
|
+
)
|
|
837
|
+
pat_pattern = re.compile(
|
|
838
|
+
r"set\s+([A-Za-z0-9_-]+)\s+pat\s+acl\s+([A-Za-z0-9_-]+)\s+interface\s+([A-Za-z0-9/._-]+)(?:\s+(overload))?",
|
|
839
|
+
flags=re.IGNORECASE,
|
|
840
|
+
)
|
|
841
|
+
ssh_ios_pattern = re.compile(
|
|
842
|
+
r"set\s+([A-Za-z0-9_-]+)\s+ssh\s+domain\s+([A-Za-z0-9._-]+)\s+username\s+([A-Za-z0-9._-]+)\s+password\s+([A-Za-z0-9._-]+)(?:\s+modulus\s+(\d+))?",
|
|
843
|
+
flags=re.IGNORECASE,
|
|
844
|
+
)
|
|
845
|
+
ntp_ios_pattern = re.compile(r"set\s+([A-Za-z0-9_-]+)\s+ntp\s+server\s+(\d+\.\d+\.\d+\.\d+)", flags=re.IGNORECASE)
|
|
846
|
+
syslog_ios_pattern = re.compile(r"set\s+([A-Za-z0-9_-]+)\s+syslog\s+server\s+(\d+\.\d+\.\d+\.\d+)", flags=re.IGNORECASE)
|
|
847
|
+
bgp_pattern = re.compile(
|
|
848
|
+
r"set\s+([A-Za-z0-9_-]+)\s+bgp\s+(\d+)\s+neighbor\s+(\d+\.\d+\.\d+\.\d+)\s+remote-as\s+(\d+)"
|
|
849
|
+
r"(?:\s+network\s+(\d+\.\d+\.\d+\.\d+)\s+mask\s+(\d+\.\d+\.\d+\.\d+))?",
|
|
850
|
+
flags=re.IGNORECASE,
|
|
851
|
+
)
|
|
698
852
|
snmp_pattern = re.compile(r"set\s+([A-Za-z0-9_-]+)\s+snmp\s+community\s+([A-Za-z0-9_-]+)\s+(ro|rw)", flags=re.IGNORECASE)
|
|
699
853
|
netflow_pattern = re.compile(
|
|
700
854
|
r"set\s+([A-Za-z0-9_-]+)\s+netflow\s+destination\s+(\d+\.\d+\.\d+\.\d+)\s+(\d+)(?:\s+version\s+(\d+))?(?:\s+interface\s+([A-Za-z0-9/._-]+)\s+(ingress|egress))?",
|
|
701
855
|
flags=re.IGNORECASE,
|
|
702
856
|
)
|
|
857
|
+
gre_tunnel_pattern = re.compile(
|
|
858
|
+
r"set\s+([A-Za-z0-9_-]+)\s+gre\s+tunnel\s+([A-Za-z0-9/._-]+)\s+source\s+([A-Za-z0-9/._:-]+)\s+destination\s+([A-Za-z0-9/._:-]+)"
|
|
859
|
+
r"(?:\s+ip\s+(\d+\.\d+\.\d+\.\d+)/(\d+))?",
|
|
860
|
+
flags=re.IGNORECASE,
|
|
861
|
+
)
|
|
862
|
+
ppp_pattern = re.compile(
|
|
863
|
+
r"set\s+([A-Za-z0-9_-]+)\s+ppp\s+interface\s+([A-Za-z0-9/._-]+)(?:\s+authentication\s+(chap|pap))?",
|
|
864
|
+
flags=re.IGNORECASE,
|
|
865
|
+
)
|
|
866
|
+
ipsec_transform_pattern = re.compile(
|
|
867
|
+
r"set\s+([A-Za-z0-9_-]+)\s+ipsec\s+transform-set\s+([A-Za-z0-9_-]+)\s+([A-Za-z0-9_-]+)\s+([A-Za-z0-9_-]+)",
|
|
868
|
+
flags=re.IGNORECASE,
|
|
869
|
+
)
|
|
870
|
+
crypto_map_pattern = re.compile(
|
|
871
|
+
r"set\s+([A-Za-z0-9_-]+)\s+crypto\s+map\s+([A-Za-z0-9_-]+)\s+(\d+)\s+peer\s+(\d+\.\d+\.\d+\.\d+)\s+transform-set\s+([A-Za-z0-9_-]+)\s+match\s+([A-Za-z0-9_-]+)(?:\s+interface\s+([A-Za-z0-9/._-]+))?",
|
|
872
|
+
flags=re.IGNORECASE,
|
|
873
|
+
)
|
|
874
|
+
cbac_pattern = re.compile(
|
|
875
|
+
r"set\s+([A-Za-z0-9_-]+)\s+cbac\s+inspect\s+([A-Za-z0-9_-]+)\s+protocol\s+([A-Za-z0-9_-]+)\s+interface\s+([A-Za-z0-9/._-]+)\s+direction\s+(in|out)",
|
|
876
|
+
flags=re.IGNORECASE,
|
|
877
|
+
)
|
|
878
|
+
zfw_zone_interface_pattern = re.compile(
|
|
879
|
+
r"set\s+([A-Za-z0-9_-]+)\s+zfw\s+zone\s+([A-Za-z0-9_-]+)\s+interface\s+([A-Za-z0-9/._-]+)",
|
|
880
|
+
flags=re.IGNORECASE,
|
|
881
|
+
)
|
|
882
|
+
zfw_zone_pair_pattern = re.compile(
|
|
883
|
+
r"set\s+([A-Za-z0-9_-]+)\s+zfw\s+zone-pair\s+([A-Za-z0-9_-]+)\s+source\s+([A-Za-z0-9_-]+)\s+destination\s+([A-Za-z0-9_-]+)\s+policy\s+([A-Za-z0-9_-]+)",
|
|
884
|
+
flags=re.IGNORECASE,
|
|
885
|
+
)
|
|
886
|
+
zfw_policy_pattern = re.compile(
|
|
887
|
+
r"set\s+([A-Za-z0-9_-]+)\s+zfw\s+class-map\s+([A-Za-z0-9_-]+)\s+match\s+protocol\s+([A-Za-z0-9_-]+)\s+policy-map\s+([A-Za-z0-9_-]+)\s+action\s+(inspect|pass|drop)",
|
|
888
|
+
flags=re.IGNORECASE,
|
|
889
|
+
)
|
|
890
|
+
telephony_service_pattern = re.compile(
|
|
891
|
+
r'set\s+"?([^"]+?)"?\s+telephony\s+service\s+source-address\s+(\d+\.\d+\.\d+\.\d+)\s+port\s+(\d+)'
|
|
892
|
+
r"(?:\s+max-ephones\s+(\d+))?(?:\s+max-dn\s+(\d+))?",
|
|
893
|
+
flags=re.IGNORECASE,
|
|
894
|
+
)
|
|
895
|
+
ephone_dn_pattern = re.compile(r'set\s+"?([^"]+?)"?\s+ephone-dn\s+(\d+)\s+number\s+([A-Za-z0-9*+#.-]+)', flags=re.IGNORECASE)
|
|
896
|
+
ephone_pattern = re.compile(
|
|
897
|
+
r'set\s+"?([^"]+?)"?\s+ephone\s+(\d+)\s+mac\s+([0-9A-Fa-f.:-]+)\s+button\s+([0-9:]+)',
|
|
898
|
+
flags=re.IGNORECASE,
|
|
899
|
+
)
|
|
900
|
+
dial_peer_pattern = re.compile(
|
|
901
|
+
r'set\s+"?([^"]+?)"?\s+dial-peer\s+voice\s+(\d+)\s+destination-pattern\s+(\S+)\s+session-target\s+ipv4:(\d+\.\d+\.\d+\.\d+)',
|
|
902
|
+
flags=re.IGNORECASE,
|
|
903
|
+
)
|
|
703
904
|
for segment in _command_segments(prompt):
|
|
704
905
|
for device in ipv6_unicast_pattern.findall(segment):
|
|
705
906
|
ops.append({"op": "enable_ipv6_unicast_routing", "device": device})
|
|
@@ -745,6 +946,40 @@ def _extract_router_ops(prompt: str) -> list[dict[str, object]]:
|
|
|
745
946
|
"priority": int(priority) if priority else None,
|
|
746
947
|
}
|
|
747
948
|
)
|
|
949
|
+
for device, process_id, network, wildcard, area in ospfv2_pattern.findall(segment):
|
|
950
|
+
ops.append({"op": "set_ospfv2_network", "device": device, "process_id": int(process_id), "network": network, "wildcard": wildcard, "area": int(area)})
|
|
951
|
+
for device, asn, network, wildcard, no_auto in eigrp_ipv4_pattern.findall(segment):
|
|
952
|
+
ops.append({"op": "set_eigrp_ipv4_network", "device": device, "asn": int(asn), "network": network, "wildcard": wildcard, "no_auto_summary": bool(no_auto)})
|
|
953
|
+
for device, network, no_auto in ripv2_pattern.findall(segment):
|
|
954
|
+
ops.append({"op": "set_ripv2_network", "device": device, "network": network, "no_auto_summary": bool(no_auto)})
|
|
955
|
+
for device, network, prefix, next_hop in static_route_pattern.findall(segment):
|
|
956
|
+
ops.append({"op": "set_static_route", "device": device, "network": network, "prefix": int(prefix), "next_hop": next_hop})
|
|
957
|
+
for device, interface_name, helper in dhcp_relay_pattern.findall(segment):
|
|
958
|
+
ops.append({"op": "set_dhcp_relay", "device": device, "interface": interface_name, "helper": helper})
|
|
959
|
+
for device, role, interface_name in nat_inside_outside_pattern.findall(segment):
|
|
960
|
+
ops.append({"op": "set_nat_interface", "device": device, "role": role.lower(), "interface": interface_name})
|
|
961
|
+
for device, inside_local, inside_global in nat_static_pattern.findall(segment):
|
|
962
|
+
ops.append({"op": "set_nat_static", "device": device, "inside_local": inside_local, "inside_global": inside_global})
|
|
963
|
+
for device, acl, interface_name, overload in pat_pattern.findall(segment):
|
|
964
|
+
ops.append({"op": "set_pat_overload", "device": device, "acl": acl, "interface": interface_name, "overload": bool(overload)})
|
|
965
|
+
for device, domain, username, password, modulus in ssh_ios_pattern.findall(segment):
|
|
966
|
+
ops.append({"op": "set_ssh_ios", "device": device, "domain": domain, "username": username, "password": password, "modulus": int(modulus) if modulus else 1024})
|
|
967
|
+
for device, server in ntp_ios_pattern.findall(segment):
|
|
968
|
+
ops.append({"op": "set_ntp_server", "device": device, "server": server})
|
|
969
|
+
for device, server in syslog_ios_pattern.findall(segment):
|
|
970
|
+
ops.append({"op": "set_syslog_server", "device": device, "server": server})
|
|
971
|
+
for device, asn, neighbor, remote_as, network, mask in bgp_pattern.findall(segment):
|
|
972
|
+
ops.append(
|
|
973
|
+
{
|
|
974
|
+
"op": "set_bgp_neighbor",
|
|
975
|
+
"device": device,
|
|
976
|
+
"asn": int(asn),
|
|
977
|
+
"neighbor": neighbor,
|
|
978
|
+
"remote_as": int(remote_as),
|
|
979
|
+
"network": network or None,
|
|
980
|
+
"mask": mask or None,
|
|
981
|
+
}
|
|
982
|
+
)
|
|
748
983
|
for device, community, mode in snmp_pattern.findall(segment):
|
|
749
984
|
ops.append({"op": "set_snmp_community", "device": device, "community": community, "mode": mode.lower()})
|
|
750
985
|
for device, destination, port, version, interface_name, direction in netflow_pattern.findall(segment):
|
|
@@ -759,6 +994,110 @@ def _extract_router_ops(prompt: str) -> list[dict[str, object]]:
|
|
|
759
994
|
"direction": direction.lower() if direction else None,
|
|
760
995
|
}
|
|
761
996
|
)
|
|
997
|
+
for device, tunnel_interface, source, destination, ip, prefix in gre_tunnel_pattern.findall(segment):
|
|
998
|
+
ops.append(
|
|
999
|
+
{
|
|
1000
|
+
"op": "set_gre_tunnel",
|
|
1001
|
+
"device": device,
|
|
1002
|
+
"interface": tunnel_interface,
|
|
1003
|
+
"source": source,
|
|
1004
|
+
"destination": destination,
|
|
1005
|
+
"ip": ip or None,
|
|
1006
|
+
"prefix": int(prefix) if prefix else None,
|
|
1007
|
+
}
|
|
1008
|
+
)
|
|
1009
|
+
for device, interface_name, authentication in ppp_pattern.findall(segment):
|
|
1010
|
+
ops.append(
|
|
1011
|
+
{
|
|
1012
|
+
"op": "set_ppp_interface",
|
|
1013
|
+
"device": device,
|
|
1014
|
+
"interface": interface_name,
|
|
1015
|
+
"authentication": authentication.lower() if authentication else None,
|
|
1016
|
+
}
|
|
1017
|
+
)
|
|
1018
|
+
for device, name, encryption, integrity in ipsec_transform_pattern.findall(segment):
|
|
1019
|
+
ops.append(
|
|
1020
|
+
{
|
|
1021
|
+
"op": "set_ipsec_transform_set",
|
|
1022
|
+
"device": device,
|
|
1023
|
+
"name": name,
|
|
1024
|
+
"encryption": encryption,
|
|
1025
|
+
"integrity": integrity,
|
|
1026
|
+
}
|
|
1027
|
+
)
|
|
1028
|
+
for device, map_name, sequence, peer, transform_set, acl_name, interface_name in crypto_map_pattern.findall(segment):
|
|
1029
|
+
ops.append(
|
|
1030
|
+
{
|
|
1031
|
+
"op": "set_crypto_map",
|
|
1032
|
+
"device": device,
|
|
1033
|
+
"map_name": map_name,
|
|
1034
|
+
"sequence": int(sequence),
|
|
1035
|
+
"peer": peer,
|
|
1036
|
+
"transform_set": transform_set,
|
|
1037
|
+
"acl_name": acl_name,
|
|
1038
|
+
"interface": interface_name or None,
|
|
1039
|
+
}
|
|
1040
|
+
)
|
|
1041
|
+
for device, name, protocol, interface_name, direction in cbac_pattern.findall(segment):
|
|
1042
|
+
ops.append(
|
|
1043
|
+
{
|
|
1044
|
+
"op": "set_cbac_inspect",
|
|
1045
|
+
"device": device,
|
|
1046
|
+
"name": name,
|
|
1047
|
+
"protocol": protocol.lower(),
|
|
1048
|
+
"interface": interface_name,
|
|
1049
|
+
"direction": direction.lower(),
|
|
1050
|
+
}
|
|
1051
|
+
)
|
|
1052
|
+
for device, zone, interface_name in zfw_zone_interface_pattern.findall(segment):
|
|
1053
|
+
ops.append({"op": "set_zfw_zone_interface", "device": device, "zone": zone, "interface": interface_name})
|
|
1054
|
+
for device, pair_name, source, destination, policy in zfw_zone_pair_pattern.findall(segment):
|
|
1055
|
+
ops.append(
|
|
1056
|
+
{
|
|
1057
|
+
"op": "set_zfw_zone_pair",
|
|
1058
|
+
"device": device,
|
|
1059
|
+
"pair_name": pair_name,
|
|
1060
|
+
"source": source,
|
|
1061
|
+
"destination": destination,
|
|
1062
|
+
"policy": policy,
|
|
1063
|
+
}
|
|
1064
|
+
)
|
|
1065
|
+
for device, class_map, protocol, policy_map, action in zfw_policy_pattern.findall(segment):
|
|
1066
|
+
ops.append(
|
|
1067
|
+
{
|
|
1068
|
+
"op": "set_zfw_policy",
|
|
1069
|
+
"device": device,
|
|
1070
|
+
"class_map": class_map,
|
|
1071
|
+
"protocol": protocol.lower(),
|
|
1072
|
+
"policy_map": policy_map,
|
|
1073
|
+
"action": action.lower(),
|
|
1074
|
+
}
|
|
1075
|
+
)
|
|
1076
|
+
for device, source_address, port, max_ephones, max_dn in telephony_service_pattern.findall(segment):
|
|
1077
|
+
ops.append(
|
|
1078
|
+
{
|
|
1079
|
+
"op": "set_telephony_service",
|
|
1080
|
+
"device": device.strip(),
|
|
1081
|
+
"source_address": source_address,
|
|
1082
|
+
"port": int(port),
|
|
1083
|
+
"max_ephones": int(max_ephones) if max_ephones else None,
|
|
1084
|
+
"max_dn": int(max_dn) if max_dn else None,
|
|
1085
|
+
}
|
|
1086
|
+
)
|
|
1087
|
+
for device, dn_id, number in ephone_dn_pattern.findall(segment):
|
|
1088
|
+
ops.append({"op": "set_ephone_dn", "device": device.strip(), "dn_id": int(dn_id), "number": number})
|
|
1089
|
+
for device, ephone_id, mac, button in ephone_pattern.findall(segment):
|
|
1090
|
+
ops.append({"op": "set_ephone", "device": device.strip(), "ephone_id": int(ephone_id), "mac": mac.upper(), "button": button})
|
|
1091
|
+
for device, peer_id, destination_pattern, session_target in dial_peer_pattern.findall(segment):
|
|
1092
|
+
ops.append(
|
|
1093
|
+
{
|
|
1094
|
+
"op": "set_dial_peer_voice",
|
|
1095
|
+
"device": device.strip(),
|
|
1096
|
+
"peer_id": int(peer_id),
|
|
1097
|
+
"destination_pattern": destination_pattern,
|
|
1098
|
+
"session_target": session_target,
|
|
1099
|
+
}
|
|
1100
|
+
)
|
|
762
1101
|
return ops
|
|
763
1102
|
|
|
764
1103
|
|
|
@@ -930,6 +1269,36 @@ def _extract_iot_ops(prompt: str) -> list[dict[str, object]]:
|
|
|
930
1269
|
return ops
|
|
931
1270
|
|
|
932
1271
|
|
|
1272
|
+
def _decode_prompt_string(value: str) -> str:
|
|
1273
|
+
return (
|
|
1274
|
+
value.replace(r"\\", "\0")
|
|
1275
|
+
.replace(r"\"", '"')
|
|
1276
|
+
.replace(r"\n", "\n")
|
|
1277
|
+
.replace(r"\r", "\r")
|
|
1278
|
+
.replace(r"\t", "\t")
|
|
1279
|
+
.replace("\0", "\\")
|
|
1280
|
+
)
|
|
1281
|
+
|
|
1282
|
+
|
|
1283
|
+
def _extract_programming_ops(prompt: str) -> list[dict[str, object]]:
|
|
1284
|
+
ops: list[dict[str, object]] = []
|
|
1285
|
+
script_pattern = re.compile(
|
|
1286
|
+
r'set\s+"((?:\\.|[^"\\])*)"\s+script\s+app\s+"((?:\\.|[^"\\])*)"\s+file\s+"((?:\\.|[^"\\])*)"\s+content\s+"((?:\\.|[^"\\])*)"',
|
|
1287
|
+
flags=re.IGNORECASE | re.DOTALL,
|
|
1288
|
+
)
|
|
1289
|
+
for device, app_name, file_name, content in script_pattern.findall(prompt):
|
|
1290
|
+
ops.append(
|
|
1291
|
+
{
|
|
1292
|
+
"op": "set_script_file_content",
|
|
1293
|
+
"device": _decode_prompt_string(device).strip(),
|
|
1294
|
+
"app_name": _decode_prompt_string(app_name).strip(),
|
|
1295
|
+
"file_name": _decode_prompt_string(file_name).strip(),
|
|
1296
|
+
"content": _decode_prompt_string(content),
|
|
1297
|
+
}
|
|
1298
|
+
)
|
|
1299
|
+
return ops
|
|
1300
|
+
|
|
1301
|
+
|
|
933
1302
|
def parse_intent(prompt: str) -> IntentPlan:
|
|
934
1303
|
pkt_path_match = re.search(r"([A-Za-z]:\\[^\"\n]+?\.pkt)\b", prompt, flags=re.IGNORECASE)
|
|
935
1304
|
pkt_path = pkt_path_match.group(1) if pkt_path_match else None
|
|
@@ -957,6 +1326,48 @@ def parse_intent(prompt: str) -> IntentPlan:
|
|
|
957
1326
|
or network_style == "l2_security_monitoring"
|
|
958
1327
|
):
|
|
959
1328
|
capability_set.add("qos")
|
|
1329
|
+
if network_style == "automation_controller" or re.search(r"\b(?:network controller|netcon|blockly|iox)\b", lowered):
|
|
1330
|
+
if re.search(r"\bpython\b", lowered):
|
|
1331
|
+
capability_set.add("python_programming")
|
|
1332
|
+
if re.search(r"\b(?:tcp|udp)\b", lowered) and re.search(r"\b(?:app|application|test app)\b", lowered):
|
|
1333
|
+
capability_set.add("tcp_udp_app")
|
|
1334
|
+
if "ospf" in capability_set and not re.search(r"\b(?:ipv6|ospfv3)\b", lowered):
|
|
1335
|
+
capability_set.add("ospfv2")
|
|
1336
|
+
if "eigrp" in capability_set and not re.search(r"\bipv6\b", lowered):
|
|
1337
|
+
capability_set.add("eigrp_ipv4")
|
|
1338
|
+
if "rip" in capability_set and not re.search(r"\b(?:ipv6|ripng)\b", lowered):
|
|
1339
|
+
capability_set.add("ripv2")
|
|
1340
|
+
if "nat" in capability_set:
|
|
1341
|
+
if re.search(r"\bpat\b|\boverload\b", lowered):
|
|
1342
|
+
capability_set.add("pat")
|
|
1343
|
+
if re.search(r"\bstatic\s+nat\b|\bnat\s+static\b", lowered):
|
|
1344
|
+
capability_set.add("nat_static")
|
|
1345
|
+
if re.search(r"\bdynamic\s+nat\b|\bnat\s+dynamic\b", lowered):
|
|
1346
|
+
capability_set.add("nat_dynamic")
|
|
1347
|
+
if network_style == "ipv4_routing_management" and not {"nat_static", "nat_dynamic"} & capability_set:
|
|
1348
|
+
capability_set.update({"nat_static", "nat_dynamic"})
|
|
1349
|
+
if "ssh" in capability_set:
|
|
1350
|
+
capability_set.add("ssh_ios")
|
|
1351
|
+
if "ntp" in capability_set:
|
|
1352
|
+
capability_set.add("ntp_ios")
|
|
1353
|
+
if (
|
|
1354
|
+
"server_syslog" in capability_set
|
|
1355
|
+
and re.search(r"\b(?:server|ftp|email|aaa|dns)\b", lowered)
|
|
1356
|
+
and not re.search(r"\b(?:logging\s+host|set\s+\S+\s+syslog\s+server)\b", lowered)
|
|
1357
|
+
):
|
|
1358
|
+
capability_set.discard("syslog_ios")
|
|
1359
|
+
if re.search(r"\blogging\s+host\b", lowered):
|
|
1360
|
+
capability_set.add("syslog_ios")
|
|
1361
|
+
if re.search(r"\bdefault\s+route\b|\b0\.0\.0\.0/0\b", lowered):
|
|
1362
|
+
capability_set.update({"static_route", "default_route"})
|
|
1363
|
+
elif re.search(r"\bstatic\s+route\b|\bip\s+route\b", lowered):
|
|
1364
|
+
capability_set.add("static_route")
|
|
1365
|
+
if re.search(r"\bdhcp\s+relay\b|\bdhcp-relay\b|\bhelper-address\b|\bip\s+helper\b", lowered):
|
|
1366
|
+
capability_set.add("dhcp_relay")
|
|
1367
|
+
if not re.search(r"\b(?:dhcp\s+pool|server\s+dhcp|dhcp\s+server|default-router)\b", lowered):
|
|
1368
|
+
capability_set.discard("dhcp_pool")
|
|
1369
|
+
capability_set.discard("router_dhcp")
|
|
1370
|
+
capability_set.discard("server_dhcp")
|
|
960
1371
|
if "dhcp_snooping" in capability_set and not re.search(r"\b(?:dhcp\s+pool|server\s+dhcp|dhcp\s+server|default-router)\b", lowered):
|
|
961
1372
|
capability_set.discard("dhcp_pool")
|
|
962
1373
|
capability_set.discard("router_dhcp")
|
|
@@ -1010,6 +1421,7 @@ def parse_intent(prompt: str) -> IntentPlan:
|
|
|
1010
1421
|
management_ops = _extract_management_ops(prompt)
|
|
1011
1422
|
verification_ops = _extract_verification_ops(prompt)
|
|
1012
1423
|
iot_ops = _extract_iot_ops(prompt)
|
|
1424
|
+
programming_ops = _extract_programming_ops(prompt)
|
|
1013
1425
|
|
|
1014
1426
|
if any(op["op"] == "associate_wireless_client" for op in wireless_ops):
|
|
1015
1427
|
capability_set.update({"wireless_client", "wireless_client_association"})
|
|
@@ -1030,6 +1442,28 @@ def parse_intent(prompt: str) -> IntentPlan:
|
|
|
1030
1442
|
capability_set.add("end_device_mutation")
|
|
1031
1443
|
if iot_ops:
|
|
1032
1444
|
capability_set.update({"iot", "iot_registration"})
|
|
1445
|
+
if any(op.get("op") == "set_iot_rule_state" for op in iot_ops):
|
|
1446
|
+
capability_set.add("iot_control")
|
|
1447
|
+
for operation in programming_ops:
|
|
1448
|
+
file_name = str(operation.get("file_name") or "").lower()
|
|
1449
|
+
app_name = str(operation.get("app_name") or "").lower()
|
|
1450
|
+
content = str(operation.get("content") or "").lower()
|
|
1451
|
+
if file_name.endswith(".py") or "python" in app_name:
|
|
1452
|
+
capability_set.add("python_programming")
|
|
1453
|
+
if file_name.endswith(".js") or "javascript" in app_name:
|
|
1454
|
+
capability_set.add("javascript_programming")
|
|
1455
|
+
if file_name.endswith(".visual") or "visual" in app_name or "<xml" in content:
|
|
1456
|
+
capability_set.add("blockly_programming")
|
|
1457
|
+
if "tcp" in " ".join([file_name, app_name, content]) or "udp" in " ".join([file_name, app_name, content]):
|
|
1458
|
+
capability_set.add("tcp_udp_app")
|
|
1459
|
+
if "realhttp" in content or "real http" in app_name:
|
|
1460
|
+
capability_set.add("real_http")
|
|
1461
|
+
if "realws" in content or "websocket" in content or "websocket" in app_name:
|
|
1462
|
+
capability_set.add("real_websocket")
|
|
1463
|
+
if "mqtt" in content or "mqtt" in app_name:
|
|
1464
|
+
capability_set.add("mqtt")
|
|
1465
|
+
if programming_ops and capability_set & {"python_programming", "javascript_programming", "blockly_programming"} and not capability_set & {"real_http", "real_websocket", "mqtt"}:
|
|
1466
|
+
network_style = network_style or "automation_controller"
|
|
1033
1467
|
switch_op_names = {str(op.get("op")) for op in switch_ops}
|
|
1034
1468
|
if "set_dhcp_snooping" in switch_op_names:
|
|
1035
1469
|
capability_set.add("dhcp_snooping")
|
|
@@ -1043,11 +1477,50 @@ def parse_intent(prompt: str) -> IntentPlan:
|
|
|
1043
1477
|
capability_set.add("span")
|
|
1044
1478
|
if "set_port_security" in switch_op_names:
|
|
1045
1479
|
capability_set.add("port_security")
|
|
1480
|
+
if "set_dot1x" in switch_op_names:
|
|
1481
|
+
capability_set.add("dot1x")
|
|
1482
|
+
if "set_qos_policy" in switch_op_names:
|
|
1483
|
+
capability_set.add("qos")
|
|
1484
|
+
if "set_stp" in switch_op_names:
|
|
1485
|
+
capability_set.add("stp")
|
|
1486
|
+
for op in switch_ops:
|
|
1487
|
+
if op.get("op") == "set_stp" and re.search(r"(?:rapid|rstp)", str(op.get("mode") or ""), flags=re.IGNORECASE):
|
|
1488
|
+
capability_set.add("rstp")
|
|
1489
|
+
if "set_etherchannel" in switch_op_names:
|
|
1490
|
+
capability_set.add("etherchannel")
|
|
1491
|
+
for op in switch_ops:
|
|
1492
|
+
if op.get("op") != "set_etherchannel":
|
|
1493
|
+
continue
|
|
1494
|
+
mode = str(op.get("mode") or "").lower()
|
|
1495
|
+
if mode in {"active", "passive"}:
|
|
1496
|
+
capability_set.add("lacp")
|
|
1497
|
+
if mode in {"desirable", "auto"}:
|
|
1498
|
+
capability_set.add("pagp")
|
|
1499
|
+
if "set_vtp" in switch_op_names:
|
|
1500
|
+
capability_set.add("vtp")
|
|
1501
|
+
if "set_dtp" in switch_op_names:
|
|
1502
|
+
capability_set.add("dtp")
|
|
1046
1503
|
router_op_names = {str(op.get("op")) for op in router_ops}
|
|
1504
|
+
if "set_bgp_neighbor" in router_op_names:
|
|
1505
|
+
capability_set.add("bgp")
|
|
1047
1506
|
if "set_snmp_community" in router_op_names:
|
|
1048
1507
|
capability_set.add("snmp")
|
|
1049
1508
|
if "set_netflow" in router_op_names:
|
|
1050
1509
|
capability_set.add("netflow")
|
|
1510
|
+
if "set_gre_tunnel" in router_op_names:
|
|
1511
|
+
capability_set.add("gre")
|
|
1512
|
+
if "set_ppp_interface" in router_op_names:
|
|
1513
|
+
capability_set.add("ppp")
|
|
1514
|
+
if router_op_names & {"set_ipsec_transform_set", "set_crypto_map"}:
|
|
1515
|
+
capability_set.update({"ipsec", "vpn"})
|
|
1516
|
+
if "set_cbac_inspect" in router_op_names:
|
|
1517
|
+
capability_set.update({"cbac", "security_edge"})
|
|
1518
|
+
if router_op_names & {"set_zfw_zone_interface", "set_zfw_zone_pair", "set_zfw_policy"}:
|
|
1519
|
+
capability_set.update({"zfw", "security_edge"})
|
|
1520
|
+
if "set_qos_policy" not in switch_op_names:
|
|
1521
|
+
capability_set.discard("qos")
|
|
1522
|
+
capability_set.discard("server_http")
|
|
1523
|
+
capability_set.discard("server_https")
|
|
1051
1524
|
if router_op_names & {"enable_ipv6_unicast_routing", "set_ipv6_address", "set_ipv6_slaac"}:
|
|
1052
1525
|
capability_set.add("ipv6_slaac")
|
|
1053
1526
|
if "set_dhcpv6_pool" in router_op_names:
|
|
@@ -1060,16 +1533,61 @@ def parse_intent(prompt: str) -> IntentPlan:
|
|
|
1060
1533
|
capability_set.add("ripng")
|
|
1061
1534
|
if "set_hsrp_ipv6" in router_op_names:
|
|
1062
1535
|
capability_set.add("hsrp")
|
|
1536
|
+
if "set_ospfv2_network" in router_op_names:
|
|
1537
|
+
capability_set.add("ospfv2")
|
|
1538
|
+
if "set_eigrp_ipv4_network" in router_op_names:
|
|
1539
|
+
capability_set.add("eigrp_ipv4")
|
|
1540
|
+
if "set_ripv2_network" in router_op_names:
|
|
1541
|
+
capability_set.add("ripv2")
|
|
1542
|
+
if "set_static_route" in router_op_names:
|
|
1543
|
+
capability_set.add("static_route")
|
|
1544
|
+
if any(op.get("op") == "set_static_route" and op.get("network") == "0.0.0.0" and op.get("prefix") == 0 for op in router_ops):
|
|
1545
|
+
capability_set.add("default_route")
|
|
1546
|
+
if "set_dhcp_relay" in router_op_names:
|
|
1547
|
+
capability_set.add("dhcp_relay")
|
|
1548
|
+
if "set_nat_interface" in router_op_names:
|
|
1549
|
+
capability_set.add("nat")
|
|
1550
|
+
if "set_nat_static" in router_op_names:
|
|
1551
|
+
capability_set.update({"nat", "nat_static"})
|
|
1552
|
+
if "set_pat_overload" in router_op_names:
|
|
1553
|
+
capability_set.update({"nat", "pat"})
|
|
1554
|
+
if "set_ssh_ios" in router_op_names:
|
|
1555
|
+
capability_set.update({"ssh", "ssh_ios"})
|
|
1556
|
+
if "set_ntp_server" in router_op_names:
|
|
1557
|
+
capability_set.update({"ntp", "ntp_ios"})
|
|
1558
|
+
if "set_syslog_server" in router_op_names:
|
|
1559
|
+
capability_set.update({"syslog_ios"})
|
|
1560
|
+
if router_op_names & {"set_telephony_service", "set_ephone_dn", "set_ephone", "set_dial_peer_voice"}:
|
|
1561
|
+
capability_set.update({"voip", "call_manager"})
|
|
1562
|
+
if router_op_names & {"set_ephone_dn", "set_ephone"}:
|
|
1563
|
+
capability_set.add("ip_phone")
|
|
1063
1564
|
if capability_set & {"vpn", "ipsec", "gre", "ppp", "security_edge"}:
|
|
1064
1565
|
network_style = network_style or "wan_security"
|
|
1065
1566
|
if capability_set & {"ipv6_slaac", "dhcpv6_stateful", "dhcpv6_stateless", "ospfv3", "eigrp_ipv6", "ripng", "hsrp"}:
|
|
1066
1567
|
network_style = network_style or "ipv6_routing"
|
|
1568
|
+
if capability_set & {"ospfv2", "eigrp_ipv4", "ripv2", "static_route", "default_route", "dhcp_relay", "nat_static", "nat_dynamic", "pat", "ssh_ios", "ntp_ios", "syslog_ios"}:
|
|
1569
|
+
network_style = network_style or "ipv4_routing_management"
|
|
1067
1570
|
if capability_set & {"dhcp_snooping", "dai", "dot1x", "lldp", "rep", "snmp", "netflow", "span", "qos", "port_security"}:
|
|
1068
1571
|
network_style = network_style or "l2_security_monitoring"
|
|
1572
|
+
if capability_set & {"bgp", "stp", "rstp", "etherchannel", "lacp", "pagp", "vtp", "dtp"}:
|
|
1573
|
+
network_style = "l2_resiliency_routing"
|
|
1069
1574
|
if capability_set & {"wlc", "wpa_enterprise", "wep", "guest_wifi", "beamforming", "meraki", "cellular_5g", "bluetooth"}:
|
|
1070
1575
|
network_style = "wireless_advanced"
|
|
1576
|
+
if capability_set & {"coaxial", "cable_dsl", "central_office", "cell_tower", "power_distribution", "hot_swappable", "ios_license"}:
|
|
1577
|
+
network_style = "physical_media_device"
|
|
1071
1578
|
if capability_set & {"mqtt", "real_http", "real_websocket", "visual_scripting", "ptp", "profinet", "l2nat", "cyberobserver", "industrial_firewall"}:
|
|
1072
1579
|
network_style = "industrial_iot"
|
|
1580
|
+
if capability_set & {"real_http", "real_websocket"}:
|
|
1581
|
+
capability_set.discard("server_http")
|
|
1582
|
+
capability_set.discard("server_https")
|
|
1583
|
+
if not (
|
|
1584
|
+
iot_ops
|
|
1585
|
+
or re.search(r"\b(?:home\s+gateway|smart\s+home|iot\s+registration|iot\s+control|iot\s+rule)\b", lowered)
|
|
1586
|
+
or re.search(r"\bregister\s+.+\s+to\b", lowered)
|
|
1587
|
+
):
|
|
1588
|
+
capability_set.discard("iot")
|
|
1589
|
+
if capability_set & {"voip", "ip_phone", "call_manager", "linksys_voice"}:
|
|
1590
|
+
network_style = "voice_collaboration"
|
|
1073
1591
|
|
|
1074
1592
|
capabilities = sorted(capability_set)
|
|
1075
1593
|
|
|
@@ -1135,4 +1653,5 @@ def parse_intent(prompt: str) -> IntentPlan:
|
|
|
1135
1653
|
management_ops=management_ops,
|
|
1136
1654
|
verification_ops=verification_ops,
|
|
1137
1655
|
iot_ops=iot_ops,
|
|
1656
|
+
programming_ops=programming_ops,
|
|
1138
1657
|
)
|