packet-tracer-skill 0.1.0 → 0.2.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 (72) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/CITATION.cff +15 -0
  3. package/CODE_OF_CONDUCT.md +26 -0
  4. package/CONTRIBUTING.md +120 -0
  5. package/README.md +164 -543
  6. package/SECURITY.md +36 -0
  7. package/SKILL.md +46 -5
  8. package/bin/packet-tracer-skill.js +503 -7
  9. package/docs/curated-donor-registry.md +51 -0
  10. package/docs/discovery-keywords.md +55 -0
  11. package/docs/github-discussions-setup.md +34 -0
  12. package/docs/github-metadata.md +33 -0
  13. package/docs/hero-demo-plan.md +42 -0
  14. package/docs/publish-preview-roadmap.md +31 -0
  15. package/docs/release-checklist.md +60 -0
  16. package/docs/release-notes-0.2.0.md +25 -0
  17. package/docs/runtime-truth.md +49 -0
  18. package/docs/screenshots/.gitkeep +1 -0
  19. package/docs/screenshots/packet-tracer-topology-cropped.png +0 -0
  20. package/examples/README.md +72 -0
  21. package/examples/complex_campus_master_edit_v4.inventory.json +94 -0
  22. package/examples/gallery.md +39 -0
  23. package/examples/home_iot_cli_edit_v1.inventory.json +58 -0
  24. package/examples/index.json +127 -0
  25. package/examples/screenshots/complex_campus_master_edit_v4.png +0 -0
  26. package/examples/screenshots/home_iot_cli_edit_v1.png +0 -0
  27. package/examples/screenshots/service_heavy_cli_edit_v1.png +0 -0
  28. package/examples/screenshots/service_heavy_cli_edit_v1_dhcp.png +0 -0
  29. package/examples/screenshots/service_heavy_cli_edit_v1_dns.png +0 -0
  30. package/examples/screenshots/service_heavy_cli_edit_v1_ftp.png +0 -0
  31. package/examples/service_heavy_cli_edit_v1.inventory.json +51 -0
  32. package/package.json +41 -4
  33. package/references/curated-donor-registry.json +71 -0
  34. package/references/scenario-fixture-corpus.json +53 -0
  35. package/scripts/__pycache__/build_examples_index.cpython-314.pyc +0 -0
  36. package/scripts/__pycache__/build_sample_catalog.cpython-314.pyc +0 -0
  37. package/scripts/__pycache__/coverage_matrix.cpython-314.pyc +0 -0
  38. package/scripts/__pycache__/donor_diagnostics.cpython-314.pyc +0 -0
  39. package/scripts/__pycache__/generate_pkt.cpython-314.pyc +0 -0
  40. package/scripts/__pycache__/install_skill.cpython-314.pyc +0 -0
  41. package/scripts/__pycache__/intent_parser.cpython-314.pyc +0 -0
  42. package/scripts/__pycache__/packet_tracer_env.cpython-314.pyc +0 -0
  43. package/scripts/__pycache__/pkt_builder.cpython-314.pyc +0 -0
  44. package/scripts/__pycache__/pkt_codec.cpython-314.pyc +0 -0
  45. package/scripts/__pycache__/pkt_editor.cpython-314.pyc +0 -0
  46. package/scripts/__pycache__/pkt_transformer.cpython-314.pyc +0 -0
  47. package/scripts/__pycache__/remote_search.cpython-314.pyc +0 -0
  48. package/scripts/__pycache__/runtime_doctor.cpython-314.pyc +0 -0
  49. package/scripts/__pycache__/sample_catalog.cpython-314.pyc +0 -0
  50. package/scripts/__pycache__/sample_selector.cpython-314.pyc +0 -0
  51. package/scripts/__pycache__/twofish_diagnostics.cpython-314.pyc +0 -0
  52. package/scripts/__pycache__/twofish_runtime.cpython-314.pyc +0 -0
  53. package/scripts/__pycache__/workspace_repair.cpython-314.pyc +0 -0
  54. package/scripts/build_examples_index.py +314 -0
  55. package/scripts/coverage_matrix.py +783 -0
  56. package/scripts/donor_diagnostics.py +6 -3
  57. package/scripts/generate_pkt.py +2605 -98
  58. package/scripts/intent_parser.py +173 -18
  59. package/scripts/packet_tracer_env.py +127 -14
  60. package/scripts/pkt_editor.py +312 -12
  61. package/scripts/pkt_transformer.py +195 -9
  62. package/scripts/remote_search.py +169 -0
  63. package/scripts/runtime_doctor.py +286 -0
  64. package/scripts/sample_catalog.py +643 -38
  65. package/scripts/sample_selector.py +251 -5
  66. package/scripts/setup.ps1 +2 -1
  67. package/scripts/twofish_diagnostics.py +20 -23
  68. package/scripts/twofish_runtime.py +101 -0
  69. package/scripts/vendor/README.md +13 -1
  70. package/scripts/vendor/__pycache__/twofish.cpython-314.pyc +0 -0
  71. package/scripts/vendor/twofish.py +11 -9
  72. package/scripts/workspace_repair.py +69 -2
@@ -0,0 +1,314 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+
7
+ ROOT = Path(__file__).resolve().parents[1]
8
+ EXAMPLES_DIR = ROOT / "examples"
9
+ SCREENSHOTS_DIR = EXAMPLES_DIR / "screenshots"
10
+ PREVIEWS_DIR = EXAMPLES_DIR / "previews"
11
+ INDEX_PATH = EXAMPLES_DIR / "index.json"
12
+ GALLERY_PATH = EXAMPLES_DIR / "gallery.md"
13
+
14
+
15
+ TITLE_BY_FAMILY = {
16
+ "campus": "Complex Campus",
17
+ "home_iot": "Home IoT",
18
+ "service_heavy": "Service Heavy",
19
+ "wan_security_edge": "WAN Security Edge",
20
+ }
21
+
22
+ SUMMARY_BY_FAMILY = {
23
+ "campus": "Management VLAN, Telnet, ACL, DNS, email, AAA, and multi-SSID wireless campus edit.",
24
+ "home_iot": "Home gateway and IoT device onboarding with gateway-backed registration state.",
25
+ "service_heavy": "Service-rich server lab with DNS, DHCP, FTP, email, syslog, AAA, and detailed service metadata.",
26
+ "wan_security_edge": "WAN/security-edge lab with multilayer, tunnel, VPN, and edge-policy coverage metadata.",
27
+ }
28
+
29
+ CAPABILITIES_BY_FAMILY = {
30
+ "campus": [
31
+ "management_vlan",
32
+ "telnet",
33
+ "acl",
34
+ "server_dns",
35
+ "server_email",
36
+ "server_aaa",
37
+ "wireless_mutation",
38
+ ],
39
+ "home_iot": [
40
+ "iot",
41
+ "iot_registration",
42
+ "wireless_ap",
43
+ ],
44
+ "service_heavy": [
45
+ "server_dns",
46
+ "server_dhcp",
47
+ "server_ftp",
48
+ "server_email",
49
+ "server_syslog",
50
+ "server_aaa",
51
+ ],
52
+ "wan_security_edge": [
53
+ "multilayer_switching",
54
+ "vpn",
55
+ "ipsec",
56
+ "gre",
57
+ "ppp",
58
+ "security_edge",
59
+ ],
60
+ }
61
+
62
+ FIXTURE_BY_FAMILY = {
63
+ "campus": "campus_core_complex",
64
+ "home_iot": "home_iot_complex",
65
+ "service_heavy": "service_heavy_complex",
66
+ "wan_security_edge": "wan_security_complex",
67
+ }
68
+
69
+ CARD_THEME_BY_FAMILY = {
70
+ "campus": {"accent": "#0f766e", "panel": "#ecfeff", "text": "#0f172a"},
71
+ "home_iot": {"accent": "#1d4ed8", "panel": "#eff6ff", "text": "#0f172a"},
72
+ "service_heavy": {"accent": "#7c3aed", "panel": "#f5f3ff", "text": "#0f172a"},
73
+ "general": {"accent": "#475569", "panel": "#f8fafc", "text": "#0f172a"},
74
+ }
75
+
76
+
77
+ def _manifest_files() -> list[Path]:
78
+ return sorted(
79
+ path
80
+ for path in EXAMPLES_DIR.glob("*.inventory.json")
81
+ if path.is_file()
82
+ )
83
+
84
+
85
+ def _detect_screenshots(example_name: str) -> list[str]:
86
+ screenshots: list[Path] = []
87
+ primary = SCREENSHOTS_DIR / f"{example_name}.png"
88
+ if primary.exists():
89
+ screenshots.append(primary)
90
+
91
+ for candidate in sorted(SCREENSHOTS_DIR.glob(f"{example_name}_*.png")):
92
+ if candidate not in screenshots:
93
+ screenshots.append(candidate)
94
+
95
+ return [path.relative_to(ROOT).as_posix() for path in screenshots]
96
+
97
+
98
+ def _preview_path(example_name: str) -> Path:
99
+ return PREVIEWS_DIR / f"{example_name}.svg"
100
+
101
+
102
+ def _escape_xml(value: object) -> str:
103
+ text = str(value)
104
+ return (
105
+ text.replace("&", "&")
106
+ .replace("<", "&lt;")
107
+ .replace(">", "&gt;")
108
+ .replace('"', "&quot;")
109
+ )
110
+
111
+
112
+ def _render_example_preview(manifest: dict[str, object]) -> str:
113
+ family = str(manifest.get("scenario_family") or manifest.get("topology_summary", {}).get("network_style") or "general")
114
+ theme = CARD_THEME_BY_FAMILY.get(family, CARD_THEME_BY_FAMILY["general"])
115
+ title = TITLE_BY_FAMILY.get(family, str(manifest["example_name"]).replace("_", " ").title())
116
+ summary = SUMMARY_BY_FAMILY.get(family, f"Curated {family} example manifest.")
117
+ capabilities = ", ".join(CAPABILITIES_BY_FAMILY.get(family, []))
118
+
119
+ detail_lines: list[str] = []
120
+ topology_summary = manifest.get("topology_summary", {})
121
+ service_summary = manifest.get("service_summary") or manifest.get("server_service_summary") or {}
122
+ if family == "campus":
123
+ detail_lines.append(f"Core router: {topology_summary.get('core_router', 'n/a')}")
124
+ detail_lines.append(f"Managed switches: {len(topology_summary.get('switches_with_management', []))}")
125
+ detail_lines.append(f"Wireless routers: {len(topology_summary.get('wireless_routers', []))}")
126
+ elif family == "home_iot":
127
+ detail_lines.append(f"Gateway: {topology_summary.get('gateway', 'n/a')}")
128
+ detail_lines.append(f"IoT things: {len(topology_summary.get('iot_things', []))}")
129
+ detail_lines.append(f"Services: {', '.join(next(iter(service_summary.values()), [])) or 'n/a'}")
130
+ elif family == "service_heavy":
131
+ detail_lines.append(f"Core server: {topology_summary.get('core_server', 'n/a')}")
132
+ detail_lines.append(f"Services: {len(next(iter(service_summary.values()), []))}")
133
+ service_details = manifest.get("service_details", {})
134
+ server0 = next(iter(service_details.values()), {})
135
+ if server0:
136
+ detail_lines.append(f"Email domain: {server0.get('email_domain', 'n/a')}")
137
+ detail_lines.append(f"AAA port: {server0.get('aaa_auth_port', 'n/a')}")
138
+ else:
139
+ detail_lines.append("Curated example preview")
140
+
141
+ while len(detail_lines) < 4:
142
+ detail_lines.append("")
143
+
144
+ y = 150
145
+ detail_svg = []
146
+ for line in detail_lines:
147
+ detail_svg.append(
148
+ f'<text x="72" y="{y}" font-family="Segoe UI, Arial, sans-serif" font-size="26" fill="{theme["text"]}">{_escape_xml(line)}</text>'
149
+ )
150
+ y += 52
151
+
152
+ capability_lines = []
153
+ cap_y = 430
154
+ for chunk_start in range(0, len(CAPABILITIES_BY_FAMILY.get(family, [])), 3):
155
+ chunk = CAPABILITIES_BY_FAMILY.get(family, [])[chunk_start:chunk_start + 3]
156
+ capability_lines.append(
157
+ f'<text x="72" y="{cap_y}" font-family="Consolas, monospace" font-size="24" fill="{theme["text"]}">{_escape_xml(", ".join(chunk))}</text>'
158
+ )
159
+ cap_y += 40
160
+
161
+ return f"""<svg xmlns="http://www.w3.org/2000/svg" width="1600" height="900" viewBox="0 0 1600 900">
162
+ <rect width="1600" height="900" fill="#f8fafc"/>
163
+ <rect x="42" y="42" width="1516" height="816" rx="28" fill="{theme["panel"]}" stroke="{theme["accent"]}" stroke-width="8"/>
164
+ <rect x="42" y="42" width="1516" height="120" rx="28" fill="{theme["accent"]}"/>
165
+ <text x="72" y="118" font-family="Segoe UI, Arial, sans-serif" font-size="52" font-weight="700" fill="#ffffff">{_escape_xml(title)}</text>
166
+ <text x="72" y="220" font-family="Segoe UI, Arial, sans-serif" font-size="30" fill="{theme["text"]}">{_escape_xml(summary)}</text>
167
+ <text x="72" y="388" font-family="Segoe UI, Arial, sans-serif" font-size="28" font-weight="700" fill="{theme["accent"]}">Capabilities</text>
168
+ {''.join(detail_svg)}
169
+ {''.join(capability_lines)}
170
+ <rect x="1020" y="170" width="458" height="560" rx="20" fill="#ffffff" stroke="{theme["accent"]}" stroke-width="4"/>
171
+ <text x="1055" y="235" font-family="Segoe UI, Arial, sans-serif" font-size="30" font-weight="700" fill="{theme["accent"]}">Artifact Policy</text>
172
+ <text x="1055" y="295" font-family="Segoe UI, Arial, sans-serif" font-size="24" fill="{theme["text"]}">- raw .pkt binary is not committed</text>
173
+ <text x="1055" y="340" font-family="Segoe UI, Arial, sans-serif" font-size="24" fill="{theme["text"]}">- inventory manifest is committed</text>
174
+ <text x="1055" y="385" font-family="Segoe UI, Arial, sans-serif" font-size="24" fill="{theme["text"]}">- preview is generated from manifest</text>
175
+ <text x="1055" y="470" font-family="Segoe UI, Arial, sans-serif" font-size="26" font-weight="700" fill="{theme["accent"]}">Scenario family</text>
176
+ <text x="1055" y="515" font-family="Consolas, monospace" font-size="30" fill="{theme["text"]}">{_escape_xml(family)}</text>
177
+ <text x="1055" y="605" font-family="Segoe UI, Arial, sans-serif" font-size="26" font-weight="700" fill="{theme["accent"]}">Verification</text>
178
+ <text x="1055" y="650" font-family="Segoe UI, Arial, sans-serif" font-size="24" fill="{theme["text"]}">inventory roundtrip verified</text>
179
+ <text x="1055" y="695" font-family="Segoe UI, Arial, sans-serif" font-size="24" fill="{theme["text"]}">full pytest passed</text>
180
+ </svg>
181
+ """
182
+
183
+
184
+ def _write_preview(manifest_path: Path) -> str:
185
+ PREVIEWS_DIR.mkdir(parents=True, exist_ok=True)
186
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
187
+ path = _preview_path(str(manifest["example_name"]))
188
+ path.write_text(_render_example_preview(manifest), encoding="utf-8")
189
+ return path.relative_to(ROOT).as_posix()
190
+
191
+
192
+ def _build_entry(manifest_path: Path) -> dict[str, object]:
193
+ payload = json.loads(manifest_path.read_text(encoding="utf-8"))
194
+ name = str(payload["example_name"])
195
+ family = str(payload.get("scenario_family") or payload.get("topology_summary", {}).get("network_style") or "general")
196
+ verification = dict(payload.get("verification_status") or {})
197
+ acceptance_rank = 3 if verification.get("inventory_roundtrip_verified") and verification.get("full_pytest_passed") else 1
198
+ acceptance_label = "known_working_example" if acceptance_rank == 3 else "partially_verified_example"
199
+ source_mode = str(payload.get("source_mode") or "").strip()
200
+ donor_origin = "donor-backed" if "donor-backed" in source_mode else (source_mode or "unknown")
201
+ screenshots = _detect_screenshots(name)
202
+ preview = _write_preview(manifest_path)
203
+ screenshot = screenshots[0] if screenshots else None
204
+ detail_images = screenshots[1:] if len(screenshots) > 1 else []
205
+ primary_capabilities = CAPABILITIES_BY_FAMILY.get(family, [])
206
+ acceptance_excerpt = f"{acceptance_label} | donor={donor_origin} | capabilities={', '.join(primary_capabilities[:3])}"
207
+ fixture_name = FIXTURE_BY_FAMILY.get(family)
208
+ matrix_excerpt = f"{fixture_name or 'ad-hoc'} | {acceptance_label} | family={family}"
209
+ parity_excerpt = ", ".join(f"{cap}=generate-ready" for cap in primary_capabilities[:3]) if primary_capabilities else "no parity excerpt"
210
+ decision_excerpt = f"decision={acceptance_label} | donor_origin={donor_origin}"
211
+ runtime_excerpt = "runtime=donor-backed example artifact"
212
+ return {
213
+ "name": name,
214
+ "title": TITLE_BY_FAMILY.get(family, name.replace("_", " ").title()),
215
+ "scenario_family": family,
216
+ "summary": SUMMARY_BY_FAMILY.get(family, f"Curated {family} example manifest."),
217
+ "capabilities": primary_capabilities,
218
+ "primary_capabilities": primary_capabilities,
219
+ "acceptance_rank": acceptance_rank,
220
+ "acceptance_label": acceptance_label,
221
+ "acceptance_excerpt": acceptance_excerpt,
222
+ "donor_origin": donor_origin,
223
+ "fixture_name": fixture_name,
224
+ "matrix_excerpt": matrix_excerpt,
225
+ "parity_excerpt": parity_excerpt,
226
+ "decision_excerpt": decision_excerpt,
227
+ "runtime_excerpt": runtime_excerpt,
228
+ "inventory_json": manifest_path.relative_to(ROOT).as_posix(),
229
+ "screenshots": screenshots,
230
+ "screenshot_count": len(screenshots),
231
+ "screenshot": screenshot,
232
+ "detail_images": detail_images,
233
+ "preview": preview,
234
+ "image": screenshot or preview,
235
+ }
236
+
237
+
238
+ def build_examples_index() -> dict[str, object]:
239
+ return {
240
+ "curated_examples": [
241
+ _build_entry(path)
242
+ for path in _manifest_files()
243
+ ]
244
+ }
245
+
246
+
247
+ def build_examples_gallery_markdown(payload: dict[str, object]) -> str:
248
+ lines = [
249
+ "## Known Working Scenario Set",
250
+ "",
251
+ "These examples are public, text-first proof artifacts derived from donor-backed workflows and aligned with the scenario fixture corpus.",
252
+ "",
253
+ "`0.2.0` canonical public set:",
254
+ "",
255
+ "- `campus`",
256
+ "- `home_iot`",
257
+ "- `service_heavy`",
258
+ "",
259
+ "Hero visual:",
260
+ "",
261
+ "- [complex campus screenshot](screenshots/complex_campus_master_edit_v4.png)",
262
+ "",
263
+ "| Title | Family | Capabilities | Image | Inventory |",
264
+ "| --- | --- | --- | --- | --- |",
265
+ ]
266
+ for entry in payload["curated_examples"]:
267
+ image_path = entry.get("image")
268
+ image_rel = Path(str(image_path)).relative_to("examples").as_posix()
269
+ image_link = "[screenshot]" if entry.get("screenshot") else "[preview]"
270
+ image = f"{image_link}({image_rel})"
271
+ inventory_rel = Path(str(entry["inventory_json"])).relative_to("examples").as_posix()
272
+ inventory = f"[manifest]({inventory_rel})"
273
+ capabilities = ", ".join(entry.get("capabilities") or [])
274
+ lines.append(
275
+ f"| {entry['title']} | `{entry['scenario_family']}` | {capabilities} | {image} | {inventory} |"
276
+ )
277
+ lines.append(f"| | | {entry['summary']} | | |")
278
+ lines.append(f"| | | `{entry['acceptance_excerpt']}` | | |")
279
+ lines.append(f"| | | `{entry['matrix_excerpt']}` | | |")
280
+ lines.append(f"| | | `{entry['parity_excerpt']}` | | |")
281
+ lines.append(f"| | | `{entry['decision_excerpt']}` | | |")
282
+ lines.append(f"| | | `{entry['runtime_excerpt']}` | | |")
283
+ detail_images = [
284
+ f"[detail {index}]({Path(path).relative_to('examples').as_posix()})"
285
+ for index, path in enumerate(entry.get("detail_images") or [], start=1)
286
+ ]
287
+ if detail_images:
288
+ lines.append(f"| | | extra visuals: {'; '.join(detail_images)} | | |")
289
+
290
+ missing = [entry for entry in payload["curated_examples"] if not entry.get("screenshots")]
291
+ if missing:
292
+ lines.extend(
293
+ [
294
+ "",
295
+ "### Pending Screenshots",
296
+ "",
297
+ ]
298
+ )
299
+ for entry in missing:
300
+ lines.append(f"- `{entry['name']}` currently uses generated preview fallback.")
301
+ lines.append("")
302
+ return "\n".join(lines)
303
+
304
+
305
+ def main() -> None:
306
+ payload = build_examples_index()
307
+ INDEX_PATH.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
308
+ GALLERY_PATH.write_text(build_examples_gallery_markdown(payload) + "\n", encoding="utf-8")
309
+ print(f"Wrote {INDEX_PATH}")
310
+ print(f"Wrote {GALLERY_PATH}")
311
+
312
+
313
+ if __name__ == "__main__":
314
+ main()