packet-tracer-skill 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/LICENSES/LICENSE.Twofish-BSD-3-Clause.txt +29 -0
- package/README.md +687 -0
- package/SKILL.md +221 -0
- package/bin/packet-tracer-skill.js +635 -0
- package/examples/blueprint_minimal.json +46 -0
- package/package.json +42 -0
- package/references/packettracer-sample-catalog.json +21410 -0
- package/references/packettracer-sample-catalog.md +1124 -0
- package/references/pkt-format.md +57 -0
- package/references/xml-skeleton-notes.md +44 -0
- package/requirements-dev.txt +1 -0
- package/requirements.txt +6 -0
- package/scripts/build_sample_catalog.py +65 -0
- package/scripts/donor_diagnostics.py +35 -0
- package/scripts/generate_pkt.py +1264 -0
- package/scripts/install_skill.py +71 -0
- package/scripts/intent_parser.py +712 -0
- package/scripts/packet_tracer_env.py +278 -0
- package/scripts/pkt_builder.py +15 -0
- package/scripts/pkt_codec.py +181 -0
- package/scripts/pkt_editor.py +752 -0
- package/scripts/pkt_transformer.py +541 -0
- package/scripts/sample_catalog.py +385 -0
- package/scripts/sample_selector.py +156 -0
- package/scripts/setup.ps1 +26 -0
- package/scripts/twofish_diagnostics.py +91 -0
- package/scripts/vendor/README.md +46 -0
- package/scripts/vendor/twofish.py +81 -0
- package/scripts/workspace_repair.py +441 -0
- package/templates/pt900/base_empty.xml +21 -0
- package/templates/pt900/device_library/pc.xml +20 -0
- package/templates/pt900/device_library/printer.xml +432 -0
- package/templates/pt900/device_library/router.xml +16 -0
- package/templates/pt900/device_library/switch.xml +38 -0
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const { spawnSync } = require("child_process");
|
|
7
|
+
|
|
8
|
+
const REPO_ROOT = path.resolve(__dirname, "..");
|
|
9
|
+
|
|
10
|
+
const HOST_PATHS = {
|
|
11
|
+
codex: path.join(os.homedir(), ".codex", "skills", "pkt"),
|
|
12
|
+
claude: path.join(os.homedir(), ".claude", "skills", "pkt"),
|
|
13
|
+
cursor: path.join(os.homedir(), ".cursor", "skills", "pkt"),
|
|
14
|
+
kiro: path.join(os.homedir(), ".kiro", "skills", "pkt"),
|
|
15
|
+
adal: path.join(os.homedir(), ".adal", "skills", "pkt"),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const IGNORE_NAMES = new Set([
|
|
19
|
+
".git",
|
|
20
|
+
".venv",
|
|
21
|
+
"venv",
|
|
22
|
+
"__pycache__",
|
|
23
|
+
".pytest_cache",
|
|
24
|
+
"output",
|
|
25
|
+
"node_modules",
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
const IGNORE_EXTS = new Set([".pyc", ".pyo", ".pkt", ".xml"]);
|
|
29
|
+
|
|
30
|
+
function printHelp() {
|
|
31
|
+
console.log(`packet-tracer-skill
|
|
32
|
+
|
|
33
|
+
Install or verify the pkt skill for a local host.
|
|
34
|
+
|
|
35
|
+
Usage:
|
|
36
|
+
packet-tracer-skill [--codex|--cursor|--claude|--kiro|--adal] [--force]
|
|
37
|
+
packet-tracer-skill --path <dir> [--direct] [--force]
|
|
38
|
+
packet-tracer-skill --verify [--codex|--cursor|--claude|--kiro|--adal]
|
|
39
|
+
packet-tracer-skill --verify --path <dir> [--direct]
|
|
40
|
+
packet-tracer-skill --bootstrap [--dev] [--codex|--cursor|--claude|--kiro|--adal]
|
|
41
|
+
packet-tracer-skill --bootstrap [--dev] --path <dir> [--direct]
|
|
42
|
+
packet-tracer-skill --doctor
|
|
43
|
+
|
|
44
|
+
Examples:
|
|
45
|
+
packet-tracer-skill
|
|
46
|
+
packet-tracer-skill --cursor
|
|
47
|
+
packet-tracer-skill --claude --force
|
|
48
|
+
packet-tracer-skill --path .agents/skills --force
|
|
49
|
+
packet-tracer-skill --verify --codex
|
|
50
|
+
packet-tracer-skill --verify --path .agents/skills
|
|
51
|
+
packet-tracer-skill --bootstrap --cursor
|
|
52
|
+
packet-tracer-skill --bootstrap --dev --path .agents/skills
|
|
53
|
+
packet-tracer-skill --doctor
|
|
54
|
+
`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function parseArgs(argv) {
|
|
58
|
+
const args = {
|
|
59
|
+
host: "codex",
|
|
60
|
+
force: false,
|
|
61
|
+
verify: false,
|
|
62
|
+
direct: false,
|
|
63
|
+
doctor: false,
|
|
64
|
+
bootstrap: false,
|
|
65
|
+
dev: false,
|
|
66
|
+
path: null,
|
|
67
|
+
help: false,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
71
|
+
const arg = argv[i];
|
|
72
|
+
if (arg === "--help" || arg === "-h") {
|
|
73
|
+
args.help = true;
|
|
74
|
+
} else if (arg === "--force") {
|
|
75
|
+
args.force = true;
|
|
76
|
+
} else if (arg === "--verify") {
|
|
77
|
+
args.verify = true;
|
|
78
|
+
} else if (arg === "--direct") {
|
|
79
|
+
args.direct = true;
|
|
80
|
+
} else if (arg === "--doctor") {
|
|
81
|
+
args.doctor = true;
|
|
82
|
+
} else if (arg === "--bootstrap") {
|
|
83
|
+
args.bootstrap = true;
|
|
84
|
+
} else if (arg === "--dev") {
|
|
85
|
+
args.dev = true;
|
|
86
|
+
} else if (arg === "--path") {
|
|
87
|
+
i += 1;
|
|
88
|
+
args.path = argv[i];
|
|
89
|
+
} else if (arg === "--codex") {
|
|
90
|
+
args.host = "codex";
|
|
91
|
+
} else if (arg === "--cursor") {
|
|
92
|
+
args.host = "cursor";
|
|
93
|
+
} else if (arg === "--claude") {
|
|
94
|
+
args.host = "claude";
|
|
95
|
+
} else if (arg === "--kiro") {
|
|
96
|
+
args.host = "kiro";
|
|
97
|
+
} else if (arg === "--adal") {
|
|
98
|
+
args.host = "adal";
|
|
99
|
+
} else {
|
|
100
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return args;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function resolveTarget(args) {
|
|
108
|
+
if (args.path) {
|
|
109
|
+
const base = path.resolve(args.path);
|
|
110
|
+
return args.direct ? base : path.join(base, "pkt");
|
|
111
|
+
}
|
|
112
|
+
return HOST_PATHS[args.host];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function shouldIgnore(name) {
|
|
116
|
+
if (IGNORE_NAMES.has(name)) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
return IGNORE_EXTS.has(path.extname(name).toLowerCase());
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function copyTree(src, dst) {
|
|
123
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
124
|
+
if (shouldIgnore(entry.name)) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const srcPath = path.join(src, entry.name);
|
|
128
|
+
const dstPath = path.join(dst, entry.name);
|
|
129
|
+
if (entry.isDirectory()) {
|
|
130
|
+
fs.mkdirSync(dstPath, { recursive: true });
|
|
131
|
+
copyTree(srcPath, dstPath);
|
|
132
|
+
} else {
|
|
133
|
+
fs.mkdirSync(path.dirname(dstPath), { recursive: true });
|
|
134
|
+
fs.copyFileSync(srcPath, dstPath);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function verifyInstall(target) {
|
|
140
|
+
const required = [
|
|
141
|
+
"SKILL.md",
|
|
142
|
+
path.join("scripts", "generate_pkt.py"),
|
|
143
|
+
path.join("scripts", "donor_diagnostics.py"),
|
|
144
|
+
path.join("scripts", "install_skill.py"),
|
|
145
|
+
path.join("scripts", "packet_tracer_env.py"),
|
|
146
|
+
path.join("scripts", "twofish_diagnostics.py"),
|
|
147
|
+
];
|
|
148
|
+
const missing = required.filter((rel) => !fs.existsSync(path.join(target, rel)));
|
|
149
|
+
return {
|
|
150
|
+
ok: missing.length === 0,
|
|
151
|
+
missing,
|
|
152
|
+
target,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function commandExists(command) {
|
|
157
|
+
const probe = process.platform === "win32" ? "where" : "which";
|
|
158
|
+
const result = spawnSync(probe, [command], { stdio: "ignore" });
|
|
159
|
+
return result.status === 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function envPathStatus(value) {
|
|
163
|
+
if (!value) {
|
|
164
|
+
return { ok: false, message: "not set" };
|
|
165
|
+
}
|
|
166
|
+
if (!fs.existsSync(value)) {
|
|
167
|
+
return { ok: false, message: `set but missing: ${value}` };
|
|
168
|
+
}
|
|
169
|
+
return { ok: true, message: value };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function packetTracerVersionDiagnostics() {
|
|
173
|
+
const pythonCommand = firstAvailableCommand(["python", "py"]);
|
|
174
|
+
if (!pythonCommand) {
|
|
175
|
+
return {
|
|
176
|
+
targetVersion: process.env.PACKET_TRACER_TARGET_VERSION || "9.0.0.0810",
|
|
177
|
+
donorPath: process.env.PACKET_TRACER_COMPAT_DONOR || "",
|
|
178
|
+
donorVersion: "",
|
|
179
|
+
donorSource: "",
|
|
180
|
+
status: "python_missing",
|
|
181
|
+
message: "python was not found in PATH",
|
|
182
|
+
blockingReason: "python was not found in PATH",
|
|
183
|
+
candidatePaths: [],
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const diagnosticsScript = path.join(REPO_ROOT, "scripts", "donor_diagnostics.py");
|
|
188
|
+
const args = pythonCommand === "py" ? ["-3", diagnosticsScript] : [diagnosticsScript];
|
|
189
|
+
const result = runCaptured(pythonCommand, args);
|
|
190
|
+
if (result.error) {
|
|
191
|
+
const donorPath = process.env.PACKET_TRACER_COMPAT_DONOR || "";
|
|
192
|
+
if (!donorPath) {
|
|
193
|
+
return {
|
|
194
|
+
targetVersion: process.env.PACKET_TRACER_TARGET_VERSION || "9.0.0.0810",
|
|
195
|
+
donorPath: "",
|
|
196
|
+
donorVersion: "",
|
|
197
|
+
donorSource: "",
|
|
198
|
+
status: "not_set",
|
|
199
|
+
message: "not set",
|
|
200
|
+
blockingReason: "not set",
|
|
201
|
+
candidatePaths: [],
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if (!fs.existsSync(donorPath)) {
|
|
205
|
+
return {
|
|
206
|
+
targetVersion: process.env.PACKET_TRACER_TARGET_VERSION || "9.0.0.0810",
|
|
207
|
+
donorPath,
|
|
208
|
+
donorVersion: "",
|
|
209
|
+
donorSource: "env",
|
|
210
|
+
status: "missing",
|
|
211
|
+
message: `set but missing: ${donorPath}`,
|
|
212
|
+
blockingReason: `set but missing: ${donorPath}`,
|
|
213
|
+
candidatePaths: [],
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
targetVersion: process.env.PACKET_TRACER_TARGET_VERSION || "9.0.0.0810",
|
|
218
|
+
donorPath,
|
|
219
|
+
donorVersion: "",
|
|
220
|
+
donorSource: "env",
|
|
221
|
+
status: "inspection_blocked",
|
|
222
|
+
message: `set, but donor version inspection is blocked in this host wrapper: ${result.error.message}`,
|
|
223
|
+
blockingReason: `set, but donor version inspection is blocked in this host wrapper: ${result.error.message}`,
|
|
224
|
+
candidatePaths: [],
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
if (result.status !== 0) {
|
|
228
|
+
const detail =
|
|
229
|
+
(result.stderr || result.stdout || "").trim().split(/\r?\n/).filter(Boolean).pop() ||
|
|
230
|
+
`exit code ${result.status}`;
|
|
231
|
+
return {
|
|
232
|
+
targetVersion: process.env.PACKET_TRACER_TARGET_VERSION || "9.0.0.0810",
|
|
233
|
+
donorPath: process.env.PACKET_TRACER_COMPAT_DONOR || "",
|
|
234
|
+
donorVersion: "",
|
|
235
|
+
donorSource: process.env.PACKET_TRACER_COMPAT_DONOR ? "env" : "",
|
|
236
|
+
status: "decode_error",
|
|
237
|
+
message: detail,
|
|
238
|
+
blockingReason: detail,
|
|
239
|
+
candidatePaths: [],
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
const parsed = JSON.parse((result.stdout || "").trim() || "{}");
|
|
245
|
+
return {
|
|
246
|
+
targetVersion: parsed.target_version || process.env.PACKET_TRACER_TARGET_VERSION || "9.0.0.0810",
|
|
247
|
+
donorPath: parsed.resolved_donor_path || parsed.donor_path || process.env.PACKET_TRACER_COMPAT_DONOR || "",
|
|
248
|
+
donorVersion: parsed.donor_version || "",
|
|
249
|
+
donorSource: parsed.donor_source || "",
|
|
250
|
+
status: parsed.status || "decode_error",
|
|
251
|
+
message: parsed.message || "unknown error",
|
|
252
|
+
blockingReason: parsed.blocking_reason || parsed.message || "unknown error",
|
|
253
|
+
candidatePaths: parsed.candidate_paths || [],
|
|
254
|
+
};
|
|
255
|
+
} catch (error) {
|
|
256
|
+
return {
|
|
257
|
+
targetVersion: process.env.PACKET_TRACER_TARGET_VERSION || "9.0.0.0810",
|
|
258
|
+
donorPath: process.env.PACKET_TRACER_COMPAT_DONOR || "",
|
|
259
|
+
donorVersion: "",
|
|
260
|
+
donorSource: process.env.PACKET_TRACER_COMPAT_DONOR ? "env" : "",
|
|
261
|
+
status: "decode_error",
|
|
262
|
+
message: error.message,
|
|
263
|
+
blockingReason: error.message,
|
|
264
|
+
candidatePaths: [],
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function twofishDiagnostics() {
|
|
270
|
+
const pythonCommand = firstAvailableCommand(["python", "py"]);
|
|
271
|
+
if (!pythonCommand) {
|
|
272
|
+
return {
|
|
273
|
+
pythonVersion: "",
|
|
274
|
+
pythonSupportStatus: "missing",
|
|
275
|
+
pythonSupportMessage: "python was not found in PATH",
|
|
276
|
+
resolvedTwofishPath: "",
|
|
277
|
+
twofishSource: "",
|
|
278
|
+
twofishLoadStatus: "python_missing",
|
|
279
|
+
twofishMessage: "python was not found in PATH",
|
|
280
|
+
twofishSha256: "",
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const diagnosticsScript = path.join(REPO_ROOT, "scripts", "twofish_diagnostics.py");
|
|
285
|
+
const args = pythonCommand === "py" ? ["-3", diagnosticsScript] : [diagnosticsScript];
|
|
286
|
+
const result = runCaptured(pythonCommand, args);
|
|
287
|
+
if (result.error) {
|
|
288
|
+
const envPath = process.env.PKT_TWOFISH_LIBRARY || "";
|
|
289
|
+
const envExists = envPath !== "" && fs.existsSync(envPath);
|
|
290
|
+
return {
|
|
291
|
+
pythonVersion: "",
|
|
292
|
+
pythonSupportStatus: "unknown",
|
|
293
|
+
pythonSupportMessage: `inspection blocked in this host wrapper: ${result.error.message}`,
|
|
294
|
+
resolvedTwofishPath: envPath,
|
|
295
|
+
twofishSource: envPath ? "env" : "",
|
|
296
|
+
twofishLoadStatus: envExists ? "inspection_blocked" : "missing",
|
|
297
|
+
twofishMessage: envExists
|
|
298
|
+
? `set, but load inspection is blocked in this host wrapper: ${result.error.message}`
|
|
299
|
+
: envPath
|
|
300
|
+
? `set but missing: ${envPath}`
|
|
301
|
+
: "not set and sibling inspection is blocked in this host wrapper",
|
|
302
|
+
twofishSha256: "",
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
if (result.status !== 0) {
|
|
306
|
+
const detail =
|
|
307
|
+
(result.stderr || result.stdout || "").trim().split(/\r?\n/).filter(Boolean).pop() ||
|
|
308
|
+
`exit code ${result.status}`;
|
|
309
|
+
return {
|
|
310
|
+
pythonVersion: "",
|
|
311
|
+
pythonSupportStatus: "unknown",
|
|
312
|
+
pythonSupportMessage: detail,
|
|
313
|
+
resolvedTwofishPath: process.env.PKT_TWOFISH_LIBRARY || "",
|
|
314
|
+
twofishSource: process.env.PKT_TWOFISH_LIBRARY ? "env" : "",
|
|
315
|
+
twofishLoadStatus: "load_error",
|
|
316
|
+
twofishMessage: detail,
|
|
317
|
+
twofishSha256: "",
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
const parsed = JSON.parse((result.stdout || "").trim() || "{}");
|
|
323
|
+
return {
|
|
324
|
+
pythonVersion: parsed.python_version || "",
|
|
325
|
+
pythonSupportStatus: parsed.python_support_status || "unknown",
|
|
326
|
+
pythonSupportMessage: parsed.python_support_message || "unknown",
|
|
327
|
+
resolvedTwofishPath: parsed.resolved_twofish_path || "",
|
|
328
|
+
twofishSource: parsed.twofish_source || "",
|
|
329
|
+
twofishLoadStatus: parsed.twofish_load_status || "unknown",
|
|
330
|
+
twofishMessage: parsed.twofish_message || "unknown",
|
|
331
|
+
twofishSha256: parsed.twofish_sha256 || "",
|
|
332
|
+
};
|
|
333
|
+
} catch (error) {
|
|
334
|
+
return {
|
|
335
|
+
pythonVersion: "",
|
|
336
|
+
pythonSupportStatus: "unknown",
|
|
337
|
+
pythonSupportMessage: error.message,
|
|
338
|
+
resolvedTwofishPath: process.env.PKT_TWOFISH_LIBRARY || "",
|
|
339
|
+
twofishSource: process.env.PKT_TWOFISH_LIBRARY ? "env" : "",
|
|
340
|
+
twofishLoadStatus: "load_error",
|
|
341
|
+
twofishMessage: error.message,
|
|
342
|
+
twofishSha256: "",
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function requirementLines(requirementsPath) {
|
|
348
|
+
if (!fs.existsSync(requirementsPath)) {
|
|
349
|
+
return [];
|
|
350
|
+
}
|
|
351
|
+
return fs
|
|
352
|
+
.readFileSync(requirementsPath, "utf8")
|
|
353
|
+
.split(/\r?\n/)
|
|
354
|
+
.map((line) => line.trim())
|
|
355
|
+
.filter((line) => line && !line.startsWith("#"));
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function runOrThrow(command, args, options = {}) {
|
|
359
|
+
const result = spawnSync(command, args, {
|
|
360
|
+
stdio: "inherit",
|
|
361
|
+
shell: false,
|
|
362
|
+
...options,
|
|
363
|
+
});
|
|
364
|
+
if (result.status !== 0) {
|
|
365
|
+
throw new Error(`${command} ${args.join(" ")} failed with exit code ${result.status}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function runCaptured(command, args, options = {}) {
|
|
370
|
+
return spawnSync(command, args, {
|
|
371
|
+
stdio: "pipe",
|
|
372
|
+
encoding: "utf8",
|
|
373
|
+
shell: false,
|
|
374
|
+
...options,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function firstAvailableCommand(candidates) {
|
|
379
|
+
for (const candidate of candidates) {
|
|
380
|
+
if (commandExists(candidate)) {
|
|
381
|
+
return candidate;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function bootstrapEnvironment(target, includeDev) {
|
|
388
|
+
const pythonCommand = firstAvailableCommand(["python", "py"]);
|
|
389
|
+
if (!pythonCommand) {
|
|
390
|
+
return {
|
|
391
|
+
ok: false,
|
|
392
|
+
message: "python was not found in PATH",
|
|
393
|
+
venvDir: path.join(target, ".venv"),
|
|
394
|
+
runtimeRequirements: [],
|
|
395
|
+
devRequirements: [],
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const venvDir = path.join(target, ".venv");
|
|
400
|
+
const pythonExe =
|
|
401
|
+
process.platform === "win32"
|
|
402
|
+
? path.join(venvDir, "Scripts", "python.exe")
|
|
403
|
+
: path.join(venvDir, "bin", "python");
|
|
404
|
+
|
|
405
|
+
const runtimeRequirements = requirementLines(path.join(target, "requirements.txt"));
|
|
406
|
+
const devRequirements = requirementLines(path.join(target, "requirements-dev.txt"));
|
|
407
|
+
try {
|
|
408
|
+
const venvArgs = pythonCommand === "py" ? ["-3", "-m", "venv", venvDir] : ["-m", "venv", venvDir];
|
|
409
|
+
const venvResult = runCaptured(pythonCommand, venvArgs);
|
|
410
|
+
if (venvResult.status !== 0) {
|
|
411
|
+
const detail = (venvResult.stderr || venvResult.stdout || "").trim().split(/\r?\n/).filter(Boolean).pop() || `exit code ${venvResult.status}`;
|
|
412
|
+
throw new Error(`${pythonCommand} ${venvArgs.join(" ")} failed: ${detail}`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (runtimeRequirements.length > 0) {
|
|
416
|
+
const runtimeResult = runCaptured(pythonExe, ["-m", "pip", "install", "-r", path.join(target, "requirements.txt")]);
|
|
417
|
+
if (runtimeResult.status !== 0) {
|
|
418
|
+
const detail = (runtimeResult.stderr || runtimeResult.stdout || "").trim().split(/\r?\n/).filter(Boolean).pop() || `exit code ${runtimeResult.status}`;
|
|
419
|
+
throw new Error(`${pythonExe} -m pip install -r requirements.txt failed: ${detail}`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (includeDev && devRequirements.length > 0) {
|
|
424
|
+
const devResult = runCaptured(pythonExe, ["-m", "pip", "install", "-r", path.join(target, "requirements-dev.txt")]);
|
|
425
|
+
if (devResult.status !== 0) {
|
|
426
|
+
const detail = (devResult.stderr || devResult.stdout || "").trim().split(/\r?\n/).filter(Boolean).pop() || `exit code ${devResult.status}`;
|
|
427
|
+
throw new Error(`${pythonExe} -m pip install -r requirements-dev.txt failed: ${detail}`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
} catch (error) {
|
|
431
|
+
return {
|
|
432
|
+
ok: false,
|
|
433
|
+
message: error.message,
|
|
434
|
+
venvDir,
|
|
435
|
+
runtimeRequirements,
|
|
436
|
+
devRequirements,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
ok: true,
|
|
442
|
+
message: "venv created",
|
|
443
|
+
venvDir,
|
|
444
|
+
runtimeRequirements,
|
|
445
|
+
devRequirements,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function doctorChecks() {
|
|
450
|
+
const nodeOk = commandExists("node");
|
|
451
|
+
const pythonOk = commandExists("python");
|
|
452
|
+
const checks = [
|
|
453
|
+
["node", nodeOk, nodeOk ? "found" : "missing"],
|
|
454
|
+
["python", pythonOk, pythonOk ? "found" : "missing"],
|
|
455
|
+
];
|
|
456
|
+
|
|
457
|
+
const root = envPathStatus(process.env.PACKET_TRACER_ROOT);
|
|
458
|
+
const donorDiagnostics = packetTracerVersionDiagnostics();
|
|
459
|
+
const twofish = twofishDiagnostics();
|
|
460
|
+
|
|
461
|
+
const strictTargetVersion = "9.0.0.0810";
|
|
462
|
+
checks.push(["PACKET_TRACER_ROOT", root.ok, root.message]);
|
|
463
|
+
checks.push([
|
|
464
|
+
"PYTHON_VERSION",
|
|
465
|
+
twofish.pythonVersion !== "",
|
|
466
|
+
twofish.pythonVersion || "unknown",
|
|
467
|
+
]);
|
|
468
|
+
checks.push([
|
|
469
|
+
"PYTHON_SUPPORT_STATUS",
|
|
470
|
+
twofish.pythonSupportStatus === "ok",
|
|
471
|
+
twofish.pythonSupportStatus === "ok"
|
|
472
|
+
? `supported (${twofish.pythonSupportMessage})`
|
|
473
|
+
: `${twofish.pythonSupportStatus} (${twofish.pythonSupportMessage})`,
|
|
474
|
+
]);
|
|
475
|
+
checks.push([
|
|
476
|
+
"PACKET_TRACER_TARGET_VERSION",
|
|
477
|
+
donorDiagnostics.targetVersion === strictTargetVersion,
|
|
478
|
+
donorDiagnostics.targetVersion === strictTargetVersion
|
|
479
|
+
? donorDiagnostics.targetVersion
|
|
480
|
+
: `${donorDiagnostics.targetVersion} (expected ${strictTargetVersion})`,
|
|
481
|
+
]);
|
|
482
|
+
checks.push([
|
|
483
|
+
"PACKET_TRACER_COMPAT_DONOR",
|
|
484
|
+
donorDiagnostics.status === "ok",
|
|
485
|
+
donorDiagnostics.message,
|
|
486
|
+
]);
|
|
487
|
+
checks.push([
|
|
488
|
+
"PACKET_TRACER_COMPAT_DONOR_VERSION",
|
|
489
|
+
donorDiagnostics.status === "ok",
|
|
490
|
+
donorDiagnostics.donorVersion || donorDiagnostics.status,
|
|
491
|
+
]);
|
|
492
|
+
checks.push([
|
|
493
|
+
"RESOLVED_DONOR_PATH",
|
|
494
|
+
donorDiagnostics.donorPath !== "",
|
|
495
|
+
donorDiagnostics.donorPath || "not resolved",
|
|
496
|
+
]);
|
|
497
|
+
checks.push([
|
|
498
|
+
"DONOR_SOURCE",
|
|
499
|
+
donorDiagnostics.status === "ok",
|
|
500
|
+
donorDiagnostics.donorSource || "not resolved",
|
|
501
|
+
]);
|
|
502
|
+
checks.push([
|
|
503
|
+
"BLOCKING_REASON",
|
|
504
|
+
donorDiagnostics.status === "ok",
|
|
505
|
+
donorDiagnostics.status === "ok" ? "none" : donorDiagnostics.blockingReason || donorDiagnostics.message,
|
|
506
|
+
]);
|
|
507
|
+
const candidateSummary = (donorDiagnostics.candidatePaths || [])
|
|
508
|
+
.slice(0, 5)
|
|
509
|
+
.map((entry) => `${entry.source}: ${entry.path}`)
|
|
510
|
+
.join(" | ");
|
|
511
|
+
checks.push([
|
|
512
|
+
"DONOR_CANDIDATES",
|
|
513
|
+
(donorDiagnostics.candidatePaths || []).length > 0,
|
|
514
|
+
candidateSummary || "none discovered",
|
|
515
|
+
]);
|
|
516
|
+
checks.push([
|
|
517
|
+
"RESOLVED_TWOFISH_PATH",
|
|
518
|
+
twofish.resolvedTwofishPath !== "",
|
|
519
|
+
twofish.resolvedTwofishPath || "not found",
|
|
520
|
+
]);
|
|
521
|
+
checks.push([
|
|
522
|
+
"TWOFISH_LOAD_STATUS",
|
|
523
|
+
twofish.twofishLoadStatus === "ok",
|
|
524
|
+
`${twofish.twofishLoadStatus} (${twofish.twofishMessage})`,
|
|
525
|
+
]);
|
|
526
|
+
checks.push([
|
|
527
|
+
"TWOFISH_SHA256",
|
|
528
|
+
twofish.twofishSha256 !== "",
|
|
529
|
+
twofish.twofishSha256 || "not available",
|
|
530
|
+
]);
|
|
531
|
+
|
|
532
|
+
return checks;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function printDoctorChecks(checks) {
|
|
536
|
+
for (const [name, ok, detail] of checks) {
|
|
537
|
+
console.log(`${ok ? "OK" : "MISSING"} ${name} ${detail}`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function main() {
|
|
542
|
+
try {
|
|
543
|
+
const args = parseArgs(process.argv.slice(2));
|
|
544
|
+
if (args.help) {
|
|
545
|
+
printHelp();
|
|
546
|
+
process.exit(0);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (args.doctor) {
|
|
550
|
+
const checks = doctorChecks();
|
|
551
|
+
printDoctorChecks(checks);
|
|
552
|
+
const failed = checks.some(([, ok]) => !ok);
|
|
553
|
+
if (failed) {
|
|
554
|
+
console.error("\nRuntime is not fully ready. Install copies are fine, but Packet Tracer generation still needs the missing items above.");
|
|
555
|
+
process.exit(1);
|
|
556
|
+
}
|
|
557
|
+
console.log("\nRuntime looks ready.");
|
|
558
|
+
process.exit(0);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const target = resolveTarget(args);
|
|
562
|
+
|
|
563
|
+
if (args.verify) {
|
|
564
|
+
const result = verifyInstall(target);
|
|
565
|
+
if (!result.ok) {
|
|
566
|
+
console.error(`Install verification failed: ${result.target}`);
|
|
567
|
+
for (const missing of result.missing) {
|
|
568
|
+
console.error(` missing: ${missing}`);
|
|
569
|
+
}
|
|
570
|
+
process.exit(1);
|
|
571
|
+
}
|
|
572
|
+
console.log(`Install verified: ${result.target}`);
|
|
573
|
+
process.exit(0);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (fs.existsSync(target)) {
|
|
577
|
+
if (!args.force) {
|
|
578
|
+
throw new Error(`Target already exists: ${target}. Use --force to replace it.`);
|
|
579
|
+
}
|
|
580
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
fs.mkdirSync(target, { recursive: true });
|
|
584
|
+
copyTree(REPO_ROOT, target);
|
|
585
|
+
|
|
586
|
+
const result = verifyInstall(target);
|
|
587
|
+
if (!result.ok) {
|
|
588
|
+
throw new Error(`Install completed but verification failed: ${result.missing.join(", ")}`);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (args.bootstrap) {
|
|
592
|
+
const bootstrapResult = bootstrapEnvironment(target, args.dev);
|
|
593
|
+
console.log(`Installed: ${target}`);
|
|
594
|
+
|
|
595
|
+
if (bootstrapResult.ok) {
|
|
596
|
+
console.log(`Bootstrap venv: ${bootstrapResult.venvDir}`);
|
|
597
|
+
if (bootstrapResult.runtimeRequirements.length === 0) {
|
|
598
|
+
console.log("Runtime requirements: none declared");
|
|
599
|
+
}
|
|
600
|
+
if (args.dev) {
|
|
601
|
+
if (bootstrapResult.devRequirements.length === 0) {
|
|
602
|
+
console.log("Dev requirements: none declared");
|
|
603
|
+
} else {
|
|
604
|
+
console.log(`Dev requirements installed: ${bootstrapResult.devRequirements.length}`);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
} else {
|
|
608
|
+
console.log(`Bootstrap venv: not completed`);
|
|
609
|
+
console.log(`Bootstrap note: ${bootstrapResult.message}`);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const checks = doctorChecks();
|
|
613
|
+
console.log("\nRemaining manual runtime checks:");
|
|
614
|
+
printDoctorChecks(checks);
|
|
615
|
+
const missing = checks.filter(([, ok]) => !ok).map(([name]) => name);
|
|
616
|
+
const donorCandidates = checks.find(([name]) => name === "DONOR_CANDIDATES");
|
|
617
|
+
if (donorCandidates && donorCandidates[2] && donorCandidates[2] !== "none discovered") {
|
|
618
|
+
console.log(`\nDiscovered donor candidates: ${donorCandidates[2]}`);
|
|
619
|
+
}
|
|
620
|
+
if (missing.length > 0) {
|
|
621
|
+
console.log("\nBootstrap finished. Packet Tracer itself and any missing runtime paths still need to be provided manually.");
|
|
622
|
+
} else {
|
|
623
|
+
console.log("\nBootstrap finished. Runtime looks ready.");
|
|
624
|
+
}
|
|
625
|
+
process.exit(0);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
console.log(`Installed: ${target}`);
|
|
629
|
+
} catch (error) {
|
|
630
|
+
console.error(error.message);
|
|
631
|
+
process.exit(1);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
main();
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"packet_tracer_version": "9.0.0.0810",
|
|
3
|
+
"name": "Minimal LAN",
|
|
4
|
+
"devices": [
|
|
5
|
+
{
|
|
6
|
+
"name": "S1",
|
|
7
|
+
"type": "Switch",
|
|
8
|
+
"model": "2960-24TT",
|
|
9
|
+
"x": 400,
|
|
10
|
+
"y": 260
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "PC1",
|
|
14
|
+
"type": "PC",
|
|
15
|
+
"model": "PC-PT",
|
|
16
|
+
"x": 250,
|
|
17
|
+
"y": 420
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"links": [
|
|
21
|
+
{
|
|
22
|
+
"a": {
|
|
23
|
+
"dev": "PC1",
|
|
24
|
+
"port": "FastEthernet0"
|
|
25
|
+
},
|
|
26
|
+
"b": {
|
|
27
|
+
"dev": "S1",
|
|
28
|
+
"port": "FastEthernet0/1"
|
|
29
|
+
},
|
|
30
|
+
"media": "copper"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"configs": {
|
|
34
|
+
"PC1": {
|
|
35
|
+
"ip": "192.168.1.10",
|
|
36
|
+
"mask": "255.255.255.0",
|
|
37
|
+
"gw": "192.168.1.1",
|
|
38
|
+
"dns": "8.8.8.8"
|
|
39
|
+
},
|
|
40
|
+
"S1": [
|
|
41
|
+
"hostname S1",
|
|
42
|
+
"interface FastEthernet0/1",
|
|
43
|
+
"switchport mode access"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
}
|