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.
@@ -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).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "2.19.80",
3
+ "version": "2.19.81",
4
4
  "description": "ICOA CLI — The world's first CLI-native CTF competition terminal",
5
5
  "type": "module",
6
6
  "bin": {