icoa-cli 2.19.17 โ 2.19.19
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 +44 -31
- package/dist/commands/lang.js +13 -7
- package/dist/lib/demo-exam.d.ts +11 -3
- package/dist/lib/demo-exam.js +149 -47
- package/dist/repl.js +28 -34
- package/dist/types/index.d.ts +2 -0
- package/package.json +1 -1
- package/translations/ar/demo-explanations.json +17 -2
- package/translations/ar/demo.json +18 -18
- package/translations/de/demo-explanations.json +16 -1
- package/translations/de/demo.json +18 -18
- package/translations/es/demo-explanations.json +16 -1
- package/translations/es/demo.json +18 -18
- package/translations/fr/demo-explanations.json +17 -2
- package/translations/fr/demo.json +18 -18
- package/translations/hi/demo-explanations.json +17 -2
- package/translations/hi/demo.json +18 -18
- package/translations/id/demo-explanations.json +17 -2
- package/translations/id/demo.json +18 -18
- package/translations/ja/demo-explanations.json +16 -1
- package/translations/ja/demo.json +18 -18
- package/translations/ko/demo-explanations.json +17 -2
- package/translations/ko/demo.json +18 -18
- package/translations/pt/demo-explanations.json +16 -1
- package/translations/pt/demo.json +18 -18
- package/translations/ru/demo-explanations.json +16 -1
- package/translations/ru/demo.json +18 -18
- package/translations/th/demo-explanations.json +16 -1
- package/translations/th/demo.json +18 -18
- package/translations/tr/demo-explanations.json +16 -1
- package/translations/tr/demo.json +18 -18
- package/translations/vi/demo-explanations.json +16 -1
- package/translations/vi/demo.json +18 -18
- package/translations/zh/demo-explanations.json +16 -1
- package/translations/zh/demo.json +18 -18
package/dist/commands/exam.js
CHANGED
|
@@ -405,13 +405,6 @@ export function registerExamCommand(program) {
|
|
|
405
405
|
const q = state.questions.find((qq) => qq.number === currentQ);
|
|
406
406
|
if (!q)
|
|
407
407
|
return;
|
|
408
|
-
// Find the correct answer for demo
|
|
409
|
-
let correctAnswer = null;
|
|
410
|
-
if (state.session.examId === 'demo-free') {
|
|
411
|
-
import('../lib/demo-exam.js').then(({ DEMO_ANSWERS }) => {
|
|
412
|
-
correctAnswer = DEMO_ANSWERS[currentQ];
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
408
|
// Check: already used 2 helps on this question
|
|
416
409
|
if (qHelps >= 2) {
|
|
417
410
|
console.log();
|
|
@@ -468,9 +461,8 @@ export function registerExamCommand(program) {
|
|
|
468
461
|
console.log(chalk.yellow(` ๐ก Option ${toRemove} eliminated!`) + chalk.gray(` (help ${help.used}/${help.max})`));
|
|
469
462
|
printQuestion(q, state.answers[q.number]);
|
|
470
463
|
};
|
|
471
|
-
if (state.session.examId === 'demo-free') {
|
|
472
|
-
|
|
473
|
-
doEliminate(DEMO_ANSWERS[currentQ]);
|
|
464
|
+
if (state.session.examId === 'demo-free' && q.answer) {
|
|
465
|
+
doEliminate(q.answer);
|
|
474
466
|
}
|
|
475
467
|
else {
|
|
476
468
|
// For server exams, we don't know the answer โ eliminate random non-selected option
|
|
@@ -671,28 +663,34 @@ export function registerExamCommand(program) {
|
|
|
671
663
|
// Demo exam: grade locally
|
|
672
664
|
if (state.session.examId === 'demo-free') {
|
|
673
665
|
try {
|
|
674
|
-
const { DEMO_ANSWERS, getLocalizedExplanations } = await import('../lib/demo-exam.js');
|
|
675
666
|
drawProgress(0, t('grading'));
|
|
676
667
|
await sleep(300);
|
|
677
668
|
let score = 0;
|
|
678
669
|
const wrongQuestions = [];
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
670
|
+
const categoryStats = {};
|
|
671
|
+
for (const q of state.questions) {
|
|
672
|
+
const cat = q.category || 'Other';
|
|
673
|
+
if (!categoryStats[cat])
|
|
674
|
+
categoryStats[cat] = { correct: 0, total: 0 };
|
|
675
|
+
categoryStats[cat].total++;
|
|
676
|
+
const userAns = state.answers[q.number];
|
|
677
|
+
if (userAns && q.answer && userAns === q.answer) {
|
|
682
678
|
score++;
|
|
679
|
+
categoryStats[cat].correct++;
|
|
683
680
|
}
|
|
684
681
|
else {
|
|
685
|
-
wrongQuestions.push(num);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
// Also count unanswered as wrong
|
|
689
|
-
for (const q of state.questions) {
|
|
690
|
-
if (!state.answers[q.number])
|
|
691
682
|
wrongQuestions.push(q.number);
|
|
683
|
+
}
|
|
692
684
|
}
|
|
693
685
|
wrongQuestions.sort((a, b) => a - b);
|
|
694
686
|
drawProgress(100, t('complete'));
|
|
695
687
|
console.log();
|
|
688
|
+
// Elapsed time
|
|
689
|
+
const startedMs = new Date(state.session.startedAt).getTime();
|
|
690
|
+
const elapsedSec = Math.max(0, Math.round((Date.now() - startedMs) / 1000));
|
|
691
|
+
const elapsedMin = Math.floor(elapsedSec / 60);
|
|
692
|
+
const elapsedS = elapsedSec % 60;
|
|
693
|
+
const elapsedLabel = `${elapsedMin}m ${elapsedS}s`;
|
|
696
694
|
const questionsSnapshot = [...state.questions];
|
|
697
695
|
const answersSnapshot = { ...state.answers };
|
|
698
696
|
const helpState = getHelpState(state);
|
|
@@ -728,14 +726,27 @@ export function registerExamCommand(program) {
|
|
|
728
726
|
console.log();
|
|
729
727
|
console.log(chalk.bold(` ${t('score')}: ${score}/${total} (${percentage}%)`));
|
|
730
728
|
console.log(chalk.bold(` ${percentage >= 60 ? chalk.green(t('passed')) : chalk.red(t('notPassed'))}`));
|
|
729
|
+
console.log(chalk.gray(` Elapsed: ${elapsedLabel}`));
|
|
731
730
|
console.log();
|
|
732
731
|
console.log(chalk.yellow(' International Cyber Olympiad in AI 2026'));
|
|
733
732
|
console.log(chalk.gray(' Sydney, Australia ยท Jun 27 - Jul 2, 2026'));
|
|
734
733
|
console.log();
|
|
735
734
|
console.log(chalk.cyan(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
735
|
+
// Per-category breakdown
|
|
736
|
+
const catEntries = Object.entries(categoryStats);
|
|
737
|
+
if (catEntries.length > 0) {
|
|
738
|
+
console.log();
|
|
739
|
+
console.log(chalk.bold.white(' By category'));
|
|
740
|
+
for (const [cat, s] of catEntries) {
|
|
741
|
+
const pct = s.total > 0 ? Math.round(s.correct / s.total * 100) : 0;
|
|
742
|
+
const color = pct >= 80 ? chalk.green : pct >= 50 ? chalk.yellow : chalk.red;
|
|
743
|
+
console.log(' ' + color(`${cat.padEnd(20)} ${s.correct}/${s.total} (${pct}%)`));
|
|
744
|
+
}
|
|
745
|
+
console.log();
|
|
746
|
+
console.log(chalk.cyan(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
747
|
+
}
|
|
736
748
|
// Show wrong answers with explanations
|
|
737
749
|
if (wrongQuestions.length > 0) {
|
|
738
|
-
const explanations = getLocalizedExplanations();
|
|
739
750
|
console.log();
|
|
740
751
|
console.log(chalk.yellow(` ${wrongQuestions.length} ${t('incorrectIntro')}`));
|
|
741
752
|
console.log();
|
|
@@ -744,7 +755,7 @@ export function registerExamCommand(program) {
|
|
|
744
755
|
if (!q)
|
|
745
756
|
continue;
|
|
746
757
|
const userAns = answersSnapshot[qn];
|
|
747
|
-
const correctAns =
|
|
758
|
+
const correctAns = q.answer;
|
|
748
759
|
console.log(chalk.white(` Q${qn}. ${q.text}`));
|
|
749
760
|
if (userAns) {
|
|
750
761
|
console.log(chalk.red(` ${t('yourAnswer')}: ${userAns}. ${q.options[userAns]}`));
|
|
@@ -752,9 +763,11 @@ export function registerExamCommand(program) {
|
|
|
752
763
|
else {
|
|
753
764
|
console.log(chalk.yellow(` ${t('yourAnswer')}: โ`));
|
|
754
765
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
766
|
+
if (correctAns) {
|
|
767
|
+
console.log(chalk.green(` ${t('correct')}: ${correctAns}. ${q.options[correctAns]}`));
|
|
768
|
+
}
|
|
769
|
+
if (q.explanation) {
|
|
770
|
+
console.log(chalk.gray(` โ ${q.explanation}`));
|
|
758
771
|
}
|
|
759
772
|
console.log();
|
|
760
773
|
}
|
|
@@ -952,8 +965,8 @@ export function registerExamCommand(program) {
|
|
|
952
965
|
.description('Try a free practice exam (no account needed)')
|
|
953
966
|
.action(async () => {
|
|
954
967
|
logCommand('exam demo');
|
|
955
|
-
const {
|
|
956
|
-
const DEMO_QUESTIONS =
|
|
968
|
+
const { pickDemoQuestions, getLocalizedDemoSession, DEMO_PICK_SIZE, DEMO_POOL_SIZE } = await import('../lib/demo-exam.js');
|
|
969
|
+
const DEMO_QUESTIONS = pickDemoQuestions(DEMO_PICK_SIZE);
|
|
957
970
|
const DEMO_SESSION = getLocalizedDemoSession();
|
|
958
971
|
const existing = getExamState();
|
|
959
972
|
if (existing) {
|
|
@@ -971,7 +984,7 @@ export function registerExamCommand(program) {
|
|
|
971
984
|
printHeader('ICOA Demo Exam โ Free Practice');
|
|
972
985
|
console.log();
|
|
973
986
|
console.log(chalk.white(' Free practice ยท No account needed ยท No time limit'));
|
|
974
|
-
console.log(chalk.white(
|
|
987
|
+
console.log(chalk.white(` ${DEMO_PICK_SIZE} random questions from a pool of ${DEMO_POOL_SIZE} ยท Pick one answer per question`));
|
|
975
988
|
console.log();
|
|
976
989
|
printHowToPlay();
|
|
977
990
|
console.log();
|
|
@@ -981,7 +994,7 @@ export function registerExamCommand(program) {
|
|
|
981
994
|
if (currentLang === 'en') {
|
|
982
995
|
console.log(chalk.gray(' Questions in English. To switch language first:'));
|
|
983
996
|
console.log(chalk.gray(' lang es (Espaรฑol) ยท lang zh (ไธญๆ) ยท lang ko (ํ๊ตญ์ด) ยท lang ja (ๆฅๆฌ่ช)'));
|
|
984
|
-
console.log(chalk.gray(' lang fr ยท lang ar ยท lang pt ยท
|
|
997
|
+
console.log(chalk.gray(' lang fr ยท lang ar ยท lang pt ยท 15 languages supported'));
|
|
985
998
|
}
|
|
986
999
|
else {
|
|
987
1000
|
console.log(chalk.green(` Language: ${currentLang}`));
|
|
@@ -992,7 +1005,7 @@ export function registerExamCommand(program) {
|
|
|
992
1005
|
console.log();
|
|
993
1006
|
drawProgress(0, 'Preparing questions...');
|
|
994
1007
|
await sleep(200);
|
|
995
|
-
drawProgress(40, '
|
|
1008
|
+
drawProgress(40, 'Shuffling options...');
|
|
996
1009
|
await sleep(200);
|
|
997
1010
|
drawProgress(80, 'Almost ready...');
|
|
998
1011
|
await sleep(150);
|
|
@@ -1000,7 +1013,7 @@ export function registerExamCommand(program) {
|
|
|
1000
1013
|
console.log();
|
|
1001
1014
|
console.log();
|
|
1002
1015
|
saveExamState({ session, questions: DEMO_QUESTIONS, answers: {} });
|
|
1003
|
-
printKeyValue('Questions',
|
|
1016
|
+
printKeyValue('Questions', String(DEMO_PICK_SIZE));
|
|
1004
1017
|
printKeyValue('Duration', 'No time limit');
|
|
1005
1018
|
// Show first question
|
|
1006
1019
|
printQuestion(DEMO_QUESTIONS[0]);
|
package/dist/commands/lang.js
CHANGED
|
@@ -50,22 +50,28 @@ export function registerLangCommand(program) {
|
|
|
50
50
|
}
|
|
51
51
|
saveConfig({ language: code });
|
|
52
52
|
printSuccess(`Language set to: ${LANG_NAMES[code] || code}`);
|
|
53
|
-
// If demo exam in progress,
|
|
53
|
+
// If demo exam in progress, restart with fresh random pick in new language.
|
|
54
|
+
// (Progress is lost because questions are randomly picked from a 30-question pool
|
|
55
|
+
// and each attempt shuffles option positions โ mid-exam translation would
|
|
56
|
+
// desync the answer key.)
|
|
54
57
|
const state = getExamState();
|
|
55
58
|
if (state && state.session.examId === 'demo-free') {
|
|
56
59
|
try {
|
|
57
|
-
const {
|
|
58
|
-
|
|
60
|
+
const { pickDemoQuestions, getLocalizedDemoSession, DEMO_PICK_SIZE } = await import('../lib/demo-exam.js');
|
|
61
|
+
const freshQuestions = pickDemoQuestions(DEMO_PICK_SIZE);
|
|
62
|
+
state.questions = freshQuestions;
|
|
63
|
+
state.answers = {};
|
|
59
64
|
state.session.examName = getLocalizedDemoSession().examName;
|
|
65
|
+
state.session.startedAt = new Date().toISOString();
|
|
66
|
+
state._lastQ = 1;
|
|
60
67
|
const { saveExamState } = await import('../lib/exam-state.js');
|
|
61
68
|
saveExamState(state);
|
|
62
|
-
const currentQ = state._lastQ || 1;
|
|
63
69
|
console.log();
|
|
64
|
-
console.log(chalk.green(` Demo
|
|
65
|
-
console.log(chalk.white(
|
|
70
|
+
console.log(chalk.green(` Demo restarted in ${LANG_NAMES[code] || code}.`));
|
|
71
|
+
console.log(chalk.white(' Type: exam q 1'));
|
|
66
72
|
}
|
|
67
73
|
catch {
|
|
68
|
-
console.log(chalk.gray(' Language changed.
|
|
74
|
+
console.log(chalk.gray(' Language changed. Type: demo'));
|
|
69
75
|
}
|
|
70
76
|
}
|
|
71
77
|
else if (state) {
|
package/dist/lib/demo-exam.d.ts
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import type { ExamQuestion, ExamSession } from '../types/index.js';
|
|
2
|
+
export declare const DEMO_POOL_SIZE = 30;
|
|
3
|
+
export declare const DEMO_PICK_SIZE = 10;
|
|
2
4
|
export declare const DEMO_SESSION: ExamSession;
|
|
3
|
-
export declare const DEMO_ANSWERS: Record<number,
|
|
5
|
+
export declare const DEMO_ANSWERS: Record<number, 'A' | 'B' | 'C' | 'D'>;
|
|
4
6
|
export declare const DEMO_EXPLANATIONS: Record<number, string>;
|
|
5
7
|
export declare const DEMO_QUESTIONS: ExamQuestion[];
|
|
8
|
+
/**
|
|
9
|
+
* Pick `n` random questions from the pool, shuffle each question's options,
|
|
10
|
+
* renumber 1..n. Returns fully self-contained questions with answer + explanation
|
|
11
|
+
* populated, so downstream code does not need to look up DEMO_ANSWERS by number.
|
|
12
|
+
*/
|
|
13
|
+
export declare function pickDemoQuestions(n?: number): ExamQuestion[];
|
|
6
14
|
/**
|
|
7
15
|
* Get localized explanations for demo questions.
|
|
8
16
|
* Reads from translations/<lang>/demo-explanations.json.
|
|
9
|
-
* Falls back to English
|
|
17
|
+
* Falls back to English for any missing entries.
|
|
10
18
|
*/
|
|
11
19
|
export declare function getLocalizedExplanations(): Record<number, string>;
|
|
12
20
|
/**
|
|
13
21
|
* Get demo questions translated to user's language.
|
|
14
22
|
* Reads from translations/<lang>/demo.json (bundled static file).
|
|
15
|
-
*
|
|
23
|
+
* Per-question fallback: missing entries use the English source.
|
|
16
24
|
*/
|
|
17
25
|
export declare function getLocalizedDemoQuestions(): ExamQuestion[];
|
|
18
26
|
/**
|
package/dist/lib/demo-exam.js
CHANGED
|
@@ -3,71 +3,166 @@ import { join, dirname } from 'node:path';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { getConfig } from './config.js';
|
|
5
5
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
export const DEMO_POOL_SIZE = 30;
|
|
7
|
+
export const DEMO_PICK_SIZE = 10;
|
|
6
8
|
export const DEMO_SESSION = {
|
|
7
9
|
examId: 'demo-free',
|
|
8
10
|
examName: 'ICOA Demo Exam โ Free Practice',
|
|
9
11
|
startedAt: '',
|
|
10
|
-
durationMinutes: 0,
|
|
11
|
-
questionCount:
|
|
12
|
+
durationMinutes: 0,
|
|
13
|
+
questionCount: DEMO_PICK_SIZE,
|
|
12
14
|
country: 'ALL',
|
|
13
15
|
};
|
|
16
|
+
// Answer key for the 30-question pool (balanced A=7 B=7 C=8 D=8)
|
|
17
|
+
// Q16-30 option order was rebalanced from translate-demo.js to distribute answers.
|
|
14
18
|
export const DEMO_ANSWERS = {
|
|
15
|
-
1: 'C', 2: '
|
|
16
|
-
9: '
|
|
19
|
+
1: 'C', 2: 'B', 3: 'C', 4: 'B', 5: 'C', 6: 'B', 7: 'B', 8: 'C',
|
|
20
|
+
9: 'C', 10: 'B', 11: 'C', 12: 'B', 13: 'B', 14: 'B', 15: 'B',
|
|
21
|
+
16: 'D', 17: 'D', 18: 'C', 19: 'C', 20: 'B', 21: 'A', 22: 'D', 23: 'C',
|
|
22
|
+
24: 'B', 25: 'A', 26: 'B', 27: 'D', 28: 'C', 29: 'A', 30: 'B',
|
|
17
23
|
};
|
|
18
24
|
export const DEMO_EXPLANATIONS = {
|
|
19
25
|
1: 'RSA is an asymmetric (public-key) cipher. AES, DES, and Blowfish are all symmetric ciphers that use the same key for encryption and decryption.',
|
|
20
26
|
2: 'SQL injection occurs when user input is inserted directly into database queries without proper sanitization, allowing attackers to manipulate the query.',
|
|
21
27
|
3: 'HTTP 403 means Forbidden โ the server understood the request but refuses to authorize it. 401 is Unauthorized, 404 is Not Found, 500 is Internal Server Error.',
|
|
22
|
-
4: '
|
|
23
|
-
5: '
|
|
24
|
-
6: '
|
|
25
|
-
7: '
|
|
26
|
-
8: 'A
|
|
27
|
-
9: '
|
|
28
|
-
10: '
|
|
29
|
-
11: '
|
|
30
|
-
12: '
|
|
31
|
-
13: '
|
|
32
|
-
14: '
|
|
33
|
-
15: '
|
|
28
|
+
4: 'A nonce (number used once) is a random value used in cryptographic protocols to prevent replay attacks โ ensuring each request or message is unique and cannot be reused by an attacker.',
|
|
29
|
+
5: 'Wireshark is the standard tool for capturing and analyzing network packets. Burp Suite is for web testing, John the Ripper for password cracking, Ghidra for reverse engineering.',
|
|
30
|
+
6: 'XSS stands for Cross-Site Scripting โ a vulnerability where attackers inject malicious scripts into web pages viewed by other users.',
|
|
31
|
+
7: 'A firewall filters network traffic based on security rules, blocking unauthorized access while allowing legitimate communication. It does not encrypt, scan for viruses, or speed up connections.',
|
|
32
|
+
8: 'A Trojan disguises itself as legitimate software to trick users into installing it. Unlike worms, Trojans do not self-replicate.',
|
|
33
|
+
9: 'HTTPS (HTTP Secure) uses TLS/SSL to encrypt web traffic, protecting data from eavesdropping and tampering. HTTP, FTP, and SMTP are not secure by default.',
|
|
34
|
+
10: 'A cryptographic hash is a one-way function that produces a fixed-size digest. It cannot be reversed, unlike encryption.',
|
|
35
|
+
11: 'Ghidra is a reverse engineering and binary analysis tool developed by the NSA. Nmap scans ports, SQLMap tests SQL injection, Nikto scans web servers.',
|
|
36
|
+
12: 'DNS Spoofing (cache poisoning) manipulates DNS responses to redirect victims to attacker-controlled servers. It is distinct from phishing, SQLi, and brute force.',
|
|
37
|
+
13: 'SSH (Secure Shell) runs on port 22 by default. Port 21 is FTP, 80 is HTTP, 443 is HTTPS.',
|
|
38
|
+
14: 'Two-factor authentication (2FA) requires two distinct types of credentials โ something you know (password) and something you have (phone/token) or are (biometrics).',
|
|
39
|
+
15: 'The command "netstat -tulpn" shows all listening TCP/UDP ports with process info. "ls -la" lists files, "chmod" changes permissions, "cat /etc/passwd" shows user accounts.',
|
|
40
|
+
16: 'A Man-in-the-Middle (MitM) attack intercepts and potentially modifies communications between two parties who believe they are communicating directly with each other.',
|
|
41
|
+
17: 'SHA-256 is a cryptographic hash function. AES-256 is a symmetric cipher, RSA-2048 is an asymmetric cipher, and Diffie-Hellman is a key exchange protocol.',
|
|
42
|
+
18: 'The principle of least privilege means granting users only the minimum permissions necessary to perform their tasks, reducing the attack surface.',
|
|
43
|
+
19: 'Nmap is the standard port scanning tool. Wireshark captures packets, Metasploit is an exploitation framework, and Hashcat is a password cracker.',
|
|
44
|
+
20: 'Ransomware encrypts the victim\'s files and demands payment (usually cryptocurrency) in exchange for the decryption key.',
|
|
45
|
+
21: 'Symmetric encryption uses a single shared key for both encrypting and decrypting. Asymmetric encryption uses a key pair โ a public key to encrypt and a private key to decrypt.',
|
|
46
|
+
22: 'Remote Code Execution (RCE) allows an attacker to run arbitrary code on a target server, often leading to full system compromise. CSRF, clickjacking, and open redirect have different impacts.',
|
|
47
|
+
23: 'OWASP (Open Web Application Security Project) is a non-profit organization that publishes widely-used web security standards and guides, including the OWASP Top 10.',
|
|
48
|
+
24: 'The chmod command changes file permissions in Linux. chown changes ownership, chgrp changes group, and passwd changes user passwords.',
|
|
49
|
+
25: 'An SSL/TLS certificate is a digital document issued by a trusted Certificate Authority that verifies a website\'s identity and enables encrypted HTTPS connections.',
|
|
50
|
+
26: 'Phishing is a social engineering attack that tricks people into revealing sensitive information. Buffer overflow, SQL injection, and port scanning are technical attacks.',
|
|
51
|
+
27: 'The grep command searches for text patterns in files using regular expressions. It is one of the most commonly used Linux text processing tools.',
|
|
52
|
+
28: 'A VPN (Virtual Private Network) creates an encrypted tunnel for internet traffic, protecting data from interception and masking the user\'s IP address.',
|
|
53
|
+
29: 'CSRF (Cross-Site Request Forgery) tricks a logged-in user\'s browser into performing unwanted actions on a trusted site, such as changing account settings or transferring funds.',
|
|
54
|
+
30: 'Passwords should be stored as salted cryptographic hashes. Plain text and Base64 are insecure, and AES encryption is reversible if the key is compromised. Salted hashing is one-way and resistant to rainbow tables.',
|
|
34
55
|
};
|
|
56
|
+
// 30-question pool. Q16-30 options were rebalanced from the original translate-demo.js layout
|
|
57
|
+
// to achieve an even answer distribution across A/B/C/D.
|
|
58
|
+
// Translation files (translations/<lang>/demo.json) must match this option order.
|
|
35
59
|
export const DEMO_QUESTIONS = [
|
|
36
60
|
{ number: 1, text: 'Which algorithm is NOT a symmetric cipher?', category: 'Cryptography',
|
|
37
|
-
options: { A: 'AES', B: '
|
|
61
|
+
options: { A: 'AES', B: 'RSA', C: 'DES', D: 'Blowfish' } },
|
|
38
62
|
{ number: 2, text: 'What does SQL injection exploit?', category: 'Web Security',
|
|
39
|
-
options: { A: '
|
|
63
|
+
options: { A: 'Buffer overflow in web server', B: 'Unsanitized user input in database queries', C: 'Weak encryption algorithms', D: 'Misconfigured firewall rules' } },
|
|
40
64
|
{ number: 3, text: 'Which HTTP status code indicates "Forbidden"?', category: 'Web Security',
|
|
41
|
-
options: { A: '401', B: '404', C: '
|
|
42
|
-
{ number: 4, text: '
|
|
43
|
-
options: { A: '
|
|
44
|
-
{ number: 5, text: '
|
|
45
|
-
options: { A: '
|
|
46
|
-
{ number: 6, text: '
|
|
47
|
-
options: { A: '
|
|
48
|
-
{ number: 7, text: 'What is the
|
|
49
|
-
options: { A: '
|
|
50
|
-
{ number: 8, text: '
|
|
65
|
+
options: { A: '401', B: '404', C: '403', D: '500' } },
|
|
66
|
+
{ number: 4, text: 'What is the primary purpose of a nonce in cryptography?', category: 'Cryptography',
|
|
67
|
+
options: { A: 'Encrypt data at rest', B: 'Prevent replay attacks', C: 'Generate random passwords', D: 'Compress data before encryption' } },
|
|
68
|
+
{ number: 5, text: 'Which tool is commonly used for network packet capture?', category: 'Network',
|
|
69
|
+
options: { A: 'Burp Suite', B: 'Ghidra', C: 'Wireshark', D: 'John the Ripper' } },
|
|
70
|
+
{ number: 6, text: 'What does XSS stand for in cybersecurity?', category: 'Web Security',
|
|
71
|
+
options: { A: 'Extended Security System', B: 'Cross-Site Scripting', C: 'XML Secure Socket', D: 'Cross-Server Sharing' } },
|
|
72
|
+
{ number: 7, text: 'What is the primary function of a firewall?', category: 'Network',
|
|
73
|
+
options: { A: 'Encrypt network data', B: 'Filter network traffic based on security rules', C: 'Detect viruses in files', D: 'Speed up internet connection' } },
|
|
74
|
+
{ number: 8, text: 'Which type of malware disguises itself as legitimate software?', category: 'Malware',
|
|
75
|
+
options: { A: 'Worm', B: 'Ransomware', C: 'Trojan', D: 'Adware' } },
|
|
76
|
+
{ number: 9, text: 'Which protocol provides secure communication on the web?', category: 'Network',
|
|
77
|
+
options: { A: 'HTTP', B: 'FTP', C: 'HTTPS', D: 'SMTP' } },
|
|
78
|
+
{ number: 10, text: 'What is a cryptographic hash?', category: 'Cryptography',
|
|
51
79
|
options: { A: 'A reversible encryption key', B: 'A one-way function producing a fixed-size digest', C: 'An authentication protocol', D: 'A type of digital signature' } },
|
|
52
|
-
{ number:
|
|
53
|
-
options: { A: '
|
|
54
|
-
{ number:
|
|
55
|
-
options: { A: '
|
|
56
|
-
{ number:
|
|
57
|
-
options: { A: '
|
|
58
|
-
{ number:
|
|
59
|
-
options: { A: '
|
|
60
|
-
{ number:
|
|
61
|
-
options: { A: '
|
|
62
|
-
{ number:
|
|
80
|
+
{ number: 11, text: 'Which tool is used for binary analysis?', category: 'Reverse Engineering',
|
|
81
|
+
options: { A: 'Nmap', B: 'SQLMap', C: 'Ghidra', D: 'Nikto' } },
|
|
82
|
+
{ number: 12, text: 'Which attack manipulates DNS requests to redirect traffic?', category: 'Network',
|
|
83
|
+
options: { A: 'Phishing', B: 'DNS Spoofing', C: 'SQL Injection', D: 'Brute Force' } },
|
|
84
|
+
{ number: 13, text: 'What is the standard port for SSH?', category: 'Network',
|
|
85
|
+
options: { A: '21', B: '22', C: '80', D: '443' } },
|
|
86
|
+
{ number: 14, text: 'What is two-factor authentication (2FA)?', category: 'Authentication',
|
|
87
|
+
options: { A: 'Using two different passwords', B: 'Verifying identity with two distinct types of credentials', C: 'Encrypting data twice', D: 'Connecting through two networks' } },
|
|
88
|
+
{ number: 15, text: 'Which Linux command shows open ports on a system?', category: 'Linux',
|
|
89
|
+
options: { A: 'ls -la', B: 'netstat -tulpn', C: 'chmod 777', D: 'cat /etc/passwd' } },
|
|
90
|
+
{ number: 16, text: 'What is a Man-in-the-Middle (MitM) attack?', category: 'Network',
|
|
91
|
+
options: { A: 'Accessing a server without authorization', B: 'Guessing passwords by brute force', C: 'Sending multiple requests to overload a server', D: 'Intercepting and modifying communications between two parties' } },
|
|
92
|
+
{ number: 17, text: 'Which of these is a hash algorithm?', category: 'Cryptography',
|
|
93
|
+
options: { A: 'AES-256', B: 'Diffie-Hellman', C: 'RSA-2048', D: 'SHA-256' } },
|
|
94
|
+
{ number: 18, text: 'What is the principle of least privilege?', category: 'Security',
|
|
95
|
+
options: { A: 'Give root access to all users', B: 'Use the shortest password possible', C: 'Grant only the permissions necessary to perform a task', D: 'Disable all firewalls' } },
|
|
96
|
+
{ number: 19, text: 'Which tool is commonly used for port scanning?', category: 'Network',
|
|
97
|
+
options: { A: 'Wireshark', B: 'Metasploit', C: 'Nmap', D: 'Hashcat' } },
|
|
98
|
+
{ number: 20, text: 'What is ransomware?', category: 'Malware',
|
|
99
|
+
options: { A: 'Software that shows unwanted ads', B: 'Software that encrypts files and demands payment to decrypt', C: 'Software that records keystrokes', D: 'Software that replicates across networks' } },
|
|
100
|
+
{ number: 21, text: 'What is the difference between symmetric and asymmetric encryption?', category: 'Cryptography',
|
|
101
|
+
options: { A: 'Symmetric uses the same key to encrypt and decrypt; asymmetric uses two different keys', B: 'Symmetric is slower than asymmetric', C: 'Asymmetric only works with small files', D: 'There is no significant difference' } },
|
|
102
|
+
{ number: 22, text: 'Which vulnerability allows arbitrary code execution on a web server?', category: 'Web Security',
|
|
103
|
+
options: { A: 'CSRF', B: 'Open Redirect', C: 'Clickjacking', D: 'Remote Code Execution (RCE)' } },
|
|
104
|
+
{ number: 23, text: 'What is OWASP?', category: 'Security',
|
|
105
|
+
options: { A: 'A security operating system', B: 'A type of firewall', C: 'An organization that publishes web security standards and guides', D: 'A programming language for security' } },
|
|
106
|
+
{ number: 24, text: 'Which Linux command changes file permissions?', category: 'Linux',
|
|
107
|
+
options: { A: 'chown', B: 'chmod', C: 'chgrp', D: 'passwd' } },
|
|
108
|
+
{ number: 25, text: 'What is an SSL/TLS certificate?', category: 'Cryptography',
|
|
109
|
+
options: { A: 'A digital document that verifies a website identity', B: 'A file containing malware', C: 'A private key for SSH', D: 'A type of encrypted database' } },
|
|
110
|
+
{ number: 26, text: 'Which of the following is a social engineering attack?', category: 'Security',
|
|
111
|
+
options: { A: 'Buffer overflow', B: 'Phishing', C: 'SQL Injection', D: 'Port scanning' } },
|
|
112
|
+
{ number: 27, text: 'What does the Linux command "grep" do?', category: 'Linux',
|
|
113
|
+
options: { A: 'Compresses files', B: 'Configures the network', C: 'Shows active processes', D: 'Searches for text patterns in files' } },
|
|
114
|
+
{ number: 28, text: 'What is a VPN?', category: 'Network',
|
|
63
115
|
options: { A: 'A type of virus', B: 'A file transfer protocol', C: 'A virtual private network that encrypts internet traffic', D: 'A vulnerability scanner' } },
|
|
64
|
-
{ number:
|
|
65
|
-
options: { A: '
|
|
116
|
+
{ number: 29, text: 'What is CSRF (Cross-Site Request Forgery)?', category: 'Web Security',
|
|
117
|
+
options: { A: 'An attack that forces a user browser to perform unauthorized actions', B: 'A data encryption method', C: 'A type of network scanner', D: 'A file compression technique' } },
|
|
118
|
+
{ number: 30, text: 'What is the best practice for storing passwords in a database?', category: 'Security',
|
|
119
|
+
options: { A: 'Plain text', B: 'Hashed with salt', C: 'Encrypted with AES', D: 'Encoded in Base64' } },
|
|
66
120
|
];
|
|
121
|
+
/** Fisher-Yates shuffle, returns a new array. */
|
|
122
|
+
function shuffle(arr) {
|
|
123
|
+
const out = [...arr];
|
|
124
|
+
for (let i = out.length - 1; i > 0; i--) {
|
|
125
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
126
|
+
[out[i], out[j]] = [out[j], out[i]];
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
/** Shuffle the four options within a single question; recompute the answer letter. */
|
|
131
|
+
function shuffleQuestionOptions(q) {
|
|
132
|
+
if (!q.answer)
|
|
133
|
+
return q;
|
|
134
|
+
const correctText = q.options[q.answer];
|
|
135
|
+
const keys = ['A', 'B', 'C', 'D'];
|
|
136
|
+
const values = shuffle(keys.map((k) => q.options[k]));
|
|
137
|
+
const newOptions = { A: values[0], B: values[1], C: values[2], D: values[3] };
|
|
138
|
+
const newAnswer = keys.find((k) => newOptions[k] === correctText);
|
|
139
|
+
return { ...q, options: newOptions, answer: newAnswer };
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Pick `n` random questions from the pool, shuffle each question's options,
|
|
143
|
+
* renumber 1..n. Returns fully self-contained questions with answer + explanation
|
|
144
|
+
* populated, so downstream code does not need to look up DEMO_ANSWERS by number.
|
|
145
|
+
*/
|
|
146
|
+
export function pickDemoQuestions(n = DEMO_PICK_SIZE) {
|
|
147
|
+
const translatedPool = getLocalizedDemoQuestions();
|
|
148
|
+
const explanations = getLocalizedExplanations();
|
|
149
|
+
// Enrich pool with answer + explanation keyed by ORIGINAL number
|
|
150
|
+
const enriched = translatedPool.map((q) => ({
|
|
151
|
+
...q,
|
|
152
|
+
answer: DEMO_ANSWERS[q.number],
|
|
153
|
+
explanation: explanations[q.number],
|
|
154
|
+
}));
|
|
155
|
+
// Shuffle pool, pick n, shuffle each question's options, renumber 1..n
|
|
156
|
+
const picked = shuffle(enriched).slice(0, n);
|
|
157
|
+
return picked.map((q, i) => ({
|
|
158
|
+
...shuffleQuestionOptions(q),
|
|
159
|
+
number: i + 1,
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
67
162
|
/**
|
|
68
163
|
* Get localized explanations for demo questions.
|
|
69
164
|
* Reads from translations/<lang>/demo-explanations.json.
|
|
70
|
-
* Falls back to English
|
|
165
|
+
* Falls back to English for any missing entries.
|
|
71
166
|
*/
|
|
72
167
|
export function getLocalizedExplanations() {
|
|
73
168
|
const lang = getConfig().language;
|
|
@@ -78,8 +173,10 @@ export function getLocalizedExplanations() {
|
|
|
78
173
|
return DEMO_EXPLANATIONS;
|
|
79
174
|
try {
|
|
80
175
|
const data = JSON.parse(readFileSync(path, 'utf-8'));
|
|
81
|
-
if (data && typeof data === 'object')
|
|
82
|
-
|
|
176
|
+
if (data && typeof data === 'object') {
|
|
177
|
+
// Merge: translated entries override English, missing entries stay English
|
|
178
|
+
return { ...DEMO_EXPLANATIONS, ...data };
|
|
179
|
+
}
|
|
83
180
|
}
|
|
84
181
|
catch { }
|
|
85
182
|
return DEMO_EXPLANATIONS;
|
|
@@ -87,7 +184,7 @@ export function getLocalizedExplanations() {
|
|
|
87
184
|
/**
|
|
88
185
|
* Get demo questions translated to user's language.
|
|
89
186
|
* Reads from translations/<lang>/demo.json (bundled static file).
|
|
90
|
-
*
|
|
187
|
+
* Per-question fallback: missing entries use the English source.
|
|
91
188
|
*/
|
|
92
189
|
export function getLocalizedDemoQuestions() {
|
|
93
190
|
const lang = getConfig().language;
|
|
@@ -98,8 +195,13 @@ export function getLocalizedDemoQuestions() {
|
|
|
98
195
|
return DEMO_QUESTIONS;
|
|
99
196
|
try {
|
|
100
197
|
const data = JSON.parse(readFileSync(path, 'utf-8'));
|
|
101
|
-
if (Array.isArray(data)
|
|
102
|
-
return
|
|
198
|
+
if (!Array.isArray(data))
|
|
199
|
+
return DEMO_QUESTIONS;
|
|
200
|
+
// Per-question merge: use translated where available, English fallback otherwise.
|
|
201
|
+
return DEMO_QUESTIONS.map((eng) => {
|
|
202
|
+
const tr = data.find((d) => d.number === eng.number);
|
|
203
|
+
return tr && tr.options ? tr : eng;
|
|
204
|
+
});
|
|
103
205
|
}
|
|
104
206
|
catch { }
|
|
105
207
|
return DEMO_QUESTIONS;
|
package/dist/repl.js
CHANGED
|
@@ -56,53 +56,47 @@ export async function startRepl(program, resumeMode) {
|
|
|
56
56
|
default: savedMode || 'selection',
|
|
57
57
|
});
|
|
58
58
|
if (selected === 'about') {
|
|
59
|
+
console.clear();
|
|
59
60
|
console.log();
|
|
60
61
|
console.log(chalk.cyan(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
61
|
-
console.log();
|
|
62
|
-
console.log(chalk.
|
|
63
|
-
console.log(chalk.
|
|
64
|
-
console.log(chalk.bold.white(' โโโโโโ โโโ โโโโโโโโโโโ'));
|
|
65
|
-
console.log(chalk.bold.white(' โโโโโโ โโโ โโโโโโโโโโโ'));
|
|
66
|
-
console.log(chalk.bold.white(' โโโโโโโโโโโโโโโโโโโโโโโ โโโ'));
|
|
67
|
-
console.log(chalk.bold.white(' โโโ โโโโโโโ โโโโโโโ โโโ โโโ'));
|
|
68
|
-
console.log();
|
|
69
|
-
console.log(chalk.bold.yellow(' The World\'s First'));
|
|
70
|
-
console.log(chalk.bold.white(' AI-Native CLI Operating System for'));
|
|
71
|
-
console.log(chalk.bold.white(' Cybersecurity & AI Security Competition'));
|
|
72
|
-
console.log(chalk.bold.white(' and Olympiad for K-12'));
|
|
73
|
-
console.log();
|
|
74
|
-
console.log(chalk.white(' One terminal. 15 languages. 15,000 concurrent participants.'));
|
|
75
|
-
console.log(chalk.white(' 109 pre-configured CTF tools. Zero browser required.'));
|
|
76
|
-
console.log();
|
|
77
|
-
console.log(chalk.cyan(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
62
|
+
console.log(chalk.bold.yellow(' ICOA') + chalk.white(' โ AI-Native CLI OS for Cyber & AI Security'));
|
|
63
|
+
console.log(chalk.gray(' Olympiad & Competition ยท K-12 to University'));
|
|
64
|
+
console.log(chalk.cyan(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
78
65
|
console.log();
|
|
79
66
|
console.log(chalk.bold.white(' What Makes ICOA Different'));
|
|
80
67
|
console.log(chalk.gray(' ยท AI-native AI teammate, AI adversary, AI translation'));
|
|
81
68
|
console.log(chalk.gray(' ยท CLI OS Complete competition environment in terminal'));
|
|
82
69
|
console.log(chalk.gray(' ยท 109 tools pwntools, z3, gdb, nmap... pre-configured'));
|
|
83
|
-
console.log(chalk.gray(' ยท Global scale 15,000+ concurrent exams
|
|
84
|
-
console.log(chalk.gray(' ยท 15 languages Real-time AI translation for all content'));
|
|
70
|
+
console.log(chalk.gray(' ยท Global scale 15,000+ concurrent exams ยท 15 languages'));
|
|
85
71
|
console.log();
|
|
86
72
|
console.log(chalk.bold.white(' Competition Format'));
|
|
87
|
-
console.log(chalk.green.bold('
|
|
88
|
-
console.log(chalk.red.bold('
|
|
89
|
-
console.log();
|
|
90
|
-
console.log(chalk.white(' Sydney, Australia') + chalk.gray(' ยท Jun 27 - Jul 2, 2026'));
|
|
91
|
-
console.log(chalk.gray(' 40+ countries and regions represented'));
|
|
73
|
+
console.log(' ' + chalk.green.bold('AI4CTF') + chalk.gray(' [Day 1] AI as teammate โ 5hr jeopardy CTF'));
|
|
74
|
+
console.log(' ' + chalk.red.bold('CTF4AI') + chalk.gray(' [Day 2] Challenge AI โ adversarial ML, red-team'));
|
|
92
75
|
console.log();
|
|
93
|
-
console.log(chalk.
|
|
94
|
-
console.log();
|
|
95
|
-
console.log(chalk.bold.white(' Organized by'));
|
|
96
|
-
console.log(chalk.gray(' ASRA โ Australia STEM and Robotics Advancement Association Inc'));
|
|
97
|
-
console.log(chalk.gray(' ICO Foundation Inc (Australia)'));
|
|
98
|
-
console.log();
|
|
99
|
-
console.log(chalk.bold.white(' Contact & Accreditation'));
|
|
100
|
-
console.log(chalk.cyan(' australia@icoa2026.au'));
|
|
101
|
-
console.log(chalk.cyan(' accreditation@icoa2026.au'));
|
|
102
|
-
console.log(chalk.cyan.underline(' https://icoa2026.au'));
|
|
76
|
+
console.log(chalk.white(' Sydney, Australia') + chalk.gray(' ยท Jun 27 - Jul 2, 2026 ยท 40+ countries'));
|
|
103
77
|
console.log();
|
|
78
|
+
console.log(chalk.bold.white(' Organized by') + chalk.gray(' ASRA (Australia) ยท ICO Foundation Inc'));
|
|
79
|
+
console.log(chalk.bold.white(' Contact ') + chalk.cyan(' australia@icoa2026.au ยท accreditation@icoa2026.au'));
|
|
80
|
+
console.log(chalk.bold.white(' Website ') + chalk.cyan.underline(' https://icoa2026.au'));
|
|
104
81
|
console.log(chalk.cyan(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
105
82
|
console.log();
|
|
83
|
+
console.log(chalk.gray(' Press ') + chalk.yellow('Enter') + chalk.gray(' to return...'));
|
|
84
|
+
await new Promise((resolve) => {
|
|
85
|
+
const onKey = (_chunk) => {
|
|
86
|
+
process.stdin.removeListener('data', onKey);
|
|
87
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
88
|
+
process.stdin.setRawMode(false);
|
|
89
|
+
}
|
|
90
|
+
process.stdin.pause();
|
|
91
|
+
resolve();
|
|
92
|
+
};
|
|
93
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
94
|
+
process.stdin.setRawMode(true);
|
|
95
|
+
}
|
|
96
|
+
process.stdin.resume();
|
|
97
|
+
process.stdin.once('data', onKey);
|
|
98
|
+
});
|
|
99
|
+
console.clear();
|
|
106
100
|
// Loop back to mode selection
|
|
107
101
|
continue;
|
|
108
102
|
}
|
package/dist/types/index.d.ts
CHANGED