icoa-cli 2.19.81 → 2.19.83

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.
@@ -44,7 +44,7 @@ const PYTHON_LIBS = [
44
44
  { name: 'pyserial', check: 'python3 -c "import serial"', install: 'pyserial==3.5', category: 'Security Tools' },
45
45
  ];
46
46
  // ══════════════════════════════════════════════════════════
47
- // 82 System Tools — brew (Mac) / apt (Ubuntu) / choco (Win)
47
+ // 83 System Tools — brew (Mac) / apt (Ubuntu) / choco (Win)
48
48
  // ══════════════════════════════════════════════════════════
49
49
  const W = process.platform === 'win32';
50
50
  const CMD = (unix, win) => W ? (win || `where ${unix}`) : `which ${unix}`;
@@ -109,6 +109,8 @@ const SYSTEM_TOOLS = [
109
109
  { name: 'xxd', check: CMD('xxd'), brew: 'vim', apt: 'xxd', category: 'Forensics' },
110
110
  { name: 'pdftotext', check: CMD('pdftotext'), brew: 'poppler', apt: 'poppler-utils', category: 'Forensics' },
111
111
  { name: 'pngcheck', check: CMD('pngcheck'), brew: 'pngcheck', apt: 'pngcheck', category: 'Forensics' },
112
+ // sleuthkit — disk-image forensics (mmls/fls/icat/blkcat + 20 more)
113
+ { name: 'sleuthkit', check: CMD('mmls'), brew: 'sleuthkit', apt: 'sleuthkit', choco: 'sleuthkit', category: 'Forensics' },
112
114
  // Crypto & Password (4)
113
115
  { name: 'john', check: CMD('john'), brew: 'john', apt: 'john', choco: 'john', category: 'Crypto & Password' },
114
116
  { name: 'hashcat', check: CMD('hashcat'), brew: 'hashcat', apt: 'hashcat', choco: 'hashcat', category: 'Crypto & Password' },
@@ -270,12 +272,12 @@ function getPythonFullVersion() {
270
272
  }
271
273
  export function registerEnvCommand(program) {
272
274
  const envCmd = program.command('env').description('Manage competition environment');
273
- envCmd.command('status').alias('check').description('Check all 109 tools').action(() => showStatus());
275
+ envCmd.command('status').alias('check').description('Check all 110 tools').action(() => showStatus());
274
276
  envCmd.command('setup').description('Install all Python libraries + system tools').action(async () => { await installAll(); });
275
277
  envCmd.command('python').description('Show Python 3.12 install guide for your platform').action(() => showPythonInstallGuide());
276
278
  envCmd.action(() => showStatus());
277
279
  }
278
- // Lightweight Python 3.12 install guide for Selection mode (no 109-tool check).
280
+ // Lightweight Python 3.12 install guide for Selection mode (no 110-tool check).
279
281
  // Detects distro on Linux so contestants get the right command the first time.
280
282
  function showPythonInstallGuide() {
281
283
  const os = platform();
@@ -285,21 +287,37 @@ function showPythonInstallGuide() {
285
287
  console.log();
286
288
  let currentPy = '';
287
289
  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';
290
+ // Prefer the versioned binary if available. On macOS with Homebrew, `python3`
291
+ // often points to the latest (e.g. 3.13) while python@3.12 is also installed
292
+ // at /opt/homebrew/opt/python@3.12/bin/python3.12. Don't force the user to
293
+ // reinstall just because the default changed.
294
+ const pyProbes = [
295
+ 'python3.12 --version',
296
+ '/opt/homebrew/opt/python@3.12/bin/python3.12 --version',
297
+ '/usr/local/opt/python@3.12/bin/python3.12 --version',
298
+ 'python3 --version',
299
+ ];
300
+ for (const cmd of pyProbes) {
301
+ try {
302
+ const out = execSync(cmd, { encoding: 'utf-8', timeout: 3000 }).trim();
303
+ const ver = out.replace('Python ', '');
304
+ const parts = ver.split('.').map(Number);
305
+ // Only accept as "ok" if this probe returned 3.12. Otherwise keep trying.
306
+ if (parts[0] === 3 && parts[1] === 12) {
307
+ currentPy = ver;
308
+ pyStatus = 'ok';
309
+ break;
310
+ }
311
+ // Remember the last-seen version for fallback reporting
312
+ currentPy = ver;
313
+ if (parts[0] === 3 && parts[1] >= 10 && parts[1] < 12)
314
+ pyStatus = 'old';
315
+ else if (parts[0] === 3 && parts[1] > 12)
316
+ pyStatus = 'new';
317
+ else
318
+ pyStatus = 'missing';
319
+ }
320
+ catch { /* try next probe */ }
303
321
  }
304
322
  if (pyStatus === 'ok') {
305
323
  console.log(chalk.green(` ✓ Python ${currentPy} — you're good!`));
@@ -389,7 +407,7 @@ function showStatus() {
389
407
  console.log(chalk.gray(' You need these for most challenges'));
390
408
  console.log(chalk.yellow(' Recommended') + chalk.gray(' pycryptodome, beautifulsoup4, scapy, sympy'));
391
409
  console.log(chalk.gray(' Covers Web, Crypto, and Forensics'));
392
- console.log(chalk.gray(' Full (109) All tools for every category'));
410
+ console.log(chalk.gray(' Full (110) All tools for every category'));
393
411
  console.log();
394
412
  console.log(chalk.gray(' Missing tools? Run ') + chalk.bold.cyan('env setup') + chalk.gray(' to install everything.'));
395
413
  console.log(chalk.gray(' ─────────────────────────────────────────────'));
@@ -486,7 +504,7 @@ function showStatus() {
486
504
  const total = installed + missing;
487
505
  console.log();
488
506
  console.log(chalk.gray(' ─────────────────────────────────────────────'));
489
- console.log(` ${chalk.green(`✓ ${installed}/${total}`)} ${missing > 0 ? chalk.red(`✗ ${missing} missing`) : chalk.green('All 109 ready!')}`);
507
+ console.log(` ${chalk.green(`✓ ${installed}/${total}`)} ${missing > 0 ? chalk.red(`✗ ${missing} missing`) : chalk.green('All 110 ready!')}`);
490
508
  if (missing > 0) {
491
509
  console.log(chalk.gray(' Install everything: ') + chalk.white('env setup'));
492
510
  }
@@ -101,7 +101,7 @@ async function playDemoIntro() {
101
101
  await waitOrSkip(1500);
102
102
  if (skipped)
103
103
  return;
104
- console.log(chalk.gray(' · 109 CTF tools pre-configured'));
104
+ console.log(chalk.gray(' · 110 CTF tools pre-configured'));
105
105
  await waitOrSkip(400);
106
106
  if (skipped)
107
107
  return;
package/dist/repl.js CHANGED
@@ -103,21 +103,35 @@ const VERSION = '2.5.1';
103
103
  // Quick Python version check (used in Selection menu startup warning).
104
104
  // Returns {ok, version, status}. Silent/defensive — any error means 'missing'.
105
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' };
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 */ }
120
133
  }
134
+ return { ok: lastStatus !== 'missing', version: lastVersion, status: lastStatus };
121
135
  }
122
136
  function printSelectionMenu() {
123
137
  const stats = getDemoStats();
@@ -231,7 +245,7 @@ export async function startRepl(program, resumeMode) {
231
245
  console.log(chalk.bold.white(' What Makes ICOA Different'));
232
246
  console.log(chalk.gray(' · AI-native AI teammate, AI adversary, AI translation'));
233
247
  console.log(chalk.gray(' · CLI OS Complete competition environment in terminal'));
234
- console.log(chalk.gray(' · 109 tools pwntools, z3, gdb, nmap... pre-configured'));
248
+ console.log(chalk.gray(' · 110 tools pwntools, z3, gdb, nmap, sleuthkit... pre-configured'));
235
249
  console.log(chalk.gray(' · Global scale 15,000+ concurrent exams · 15 languages'));
236
250
  console.log();
237
251
  console.log(chalk.bold.white(' Competition Format'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "2.19.81",
3
+ "version": "2.19.83",
4
4
  "description": "ICOA CLI — The world's first CLI-native CTF competition terminal",
5
5
  "type": "module",
6
6
  "bin": {