icoa-cli 2.19.80 → 2.19.81
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 +101 -0
- package/dist/repl.js +36 -1
- package/package.json +1 -1
package/dist/commands/env.js
CHANGED
|
@@ -272,8 +272,109 @@ 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
|
+
try {
|
|
289
|
+
const out = execSync('python3 --version', { encoding: 'utf-8', timeout: 3000 }).trim();
|
|
290
|
+
currentPy = out.replace('Python ', '');
|
|
291
|
+
const parts = currentPy.split('.').map(Number);
|
|
292
|
+
if (parts[0] === 3 && parts[1] === 12)
|
|
293
|
+
pyStatus = 'ok';
|
|
294
|
+
else if (parts[0] === 3 && parts[1] >= 10 && parts[1] < 12)
|
|
295
|
+
pyStatus = 'old';
|
|
296
|
+
else if (parts[0] === 3 && parts[1] > 12)
|
|
297
|
+
pyStatus = 'new';
|
|
298
|
+
else
|
|
299
|
+
pyStatus = 'missing';
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
pyStatus = 'missing';
|
|
303
|
+
}
|
|
304
|
+
if (pyStatus === 'ok') {
|
|
305
|
+
console.log(chalk.green(` ✓ Python ${currentPy} — you're good!`));
|
|
306
|
+
console.log(chalk.gray(' Exam toolkit ready. Next: ') + chalk.bold.cyan('exam setup'));
|
|
307
|
+
console.log();
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (pyStatus === 'old') {
|
|
311
|
+
console.log(chalk.yellow(` ⚠ Python ${currentPy} — works, but 3.12 recommended`));
|
|
312
|
+
}
|
|
313
|
+
else if (pyStatus === 'new') {
|
|
314
|
+
console.log(chalk.yellow(` ⚠ Python ${currentPy} — some packages (pwntools, scapy) lack wheels`));
|
|
315
|
+
console.log(chalk.gray(' Downgrading to 3.12 is strongly recommended'));
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
console.log(chalk.red(' ✗ Python 3 not found'));
|
|
319
|
+
}
|
|
320
|
+
console.log();
|
|
321
|
+
if (os === 'darwin') {
|
|
322
|
+
console.log(chalk.bold.white(' macOS (Homebrew)'));
|
|
323
|
+
console.log();
|
|
324
|
+
console.log(chalk.green(' brew install python@3.12'));
|
|
325
|
+
}
|
|
326
|
+
else if (os === 'linux') {
|
|
327
|
+
let distro = 'ubuntu';
|
|
328
|
+
try {
|
|
329
|
+
const release = execSync('cat /etc/os-release 2>/dev/null', { encoding: 'utf-8', timeout: 2000 });
|
|
330
|
+
if (/fedora|rhel|centos/i.test(release))
|
|
331
|
+
distro = 'fedora';
|
|
332
|
+
else if (/arch|manjaro/i.test(release))
|
|
333
|
+
distro = 'arch';
|
|
334
|
+
else
|
|
335
|
+
distro = 'ubuntu';
|
|
336
|
+
}
|
|
337
|
+
catch { }
|
|
338
|
+
if (distro === 'ubuntu') {
|
|
339
|
+
console.log(chalk.bold.white(' Ubuntu / Debian / Kali (deadsnakes PPA)'));
|
|
340
|
+
console.log();
|
|
341
|
+
console.log(chalk.gray(' Copy-paste this one-liner:'));
|
|
342
|
+
console.log();
|
|
343
|
+
console.log(chalk.green(' sudo add-apt-repository ppa:deadsnakes/ppa -y && \\'));
|
|
344
|
+
console.log(chalk.green(' sudo apt update && \\'));
|
|
345
|
+
console.log(chalk.green(' sudo apt install -y python3.12 python3.12-venv python3.12-distutils && \\'));
|
|
346
|
+
console.log(chalk.green(' sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1'));
|
|
347
|
+
}
|
|
348
|
+
else if (distro === 'fedora') {
|
|
349
|
+
console.log(chalk.bold.white(' Fedora / RHEL / CentOS'));
|
|
350
|
+
console.log();
|
|
351
|
+
console.log(chalk.green(' sudo dnf install -y python3.12 python3.12-pip'));
|
|
352
|
+
}
|
|
353
|
+
else if (distro === 'arch') {
|
|
354
|
+
console.log(chalk.bold.white(' Arch / Manjaro (via pyenv)'));
|
|
355
|
+
console.log();
|
|
356
|
+
console.log(chalk.green(' sudo pacman -S pyenv && pyenv install 3.12 && pyenv global 3.12'));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
else if (os === 'win32') {
|
|
360
|
+
console.log(chalk.bold.white(' Windows (WSL recommended)'));
|
|
361
|
+
console.log();
|
|
362
|
+
console.log(chalk.gray(' WSL + Ubuntu (best):'));
|
|
363
|
+
console.log(chalk.green(' wsl --install'));
|
|
364
|
+
console.log(chalk.gray(' Then inside Ubuntu, run the Ubuntu commands.'));
|
|
365
|
+
console.log();
|
|
366
|
+
console.log(chalk.gray(' Or native:'));
|
|
367
|
+
console.log(chalk.green(' winget install Python.Python.3.12'));
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
console.log(chalk.gray(' https://www.python.org/downloads/'));
|
|
371
|
+
}
|
|
372
|
+
console.log();
|
|
373
|
+
console.log(chalk.white(' After installing, verify:'));
|
|
374
|
+
console.log(chalk.bold.cyan(' env python') + chalk.gray(' # re-check Python'));
|
|
375
|
+
console.log(chalk.bold.cyan(' exam setup') + chalk.gray(' # install the 13 exam packages'));
|
|
376
|
+
console.log();
|
|
377
|
+
}
|
|
277
378
|
function showStatus() {
|
|
278
379
|
console.log();
|
|
279
380
|
const os = platform();
|
package/dist/repl.js
CHANGED
|
@@ -100,6 +100,25 @@ 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
|
+
try {
|
|
107
|
+
const out = execSyncFn('python3 --version', { encoding: 'utf-8', timeout: 2000 }).trim();
|
|
108
|
+
const version = out.replace('Python ', '');
|
|
109
|
+
const [maj, min] = version.split('.').map(Number);
|
|
110
|
+
if (maj === 3 && min === 12)
|
|
111
|
+
return { ok: true, version, status: 'ok' };
|
|
112
|
+
if (maj === 3 && min >= 10 && min < 12)
|
|
113
|
+
return { ok: true, version, status: 'old' };
|
|
114
|
+
if (maj === 3 && min > 12)
|
|
115
|
+
return { ok: true, version, status: 'new' };
|
|
116
|
+
return { ok: false, version, status: 'missing' };
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return { ok: false, version: '', status: 'missing' };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
103
122
|
function printSelectionMenu() {
|
|
104
123
|
const stats = getDemoStats();
|
|
105
124
|
const setupDone = isExamSetupComplete();
|
|
@@ -107,6 +126,22 @@ function printSelectionMenu() {
|
|
|
107
126
|
console.log();
|
|
108
127
|
console.log(' ' + chalk.cyan.bold('[Selection Mode]'));
|
|
109
128
|
console.log();
|
|
129
|
+
// Proactive Python check: only warn when it actually matters (user past demo
|
|
130
|
+
// and heading toward exam setup). Missing or version >= 3.13 shows yellow
|
|
131
|
+
// note pointing at `env python`. Quiet when 3.10-3.12 are installed.
|
|
132
|
+
if (stats.attempts > 0) {
|
|
133
|
+
const py = checkPython();
|
|
134
|
+
if (py.status === 'missing') {
|
|
135
|
+
console.log(chalk.yellow(' ⚠ Python not detected. For exam practical questions:'));
|
|
136
|
+
console.log(chalk.gray(' → ') + chalk.bold.cyan('env python') + chalk.gray(' (platform install guide)'));
|
|
137
|
+
console.log();
|
|
138
|
+
}
|
|
139
|
+
else if (py.status === 'new') {
|
|
140
|
+
console.log(chalk.yellow(` ⚠ Python ${py.version} may lack CTF wheels. Python 3.12 recommended:`));
|
|
141
|
+
console.log(chalk.gray(' → ') + chalk.bold.cyan('env python') + chalk.gray(' (install guide)'));
|
|
142
|
+
console.log();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
110
145
|
if (stats.attempts === 0) {
|
|
111
146
|
// State 0: brand new user. Only demo matters right now.
|
|
112
147
|
console.log(chalk.white(' New here? Start with ') + chalk.bold.cyan('demo') + chalk.white(' — it takes a few minutes.'));
|
|
@@ -751,7 +786,7 @@ export async function startRepl(program, resumeMode) {
|
|
|
751
786
|
}
|
|
752
787
|
const cmd = input.split(/\s+/)[0].toLowerCase();
|
|
753
788
|
// ─── Mode-based command filtering ───
|
|
754
|
-
const selectionCommands = ['exam', 'demo', 'retry', 'nations', 'next', 'prev', 'continue', 'setup', 'lang', 'ref', 'ai4ctf', 'ctf4ai', 'mark', 'unmark', 'review', 'submit'];
|
|
789
|
+
const selectionCommands = ['exam', 'demo', 'retry', 'nations', 'next', 'prev', 'continue', 'setup', 'lang', 'ref', 'ai4ctf', 'ctf4ai', 'mark', 'unmark', 'review', 'submit', 'env'];
|
|
755
790
|
const organizerCommands = ['join', 'exam', 'demo', 'retry', 'next', 'prev', 'logout', 'setup', 'lang', 'ref', 'ctf', 'mark', 'unmark', 'review', 'submit'];
|
|
756
791
|
// Selection mode allows shell commands prefixed with ! — contestants
|
|
757
792
|
// need !python3, !cat, !base64 etc. for practical questions (Q31-Q40).
|