icoa-cli 2.19.80 → 2.19.82
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/dist/commands/env.js +117 -0
- package/dist/repl.js +50 -1
- package/package.json +1 -1
package/dist/commands/env.js
CHANGED
|
@@ -272,8 +272,125 @@ export function registerEnvCommand(program) {
|
|
|
272
272
|
const envCmd = program.command('env').description('Manage competition environment');
|
|
273
273
|
envCmd.command('status').alias('check').description('Check all 109 tools').action(() => showStatus());
|
|
274
274
|
envCmd.command('setup').description('Install all Python libraries + system tools').action(async () => { await installAll(); });
|
|
275
|
+
envCmd.command('python').description('Show Python 3.12 install guide for your platform').action(() => showPythonInstallGuide());
|
|
275
276
|
envCmd.action(() => showStatus());
|
|
276
277
|
}
|
|
278
|
+
// Lightweight Python 3.12 install guide for Selection mode (no 109-tool check).
|
|
279
|
+
// Detects distro on Linux so contestants get the right command the first time.
|
|
280
|
+
function showPythonInstallGuide() {
|
|
281
|
+
const os = platform();
|
|
282
|
+
console.log();
|
|
283
|
+
console.log(chalk.bold.white(' Python 3.12 — Installation Guide'));
|
|
284
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
285
|
+
console.log();
|
|
286
|
+
let currentPy = '';
|
|
287
|
+
let pyStatus = 'missing';
|
|
288
|
+
// Prefer the versioned binary if available. On macOS with Homebrew, `python3`
|
|
289
|
+
// often points to the latest (e.g. 3.13) while python@3.12 is also installed
|
|
290
|
+
// at /opt/homebrew/opt/python@3.12/bin/python3.12. Don't force the user to
|
|
291
|
+
// reinstall just because the default changed.
|
|
292
|
+
const pyProbes = [
|
|
293
|
+
'python3.12 --version',
|
|
294
|
+
'/opt/homebrew/opt/python@3.12/bin/python3.12 --version',
|
|
295
|
+
'/usr/local/opt/python@3.12/bin/python3.12 --version',
|
|
296
|
+
'python3 --version',
|
|
297
|
+
];
|
|
298
|
+
for (const cmd of pyProbes) {
|
|
299
|
+
try {
|
|
300
|
+
const out = execSync(cmd, { encoding: 'utf-8', timeout: 3000 }).trim();
|
|
301
|
+
const ver = out.replace('Python ', '');
|
|
302
|
+
const parts = ver.split('.').map(Number);
|
|
303
|
+
// Only accept as "ok" if this probe returned 3.12. Otherwise keep trying.
|
|
304
|
+
if (parts[0] === 3 && parts[1] === 12) {
|
|
305
|
+
currentPy = ver;
|
|
306
|
+
pyStatus = 'ok';
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
// Remember the last-seen version for fallback reporting
|
|
310
|
+
currentPy = ver;
|
|
311
|
+
if (parts[0] === 3 && parts[1] >= 10 && parts[1] < 12)
|
|
312
|
+
pyStatus = 'old';
|
|
313
|
+
else if (parts[0] === 3 && parts[1] > 12)
|
|
314
|
+
pyStatus = 'new';
|
|
315
|
+
else
|
|
316
|
+
pyStatus = 'missing';
|
|
317
|
+
}
|
|
318
|
+
catch { /* try next probe */ }
|
|
319
|
+
}
|
|
320
|
+
if (pyStatus === 'ok') {
|
|
321
|
+
console.log(chalk.green(` ✓ Python ${currentPy} — you're good!`));
|
|
322
|
+
console.log(chalk.gray(' Exam toolkit ready. Next: ') + chalk.bold.cyan('exam setup'));
|
|
323
|
+
console.log();
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (pyStatus === 'old') {
|
|
327
|
+
console.log(chalk.yellow(` ⚠ Python ${currentPy} — works, but 3.12 recommended`));
|
|
328
|
+
}
|
|
329
|
+
else if (pyStatus === 'new') {
|
|
330
|
+
console.log(chalk.yellow(` ⚠ Python ${currentPy} — some packages (pwntools, scapy) lack wheels`));
|
|
331
|
+
console.log(chalk.gray(' Downgrading to 3.12 is strongly recommended'));
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
console.log(chalk.red(' ✗ Python 3 not found'));
|
|
335
|
+
}
|
|
336
|
+
console.log();
|
|
337
|
+
if (os === 'darwin') {
|
|
338
|
+
console.log(chalk.bold.white(' macOS (Homebrew)'));
|
|
339
|
+
console.log();
|
|
340
|
+
console.log(chalk.green(' brew install python@3.12'));
|
|
341
|
+
}
|
|
342
|
+
else if (os === 'linux') {
|
|
343
|
+
let distro = 'ubuntu';
|
|
344
|
+
try {
|
|
345
|
+
const release = execSync('cat /etc/os-release 2>/dev/null', { encoding: 'utf-8', timeout: 2000 });
|
|
346
|
+
if (/fedora|rhel|centos/i.test(release))
|
|
347
|
+
distro = 'fedora';
|
|
348
|
+
else if (/arch|manjaro/i.test(release))
|
|
349
|
+
distro = 'arch';
|
|
350
|
+
else
|
|
351
|
+
distro = 'ubuntu';
|
|
352
|
+
}
|
|
353
|
+
catch { }
|
|
354
|
+
if (distro === 'ubuntu') {
|
|
355
|
+
console.log(chalk.bold.white(' Ubuntu / Debian / Kali (deadsnakes PPA)'));
|
|
356
|
+
console.log();
|
|
357
|
+
console.log(chalk.gray(' Copy-paste this one-liner:'));
|
|
358
|
+
console.log();
|
|
359
|
+
console.log(chalk.green(' sudo add-apt-repository ppa:deadsnakes/ppa -y && \\'));
|
|
360
|
+
console.log(chalk.green(' sudo apt update && \\'));
|
|
361
|
+
console.log(chalk.green(' sudo apt install -y python3.12 python3.12-venv python3.12-distutils && \\'));
|
|
362
|
+
console.log(chalk.green(' sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1'));
|
|
363
|
+
}
|
|
364
|
+
else if (distro === 'fedora') {
|
|
365
|
+
console.log(chalk.bold.white(' Fedora / RHEL / CentOS'));
|
|
366
|
+
console.log();
|
|
367
|
+
console.log(chalk.green(' sudo dnf install -y python3.12 python3.12-pip'));
|
|
368
|
+
}
|
|
369
|
+
else if (distro === 'arch') {
|
|
370
|
+
console.log(chalk.bold.white(' Arch / Manjaro (via pyenv)'));
|
|
371
|
+
console.log();
|
|
372
|
+
console.log(chalk.green(' sudo pacman -S pyenv && pyenv install 3.12 && pyenv global 3.12'));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
else if (os === 'win32') {
|
|
376
|
+
console.log(chalk.bold.white(' Windows (WSL recommended)'));
|
|
377
|
+
console.log();
|
|
378
|
+
console.log(chalk.gray(' WSL + Ubuntu (best):'));
|
|
379
|
+
console.log(chalk.green(' wsl --install'));
|
|
380
|
+
console.log(chalk.gray(' Then inside Ubuntu, run the Ubuntu commands.'));
|
|
381
|
+
console.log();
|
|
382
|
+
console.log(chalk.gray(' Or native:'));
|
|
383
|
+
console.log(chalk.green(' winget install Python.Python.3.12'));
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
console.log(chalk.gray(' https://www.python.org/downloads/'));
|
|
387
|
+
}
|
|
388
|
+
console.log();
|
|
389
|
+
console.log(chalk.white(' After installing, verify:'));
|
|
390
|
+
console.log(chalk.bold.cyan(' env python') + chalk.gray(' # re-check Python'));
|
|
391
|
+
console.log(chalk.bold.cyan(' exam setup') + chalk.gray(' # install the 13 exam packages'));
|
|
392
|
+
console.log();
|
|
393
|
+
}
|
|
277
394
|
function showStatus() {
|
|
278
395
|
console.log();
|
|
279
396
|
const os = platform();
|
package/dist/repl.js
CHANGED
|
@@ -100,6 +100,39 @@ const VERSION = '2.5.1';
|
|
|
100
100
|
// State 0 (no demo yet): recommend demo, hide exam setup / exam <token>
|
|
101
101
|
// State 1 (demo done, no setup): show demo completion + setup CTA, hide token
|
|
102
102
|
// State 2 (demo done + setup done): show token entry CTA
|
|
103
|
+
// Quick Python version check (used in Selection menu startup warning).
|
|
104
|
+
// Returns {ok, version, status}. Silent/defensive — any error means 'missing'.
|
|
105
|
+
function checkPython() {
|
|
106
|
+
// Probe python3.12 first — macOS Homebrew installs python@3.12 alongside a
|
|
107
|
+
// newer default python3, and we don't want to flag a 3.12-ready machine just
|
|
108
|
+
// because the default alias moved to 3.13.
|
|
109
|
+
const probes = [
|
|
110
|
+
'python3.12 --version',
|
|
111
|
+
'/opt/homebrew/opt/python@3.12/bin/python3.12 --version',
|
|
112
|
+
'/usr/local/opt/python@3.12/bin/python3.12 --version',
|
|
113
|
+
'python3 --version',
|
|
114
|
+
];
|
|
115
|
+
let lastVersion = '';
|
|
116
|
+
let lastStatus = 'missing';
|
|
117
|
+
for (const cmd of probes) {
|
|
118
|
+
try {
|
|
119
|
+
const out = execSyncFn(cmd, { encoding: 'utf-8', timeout: 2000 }).trim();
|
|
120
|
+
const version = out.replace('Python ', '');
|
|
121
|
+
const [maj, min] = version.split('.').map(Number);
|
|
122
|
+
if (maj === 3 && min === 12)
|
|
123
|
+
return { ok: true, version, status: 'ok' };
|
|
124
|
+
lastVersion = version;
|
|
125
|
+
if (maj === 3 && min >= 10 && min < 12)
|
|
126
|
+
lastStatus = 'old';
|
|
127
|
+
else if (maj === 3 && min > 12)
|
|
128
|
+
lastStatus = 'new';
|
|
129
|
+
else
|
|
130
|
+
lastStatus = 'missing';
|
|
131
|
+
}
|
|
132
|
+
catch { /* try next probe */ }
|
|
133
|
+
}
|
|
134
|
+
return { ok: lastStatus !== 'missing', version: lastVersion, status: lastStatus };
|
|
135
|
+
}
|
|
103
136
|
function printSelectionMenu() {
|
|
104
137
|
const stats = getDemoStats();
|
|
105
138
|
const setupDone = isExamSetupComplete();
|
|
@@ -107,6 +140,22 @@ function printSelectionMenu() {
|
|
|
107
140
|
console.log();
|
|
108
141
|
console.log(' ' + chalk.cyan.bold('[Selection Mode]'));
|
|
109
142
|
console.log();
|
|
143
|
+
// Proactive Python check: only warn when it actually matters (user past demo
|
|
144
|
+
// and heading toward exam setup). Missing or version >= 3.13 shows yellow
|
|
145
|
+
// note pointing at `env python`. Quiet when 3.10-3.12 are installed.
|
|
146
|
+
if (stats.attempts > 0) {
|
|
147
|
+
const py = checkPython();
|
|
148
|
+
if (py.status === 'missing') {
|
|
149
|
+
console.log(chalk.yellow(' ⚠ Python not detected. For exam practical questions:'));
|
|
150
|
+
console.log(chalk.gray(' → ') + chalk.bold.cyan('env python') + chalk.gray(' (platform install guide)'));
|
|
151
|
+
console.log();
|
|
152
|
+
}
|
|
153
|
+
else if (py.status === 'new') {
|
|
154
|
+
console.log(chalk.yellow(` ⚠ Python ${py.version} may lack CTF wheels. Python 3.12 recommended:`));
|
|
155
|
+
console.log(chalk.gray(' → ') + chalk.bold.cyan('env python') + chalk.gray(' (install guide)'));
|
|
156
|
+
console.log();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
110
159
|
if (stats.attempts === 0) {
|
|
111
160
|
// State 0: brand new user. Only demo matters right now.
|
|
112
161
|
console.log(chalk.white(' New here? Start with ') + chalk.bold.cyan('demo') + chalk.white(' — it takes a few minutes.'));
|
|
@@ -751,7 +800,7 @@ export async function startRepl(program, resumeMode) {
|
|
|
751
800
|
}
|
|
752
801
|
const cmd = input.split(/\s+/)[0].toLowerCase();
|
|
753
802
|
// ─── Mode-based command filtering ───
|
|
754
|
-
const selectionCommands = ['exam', 'demo', 'retry', 'nations', 'next', 'prev', 'continue', 'setup', 'lang', 'ref', 'ai4ctf', 'ctf4ai', 'mark', 'unmark', 'review', 'submit'];
|
|
803
|
+
const selectionCommands = ['exam', 'demo', 'retry', 'nations', 'next', 'prev', 'continue', 'setup', 'lang', 'ref', 'ai4ctf', 'ctf4ai', 'mark', 'unmark', 'review', 'submit', 'env'];
|
|
755
804
|
const organizerCommands = ['join', 'exam', 'demo', 'retry', 'next', 'prev', 'logout', 'setup', 'lang', 'ref', 'ctf', 'mark', 'unmark', 'review', 'submit'];
|
|
756
805
|
// Selection mode allows shell commands prefixed with ! — contestants
|
|
757
806
|
// need !python3, !cat, !base64 etc. for practical questions (Q31-Q40).
|