icoa-cli 2.19.79 → 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/commands/exam.js +10 -5
- 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/commands/exam.js
CHANGED
|
@@ -1925,7 +1925,7 @@ export function registerExamCommand(program) {
|
|
|
1925
1925
|
console.log(chalk.white(' Installing Python packages for practical questions.'));
|
|
1926
1926
|
console.log(chalk.gray(' Expected: 1-2 minutes · ~150MB disk'));
|
|
1927
1927
|
console.log();
|
|
1928
|
-
// Step 1: Check Python 3.12
|
|
1928
|
+
// Step 1: Check Python 3.10+ (all packages support 3.10, 3.12 recommended)
|
|
1929
1929
|
const printPythonInstallGuide = (reason, currentVersion) => {
|
|
1930
1930
|
const platform = process.platform;
|
|
1931
1931
|
console.log();
|
|
@@ -1933,10 +1933,10 @@ export function registerExamCommand(program) {
|
|
|
1933
1933
|
printError('Python 3 not found.');
|
|
1934
1934
|
}
|
|
1935
1935
|
else {
|
|
1936
|
-
printError(`Python ${currentVersion} found, but 3.
|
|
1936
|
+
printError(`Python ${currentVersion} found, but 3.10+ required for the exam.`);
|
|
1937
1937
|
}
|
|
1938
1938
|
console.log();
|
|
1939
|
-
console.log(chalk.bold.white(' How to install Python 3.12:'));
|
|
1939
|
+
console.log(chalk.bold.white(' How to install Python 3.10+ (3.12 recommended):'));
|
|
1940
1940
|
console.log();
|
|
1941
1941
|
if (platform === 'darwin') {
|
|
1942
1942
|
console.log(chalk.yellow(' macOS (Homebrew, recommended):'));
|
|
@@ -1978,11 +1978,16 @@ export function registerExamCommand(program) {
|
|
|
1978
1978
|
const raw = execSync(`${pythonBin} --version`, { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
1979
1979
|
pythonVersion = raw.replace('Python ', '');
|
|
1980
1980
|
const parts = pythonVersion.split('.').map(Number);
|
|
1981
|
-
if (parts[0] < 3 || (parts[0] === 3 && parts[1] <
|
|
1981
|
+
if (parts[0] < 3 || (parts[0] === 3 && parts[1] < 10)) {
|
|
1982
1982
|
printPythonInstallGuide('too_old', pythonVersion);
|
|
1983
1983
|
return;
|
|
1984
1984
|
}
|
|
1985
|
-
|
|
1985
|
+
if (parts[0] === 3 && parts[1] < 12) {
|
|
1986
|
+
console.log(chalk.green(` ✓ Python ${pythonVersion}`) + chalk.gray(' (works, but 3.12 recommended)'));
|
|
1987
|
+
}
|
|
1988
|
+
else {
|
|
1989
|
+
console.log(chalk.green(` ✓ Python ${pythonVersion}`));
|
|
1990
|
+
}
|
|
1986
1991
|
}
|
|
1987
1992
|
catch {
|
|
1988
1993
|
printPythonInstallGuide('missing');
|
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).
|