icoa-cli 2.19.78 → 2.19.80
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/exam.js +92 -22
- package/package.json +1 -1
package/dist/commands/exam.js
CHANGED
|
@@ -1891,22 +1891,33 @@ export function registerExamCommand(program) {
|
|
|
1891
1891
|
console.log();
|
|
1892
1892
|
return;
|
|
1893
1893
|
}
|
|
1894
|
-
// Skip if already set up
|
|
1894
|
+
// Skip if already set up — but only if MOST packages actually installed.
|
|
1895
|
+
// If previous run had 0 or very few successes, retry instead of saying
|
|
1896
|
+
// "already set up" (which is misleading and strands the user).
|
|
1895
1897
|
const existingSetup = getExamSetup();
|
|
1896
1898
|
if (existingSetup) {
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
console.log(chalk.
|
|
1899
|
+
const installedCount = existingSetup.installedPackages.length;
|
|
1900
|
+
const totalAttempted = installedCount + existingSetup.failedPackages.length;
|
|
1901
|
+
const successRate = totalAttempted > 0 ? installedCount / totalAttempted : 0;
|
|
1902
|
+
if (installedCount >= 8 || successRate >= 0.7) {
|
|
1903
|
+
console.log();
|
|
1904
|
+
console.log(chalk.green(' ✓ ') + chalk.green('Environment already set up'));
|
|
1905
|
+
console.log(chalk.gray(` Completed: ${existingSetup.completedAt.split('T')[0]}`));
|
|
1906
|
+
console.log(chalk.gray(` Python: ${existingSetup.pythonVersion}`));
|
|
1907
|
+
console.log(chalk.gray(` Packages: ${installedCount} installed`));
|
|
1908
|
+
if (existingSetup.failedPackages.length > 0) {
|
|
1909
|
+
console.log(chalk.yellow(` Failed: ${existingSetup.failedPackages.join(', ')}`));
|
|
1910
|
+
}
|
|
1911
|
+
console.log();
|
|
1912
|
+
console.log(chalk.white(' Next step: ') + chalk.bold.cyan('exam <token>'));
|
|
1913
|
+
console.log(chalk.gray(' Or type ') + chalk.bold.cyan('back') + chalk.gray(' to return to the main menu.'));
|
|
1914
|
+
console.log();
|
|
1915
|
+
return;
|
|
1904
1916
|
}
|
|
1917
|
+
// Prior setup mostly failed — retry
|
|
1905
1918
|
console.log();
|
|
1906
|
-
console.log(chalk.
|
|
1907
|
-
console.log(chalk.gray(' Or type ') + chalk.bold.cyan('back') + chalk.gray(' to return to the main menu.'));
|
|
1919
|
+
console.log(chalk.yellow(` Previous setup had only ${installedCount}/${totalAttempted} packages. Retrying...`));
|
|
1908
1920
|
console.log();
|
|
1909
|
-
return;
|
|
1910
1921
|
}
|
|
1911
1922
|
console.log();
|
|
1912
1923
|
printHeader('Exam Environment Setup');
|
|
@@ -1914,7 +1925,7 @@ export function registerExamCommand(program) {
|
|
|
1914
1925
|
console.log(chalk.white(' Installing Python packages for practical questions.'));
|
|
1915
1926
|
console.log(chalk.gray(' Expected: 1-2 minutes · ~150MB disk'));
|
|
1916
1927
|
console.log();
|
|
1917
|
-
// Step 1: Check Python 3.12
|
|
1928
|
+
// Step 1: Check Python 3.10+ (all packages support 3.10, 3.12 recommended)
|
|
1918
1929
|
const printPythonInstallGuide = (reason, currentVersion) => {
|
|
1919
1930
|
const platform = process.platform;
|
|
1920
1931
|
console.log();
|
|
@@ -1922,10 +1933,10 @@ export function registerExamCommand(program) {
|
|
|
1922
1933
|
printError('Python 3 not found.');
|
|
1923
1934
|
}
|
|
1924
1935
|
else {
|
|
1925
|
-
printError(`Python ${currentVersion} found, but 3.
|
|
1936
|
+
printError(`Python ${currentVersion} found, but 3.10+ required for the exam.`);
|
|
1926
1937
|
}
|
|
1927
1938
|
console.log();
|
|
1928
|
-
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):'));
|
|
1929
1940
|
console.log();
|
|
1930
1941
|
if (platform === 'darwin') {
|
|
1931
1942
|
console.log(chalk.yellow(' macOS (Homebrew, recommended):'));
|
|
@@ -1967,11 +1978,16 @@ export function registerExamCommand(program) {
|
|
|
1967
1978
|
const raw = execSync(`${pythonBin} --version`, { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
1968
1979
|
pythonVersion = raw.replace('Python ', '');
|
|
1969
1980
|
const parts = pythonVersion.split('.').map(Number);
|
|
1970
|
-
if (parts[0] < 3 || (parts[0] === 3 && parts[1] <
|
|
1981
|
+
if (parts[0] < 3 || (parts[0] === 3 && parts[1] < 10)) {
|
|
1971
1982
|
printPythonInstallGuide('too_old', pythonVersion);
|
|
1972
1983
|
return;
|
|
1973
1984
|
}
|
|
1974
|
-
|
|
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
|
+
}
|
|
1975
1991
|
}
|
|
1976
1992
|
catch {
|
|
1977
1993
|
printPythonInstallGuide('missing');
|
|
@@ -2006,11 +2022,39 @@ export function registerExamCommand(program) {
|
|
|
2006
2022
|
console.log();
|
|
2007
2023
|
console.log(chalk.white(` Installing ${PACKAGES.length} packages...`));
|
|
2008
2024
|
console.log();
|
|
2025
|
+
// Detect PEP 668 "externally-managed-environment" — modern Ubuntu/Debian/Kali
|
|
2026
|
+
// block system-wide pip installs. Try a harmless install to detect.
|
|
2027
|
+
let pipExtraFlags = '';
|
|
2028
|
+
try {
|
|
2029
|
+
execSync(`${pythonBin} -m pip install --dry-run pip 2>&1`, { encoding: 'utf-8', timeout: 10000 });
|
|
2030
|
+
}
|
|
2031
|
+
catch (e) {
|
|
2032
|
+
const msg = String(e?.stdout || e?.stderr || e?.message || '');
|
|
2033
|
+
if (/externally-managed-environment|break-system-packages/i.test(msg)) {
|
|
2034
|
+
// Pick the safer option: --user (installs to ~/.local, no sudo needed)
|
|
2035
|
+
pipExtraFlags = '--user --break-system-packages';
|
|
2036
|
+
console.log(chalk.yellow(' ℹ PEP 668 detected — using --user install (no sudo needed)'));
|
|
2037
|
+
console.log();
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
// Python 3.13 warning: pwntools may fail to build
|
|
2041
|
+
const pyParts = pythonVersion.split('.').map(Number);
|
|
2042
|
+
if (pyParts[0] === 3 && pyParts[1] >= 13) {
|
|
2043
|
+
console.log(chalk.yellow(` ⚠ Python ${pythonVersion} detected`));
|
|
2044
|
+
console.log(chalk.gray(' Some packages (pwntools, scapy) may not have wheels yet for 3.13.'));
|
|
2045
|
+
console.log(chalk.gray(' Python 3.12 is recommended. Install:'));
|
|
2046
|
+
if (process.platform === 'linux') {
|
|
2047
|
+
console.log(chalk.green(' sudo apt install python3.12 python3.12-venv'));
|
|
2048
|
+
console.log(chalk.green(' sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1'));
|
|
2049
|
+
}
|
|
2050
|
+
console.log();
|
|
2051
|
+
}
|
|
2009
2052
|
const installed = [];
|
|
2010
2053
|
const failed = [];
|
|
2054
|
+
let firstError = '';
|
|
2011
2055
|
for (const pkg of PACKAGES) {
|
|
2012
2056
|
try {
|
|
2013
|
-
execSync(`${pythonBin} -m pip install "${pkg}" --quiet 2
|
|
2057
|
+
execSync(`${pythonBin} -m pip install "${pkg}" ${pipExtraFlags} --quiet 2>&1`, {
|
|
2014
2058
|
encoding: 'utf-8', timeout: 180000, stdio: 'pipe',
|
|
2015
2059
|
});
|
|
2016
2060
|
const importName = IMPORT_MAP[pkg] || pkg;
|
|
@@ -2028,24 +2072,50 @@ export function registerExamCommand(program) {
|
|
|
2028
2072
|
console.log(chalk.green(` ✓ ${pkg}`) + (ver ? chalk.gray(` (${ver})`) : ''));
|
|
2029
2073
|
installed.push(ver ? `${pkg}==${ver}` : pkg);
|
|
2030
2074
|
}
|
|
2031
|
-
catch {
|
|
2032
|
-
|
|
2033
|
-
|
|
2075
|
+
catch (e) {
|
|
2076
|
+
const raw = String(e?.stdout || e?.stderr || e?.message || '').slice(-400);
|
|
2077
|
+
const short = raw.split('\n').filter((l) => l.trim()).slice(-2).join(' | ').slice(0, 120);
|
|
2078
|
+
console.log(chalk.red(` ✗ ${pkg}`) + chalk.gray(` ${short}`));
|
|
2079
|
+
failed.push({ pkg, error: short });
|
|
2080
|
+
if (!firstError)
|
|
2081
|
+
firstError = raw;
|
|
2034
2082
|
}
|
|
2035
2083
|
}
|
|
2084
|
+
// If wholesale failure, show the first full error so user can debug
|
|
2085
|
+
if (installed.length === 0 && firstError) {
|
|
2086
|
+
console.log();
|
|
2087
|
+
console.log(chalk.yellow(' First error detail (for debugging):'));
|
|
2088
|
+
console.log(chalk.gray(' ' + firstError.split('\n').slice(-6).join('\n ')));
|
|
2089
|
+
}
|
|
2036
2090
|
// Save state
|
|
2037
2091
|
saveExamSetup({
|
|
2038
2092
|
completedAt: new Date().toISOString(),
|
|
2039
2093
|
pythonVersion,
|
|
2040
2094
|
installedPackages: installed,
|
|
2041
|
-
failedPackages: failed,
|
|
2095
|
+
failedPackages: failed.map((f) => f.pkg),
|
|
2042
2096
|
});
|
|
2043
2097
|
console.log();
|
|
2044
2098
|
if (failed.length === 0) {
|
|
2045
2099
|
printSuccess(`Environment ready! All ${PACKAGES.length} packages installed.`);
|
|
2046
2100
|
}
|
|
2101
|
+
else if (installed.length === 0) {
|
|
2102
|
+
printError(`Setup failed — 0 of ${PACKAGES.length} packages installed.`);
|
|
2103
|
+
console.log();
|
|
2104
|
+
console.log(chalk.yellow(' Troubleshooting:'));
|
|
2105
|
+
if (pyParts[0] === 3 && pyParts[1] >= 13) {
|
|
2106
|
+
console.log(chalk.gray(' Python 3.13 is too new — most CTF libraries lack wheels.'));
|
|
2107
|
+
console.log(chalk.gray(' Install Python 3.12 and re-run exam setup.'));
|
|
2108
|
+
}
|
|
2109
|
+
else {
|
|
2110
|
+
console.log(chalk.gray(' • Check you have internet (pip.pypa.io must be reachable)'));
|
|
2111
|
+
console.log(chalk.gray(' • Try: ') + chalk.cyan(`${pythonBin} -m pip install pwntools`) + chalk.gray(' — see full error'));
|
|
2112
|
+
console.log(chalk.gray(' • On Kali/Ubuntu 22+: might need python3.12-dev + build-essential'));
|
|
2113
|
+
}
|
|
2114
|
+
console.log();
|
|
2115
|
+
console.log(chalk.white(' Rerun when fixed: ') + chalk.bold.cyan('exam setup'));
|
|
2116
|
+
}
|
|
2047
2117
|
else {
|
|
2048
|
-
printWarning(`${installed.length}/${PACKAGES.length} packages installed. ${failed.length} failed: ${failed.join(', ')}`);
|
|
2118
|
+
printWarning(`${installed.length}/${PACKAGES.length} packages installed. ${failed.length} failed: ${failed.map((f) => f.pkg).join(', ')}`);
|
|
2049
2119
|
}
|
|
2050
2120
|
// Python usage tutorial for beginners
|
|
2051
2121
|
console.log();
|