icoa-cli 2.19.67 → 2.19.68
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 +48 -4
- package/dist/repl.js +25 -5
- package/package.json +1 -1
package/dist/commands/exam.js
CHANGED
|
@@ -1898,6 +1898,52 @@ export function registerExamCommand(program) {
|
|
|
1898
1898
|
console.log(chalk.gray(' Expected: 1-2 minutes · ~150MB disk'));
|
|
1899
1899
|
console.log();
|
|
1900
1900
|
// Step 1: Check Python 3.12+
|
|
1901
|
+
const printPythonInstallGuide = (reason, currentVersion) => {
|
|
1902
|
+
const platform = process.platform;
|
|
1903
|
+
console.log();
|
|
1904
|
+
if (reason === 'missing') {
|
|
1905
|
+
printError('Python 3 not found.');
|
|
1906
|
+
}
|
|
1907
|
+
else {
|
|
1908
|
+
printError(`Python ${currentVersion} found, but 3.12+ required for the exam.`);
|
|
1909
|
+
}
|
|
1910
|
+
console.log();
|
|
1911
|
+
console.log(chalk.bold.white(' How to install Python 3.12:'));
|
|
1912
|
+
console.log();
|
|
1913
|
+
if (platform === 'darwin') {
|
|
1914
|
+
console.log(chalk.yellow(' macOS (Homebrew, recommended):'));
|
|
1915
|
+
console.log(chalk.green(' brew install python@3.12'));
|
|
1916
|
+
console.log();
|
|
1917
|
+
console.log(chalk.gray(' Or download installer:'));
|
|
1918
|
+
console.log(chalk.gray(' https://www.python.org/downloads/macos/'));
|
|
1919
|
+
}
|
|
1920
|
+
else if (platform === 'linux') {
|
|
1921
|
+
console.log(chalk.yellow(' Ubuntu / Debian (deadsnakes PPA):'));
|
|
1922
|
+
console.log(chalk.green(' sudo add-apt-repository ppa:deadsnakes/ppa -y'));
|
|
1923
|
+
console.log(chalk.green(' sudo apt update'));
|
|
1924
|
+
console.log(chalk.green(' sudo apt install -y python3.12 python3.12-venv python3.12-distutils'));
|
|
1925
|
+
console.log(chalk.gray(' sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1'));
|
|
1926
|
+
console.log();
|
|
1927
|
+
console.log(chalk.yellow(' Fedora / RHEL:'));
|
|
1928
|
+
console.log(chalk.green(' sudo dnf install -y python3.12'));
|
|
1929
|
+
console.log();
|
|
1930
|
+
console.log(chalk.yellow(' Arch / Manjaro:'));
|
|
1931
|
+
console.log(chalk.green(' sudo pacman -S python'));
|
|
1932
|
+
}
|
|
1933
|
+
else if (platform === 'win32') {
|
|
1934
|
+
console.log(chalk.yellow(' Windows (winget, recommended):'));
|
|
1935
|
+
console.log(chalk.green(' winget install Python.Python.3.12'));
|
|
1936
|
+
console.log();
|
|
1937
|
+
console.log(chalk.gray(' Or download installer:'));
|
|
1938
|
+
console.log(chalk.gray(' https://www.python.org/downloads/windows/'));
|
|
1939
|
+
}
|
|
1940
|
+
else {
|
|
1941
|
+
console.log(chalk.gray(' https://www.python.org/downloads/'));
|
|
1942
|
+
}
|
|
1943
|
+
console.log();
|
|
1944
|
+
console.log(chalk.white(' After installing, run: ') + chalk.bold.cyan('exam setup'));
|
|
1945
|
+
console.log();
|
|
1946
|
+
};
|
|
1901
1947
|
const pythonBin = 'python3';
|
|
1902
1948
|
let pythonVersion = '';
|
|
1903
1949
|
try {
|
|
@@ -1905,15 +1951,13 @@ export function registerExamCommand(program) {
|
|
|
1905
1951
|
pythonVersion = raw.replace('Python ', '');
|
|
1906
1952
|
const parts = pythonVersion.split('.').map(Number);
|
|
1907
1953
|
if (parts[0] < 3 || (parts[0] === 3 && parts[1] < 12)) {
|
|
1908
|
-
|
|
1909
|
-
printInfo('Install: https://www.python.org/downloads/');
|
|
1954
|
+
printPythonInstallGuide('too_old', pythonVersion);
|
|
1910
1955
|
return;
|
|
1911
1956
|
}
|
|
1912
1957
|
console.log(chalk.green(` ✓ Python ${pythonVersion}`));
|
|
1913
1958
|
}
|
|
1914
1959
|
catch {
|
|
1915
|
-
|
|
1916
|
-
printInfo('Install: https://www.python.org/downloads/');
|
|
1960
|
+
printPythonInstallGuide('missing');
|
|
1917
1961
|
return;
|
|
1918
1962
|
}
|
|
1919
1963
|
// Step 2: Check pip
|
package/dist/repl.js
CHANGED
|
@@ -6,7 +6,7 @@ import { isActivated, activateToken, isFreeCommand, isDeviceMatch, recordExit, r
|
|
|
6
6
|
import { setReplMode } from './lib/ui.js';
|
|
7
7
|
import { isChatActive, handleChatMessage } from './commands/ai4ctf.js';
|
|
8
8
|
import { isCtf4aiActive, handleCtf4aiMessage } from './commands/ctf4ai-demo.js';
|
|
9
|
-
import { getExamState } from './lib/exam-state.js';
|
|
9
|
+
import { getExamState, getRealExamState, getDemoState } from './lib/exam-state.js';
|
|
10
10
|
import { getDemoStats } from './lib/demo-stats.js';
|
|
11
11
|
import { isExamSetupComplete } from './lib/exam-setup.js';
|
|
12
12
|
import { DEMO_PICK_SIZE, DEMO_POOL_SIZE } from './lib/demo-exam.js';
|
|
@@ -17,6 +17,16 @@ import { startLogSync, stopLogSync } from './lib/log-sync.js';
|
|
|
17
17
|
import { existsSync, mkdirSync } from 'node:fs';
|
|
18
18
|
import { join } from 'node:path';
|
|
19
19
|
import { homedir } from 'node:os';
|
|
20
|
+
// Compute the REPL prompt based on current state.
|
|
21
|
+
// Real exam wins over demo (matches getExamState() priority). Chat modes have
|
|
22
|
+
// their own prompts and are handled separately.
|
|
23
|
+
function computePrompt() {
|
|
24
|
+
if (getRealExamState())
|
|
25
|
+
return chalk.cyan('exam> ');
|
|
26
|
+
if (getDemoState())
|
|
27
|
+
return chalk.yellow('demo> ');
|
|
28
|
+
return chalk.green('icoa> ');
|
|
29
|
+
}
|
|
20
30
|
// Competition workspace — all system commands restricted here
|
|
21
31
|
const WORKSPACE = join(homedir(), 'icoa-workspace');
|
|
22
32
|
function ensureWorkspace() {
|
|
@@ -334,19 +344,29 @@ export async function startRepl(program, resumeMode) {
|
|
|
334
344
|
const rl = createInterface({
|
|
335
345
|
input: process.stdin,
|
|
336
346
|
output: process.stdout,
|
|
337
|
-
prompt:
|
|
347
|
+
prompt: computePrompt(),
|
|
338
348
|
terminal: true,
|
|
339
349
|
});
|
|
340
350
|
let processing = false;
|
|
341
351
|
setReplMode(true);
|
|
342
352
|
startLogSync();
|
|
353
|
+
// Wrap rl.prompt() so it always picks up current state (icoa/demo/exam mode).
|
|
354
|
+
// This keeps the prompt accurate after exam token entry, exam submit, demo finish, etc.
|
|
355
|
+
// Chat modes (ai4ctf/ctf4ai) set their own prompt via setPrompt and bypass this.
|
|
356
|
+
const _origPrompt = rl.prompt.bind(rl);
|
|
357
|
+
rl.prompt = (preserveCursor) => {
|
|
358
|
+
if (!isChatActive() && !isCtf4aiActive()) {
|
|
359
|
+
rl.setPrompt(computePrompt());
|
|
360
|
+
}
|
|
361
|
+
_origPrompt(preserveCursor);
|
|
362
|
+
};
|
|
343
363
|
rl.prompt();
|
|
344
364
|
rl.on('line', async (line) => {
|
|
345
365
|
if (processing)
|
|
346
366
|
return;
|
|
347
367
|
const input = line.trim();
|
|
348
368
|
if (!input) {
|
|
349
|
-
rl.setPrompt(isChatActive() ? chalk.magenta('ai4ctf> ') :
|
|
369
|
+
rl.setPrompt(isChatActive() ? chalk.magenta('ai4ctf> ') : computePrompt());
|
|
350
370
|
rl.prompt();
|
|
351
371
|
return;
|
|
352
372
|
}
|
|
@@ -356,7 +376,7 @@ export async function startRepl(program, resumeMode) {
|
|
|
356
376
|
const result = await handleChatMessage(input);
|
|
357
377
|
processing = false;
|
|
358
378
|
if (result === 'exit') {
|
|
359
|
-
rl.setPrompt(
|
|
379
|
+
rl.setPrompt(computePrompt());
|
|
360
380
|
}
|
|
361
381
|
rl.prompt();
|
|
362
382
|
return;
|
|
@@ -367,7 +387,7 @@ export async function startRepl(program, resumeMode) {
|
|
|
367
387
|
const result = await handleCtf4aiMessage(input);
|
|
368
388
|
processing = false;
|
|
369
389
|
if (result === 'exit' || result === 'solved') {
|
|
370
|
-
rl.setPrompt(
|
|
390
|
+
rl.setPrompt(computePrompt());
|
|
371
391
|
}
|
|
372
392
|
rl.prompt();
|
|
373
393
|
return;
|