icoa-cli 2.5.3 → 2.6.1

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.
@@ -296,10 +296,43 @@ export function registerExamCommand(program) {
296
296
  const proceed = await confirm({ message: msg, default: false });
297
297
  if (!proceed)
298
298
  return;
299
+ console.log();
300
+ // Demo exam: grade locally
301
+ if (state.session.examId === 'demo-free') {
302
+ try {
303
+ const { DEMO_ANSWERS } = await import('../lib/demo-exam.js');
304
+ drawProgress(0, 'Grading...');
305
+ await sleep(300);
306
+ let score = 0;
307
+ for (const [qn, ans] of Object.entries(state.answers)) {
308
+ if (DEMO_ANSWERS[Number(qn)] === ans)
309
+ score++;
310
+ }
311
+ drawProgress(100, 'Complete!');
312
+ console.log();
313
+ clearExamState();
314
+ const percentage = Math.round(score / total * 100);
315
+ console.log();
316
+ printHeader('Demo Exam Result');
317
+ printKeyValue('Score', chalk.bold(`${score}/${total}`));
318
+ printKeyValue('Percentage', chalk.bold(`${percentage}%`));
319
+ printKeyValue('Status', percentage >= 60 ? chalk.green.bold('PASSED') : chalk.red.bold('NOT PASSED'));
320
+ console.log();
321
+ console.log(chalk.gray(' This was a free practice exam.'));
322
+ console.log(chalk.gray(' For the real exam, contact your national organizer.'));
323
+ console.log(chalk.white(' Ready? Use: join <url>'));
324
+ console.log();
325
+ }
326
+ catch (err) {
327
+ console.log();
328
+ printError(err.message);
329
+ }
330
+ return;
331
+ }
332
+ // Real exam: submit to server
299
333
  const client = requireExamConnection();
300
334
  if (!client)
301
335
  return;
302
- console.log();
303
336
  try {
304
337
  drawProgress(0, 'Uploading answers...');
305
338
  const result = await client.submitExam(state.session.examId, state.answers);
@@ -357,6 +390,63 @@ export function registerExamCommand(program) {
357
390
  printError(err.message);
358
391
  }
359
392
  });
393
+ // ─── exam demo ───
394
+ exam
395
+ .command('demo')
396
+ .description('Try a free practice exam (no account needed)')
397
+ .action(async () => {
398
+ logCommand('exam demo');
399
+ const { DEMO_SESSION, DEMO_QUESTIONS } = await import('../lib/demo-exam.js');
400
+ const existing = getExamState();
401
+ if (existing) {
402
+ if (existing.session.examId === 'demo-free') {
403
+ printInfo('Demo exam already in progress.');
404
+ printInfo('Use "exam q 1" to continue, or "exam submit" to finish.');
405
+ return;
406
+ }
407
+ printWarning(`Exam "${existing.session.examName}" is in progress.`);
408
+ printInfo('Submit it first: exam submit');
409
+ return;
410
+ }
411
+ console.log();
412
+ printHeader('ICOA Demo Exam — Free Practice');
413
+ console.log();
414
+ console.log(chalk.white(' This is a free practice exam to help you prepare.'));
415
+ console.log(chalk.white(' 30 questions · 30 minutes · English'));
416
+ console.log(chalk.white(' No account or login required.'));
417
+ console.log();
418
+ console.log(chalk.gray(' ┌─────────────────────────────────────────────────┐'));
419
+ console.log(chalk.gray(' │') + chalk.yellow(' For real exams, your proctor will provide: ') + chalk.gray('│'));
420
+ console.log(chalk.gray(' │') + chalk.white(' · Server URL ') + chalk.gray('│'));
421
+ console.log(chalk.gray(' │') + chalk.white(' · Username & Password ') + chalk.gray('│'));
422
+ console.log(chalk.gray(' │') + chalk.white(' · Exam ID ') + chalk.gray('│'));
423
+ console.log(chalk.gray(' └─────────────────────────────────────────────────┘'));
424
+ console.log();
425
+ const proceed = await confirm({
426
+ message: 'Start demo exam now?',
427
+ default: true,
428
+ });
429
+ if (!proceed)
430
+ return;
431
+ const session = { ...DEMO_SESSION, startedAt: new Date().toISOString() };
432
+ console.log();
433
+ drawProgress(0, 'Preparing questions...');
434
+ await sleep(200);
435
+ drawProgress(40, 'Setting up timer...');
436
+ await sleep(200);
437
+ drawProgress(80, 'Almost ready...');
438
+ await sleep(150);
439
+ drawProgress(100, 'Ready!');
440
+ console.log();
441
+ saveExamState({ session, questions: DEMO_QUESTIONS, answers: {} });
442
+ console.log();
443
+ printKeyValue('Questions', '30');
444
+ printKeyValue('Duration', '30 minutes');
445
+ printTimeRemaining();
446
+ // Show first question
447
+ printQuestion(DEMO_QUESTIONS[0]);
448
+ console.log(chalk.gray(' Commands: exam q <n> | exam answer <n> <A-D> | exam review | exam submit'));
449
+ });
360
450
  // Default action: show status or help
361
451
  exam.action(() => {
362
452
  logCommand('exam');
@@ -370,7 +460,12 @@ export function registerExamCommand(program) {
370
460
  console.log(chalk.gray(' exam q [n] | exam answer <n> <A-D> | exam review | exam submit'));
371
461
  }
372
462
  else {
373
- printInfo('No exam in progress. Use "exam list" to see available exams.');
463
+ console.log();
464
+ console.log(chalk.white(' No exam in progress.'));
465
+ console.log();
466
+ console.log(chalk.white(' exam demo ') + chalk.gray('Try free practice exam (no account needed)'));
467
+ console.log(chalk.white(' exam list ') + chalk.gray('View exams (requires login)'));
468
+ console.log();
374
469
  }
375
470
  });
376
471
  }
package/dist/index.js CHANGED
@@ -38,7 +38,7 @@ ${LINE}
38
38
  ${chalk.white('Sydney, Australia')} ${chalk.gray('Jun 27 - Jul 2, 2026')}
39
39
  ${chalk.cyan.underline('https://icoa2026.au')}
40
40
 
41
- ${chalk.gray('CLI-Native Competition Terminal v2.5.3')}
41
+ ${chalk.gray('CLI-Native Competition Terminal v2.6.1')}
42
42
 
43
43
  ${LINE}
44
44
  `;
@@ -0,0 +1,4 @@
1
+ import type { ExamQuestion, ExamSession } from '../types/index.js';
2
+ export declare const DEMO_SESSION: ExamSession;
3
+ export declare const DEMO_ANSWERS: Record<number, string>;
4
+ export declare const DEMO_QUESTIONS: ExamQuestion[];
@@ -0,0 +1,75 @@
1
+ export const DEMO_SESSION = {
2
+ examId: 'demo-free',
3
+ examName: 'ICOA Demo Exam — Free Practice',
4
+ startedAt: '',
5
+ durationMinutes: 30,
6
+ questionCount: 30,
7
+ country: 'ALL',
8
+ };
9
+ export const DEMO_ANSWERS = {
10
+ 1: 'B', 2: 'B', 3: 'C', 4: 'B', 5: 'C', 6: 'B', 7: 'B', 8: 'C', 9: 'C', 10: 'B',
11
+ 11: 'C', 12: 'B', 13: 'B', 14: 'B', 15: 'B', 16: 'B', 17: 'B', 18: 'B', 19: 'C', 20: 'B',
12
+ 21: 'A', 22: 'B', 23: 'B', 24: 'B', 25: 'B', 26: 'B', 27: 'B', 28: 'B', 29: 'A', 30: 'C',
13
+ };
14
+ export const DEMO_QUESTIONS = [
15
+ { number: 1, text: 'Which algorithm is NOT a symmetric cipher?', category: 'Cryptography',
16
+ options: { A: 'AES', B: 'RSA', C: 'DES', D: 'Blowfish' } },
17
+ { number: 2, text: 'What does SQL injection exploit?', category: 'Web Security',
18
+ options: { A: 'Buffer overflow in web server', B: 'Unsanitized user input in database queries', C: 'Weak encryption algorithms', D: 'Misconfigured firewall rules' } },
19
+ { number: 3, text: 'Which HTTP status code indicates "Forbidden"?', category: 'Web Security',
20
+ options: { A: '401', B: '404', C: '403', D: '500' } },
21
+ { number: 4, text: 'What is the primary purpose of a nonce in cryptography?', category: 'Cryptography',
22
+ options: { A: 'Encrypt data at rest', B: 'Prevent replay attacks', C: 'Generate random passwords', D: 'Compress data before encryption' } },
23
+ { number: 5, text: 'Which tool is commonly used for network packet capture?', category: 'Network',
24
+ options: { A: 'Burp Suite', B: 'Ghidra', C: 'Wireshark', D: 'John the Ripper' } },
25
+ { number: 6, text: 'What does XSS stand for in cybersecurity?', category: 'Web Security',
26
+ options: { A: 'Extended Security System', B: 'Cross-Site Scripting', C: 'XML Secure Socket', D: 'Cross-Server Sharing' } },
27
+ { number: 7, text: 'What is the primary function of a firewall?', category: 'Network',
28
+ options: { A: 'Encrypt network data', B: 'Filter network traffic based on security rules', C: 'Detect viruses in files', D: 'Speed up internet connection' } },
29
+ { number: 8, text: 'Which type of malware disguises itself as legitimate software?', category: 'Malware',
30
+ options: { A: 'Worm', B: 'Ransomware', C: 'Trojan', D: 'Adware' } },
31
+ { number: 9, text: 'Which protocol provides secure communication on the web?', category: 'Network',
32
+ options: { A: 'HTTP', B: 'FTP', C: 'HTTPS', D: 'SMTP' } },
33
+ { number: 10, text: 'What is a cryptographic hash?', category: 'Cryptography',
34
+ 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' } },
35
+ { number: 11, text: 'Which tool is used for binary analysis?', category: 'Reverse Engineering',
36
+ options: { A: 'Nmap', B: 'SQLMap', C: 'Ghidra', D: 'Nikto' } },
37
+ { number: 12, text: 'Which attack manipulates DNS requests to redirect traffic?', category: 'Network',
38
+ options: { A: 'Phishing', B: 'DNS Spoofing', C: 'SQL Injection', D: 'Brute Force' } },
39
+ { number: 13, text: 'What is the standard port for SSH?', category: 'Network',
40
+ options: { A: '21', B: '22', C: '80', D: '443' } },
41
+ { number: 14, text: 'What is two-factor authentication (2FA)?', category: 'Authentication',
42
+ options: { A: 'Using two different passwords', B: 'Verifying identity with two distinct types of credentials', C: 'Encrypting data twice', D: 'Connecting through two networks' } },
43
+ { number: 15, text: 'Which Linux command shows open ports on a system?', category: 'Linux',
44
+ options: { A: 'ls -la', B: 'netstat -tulpn', C: 'chmod 777', D: 'cat /etc/passwd' } },
45
+ { number: 16, text: 'What is a Man-in-the-Middle (MitM) attack?', category: 'Network',
46
+ options: { A: 'Accessing a server without authorization', B: 'Intercepting and modifying communications between two parties', C: 'Sending multiple requests to overload a server', D: 'Guessing passwords by brute force' } },
47
+ { number: 17, text: 'Which of these is a hash algorithm?', category: 'Cryptography',
48
+ options: { A: 'AES-256', B: 'SHA-256', C: 'RSA-2048', D: 'Diffie-Hellman' } },
49
+ { number: 18, text: 'What is the principle of least privilege?', category: 'Security',
50
+ options: { A: 'Give root access to all users', B: 'Grant only the permissions necessary to perform a task', C: 'Use the shortest password possible', D: 'Disable all firewalls' } },
51
+ { number: 19, text: 'Which tool is commonly used for port scanning?', category: 'Network',
52
+ options: { A: 'Wireshark', B: 'Metasploit', C: 'Nmap', D: 'Hashcat' } },
53
+ { number: 20, text: 'What is ransomware?', category: 'Malware',
54
+ 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' } },
55
+ { number: 21, text: 'What is the difference between symmetric and asymmetric encryption?', category: 'Cryptography',
56
+ 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' } },
57
+ { number: 22, text: 'Which vulnerability allows arbitrary code execution on a web server?', category: 'Web Security',
58
+ options: { A: 'CSRF', B: 'Remote Code Execution (RCE)', C: 'Clickjacking', D: 'Open Redirect' } },
59
+ { number: 23, text: 'What is OWASP?', category: 'Security',
60
+ options: { A: 'A security operating system', B: 'An organization that publishes web security standards and guides', C: 'A type of firewall', D: 'A programming language for security' } },
61
+ { number: 24, text: 'Which Linux command changes file permissions?', category: 'Linux',
62
+ options: { A: 'chown', B: 'chmod', C: 'chgrp', D: 'passwd' } },
63
+ { number: 25, text: 'What is an SSL/TLS certificate?', category: 'Cryptography',
64
+ options: { A: 'A file containing malware', B: 'A digital document that verifies a website\'s identity', C: 'A private key for SSH', D: 'A type of encrypted database' } },
65
+ { number: 26, text: 'Which of the following is a social engineering attack?', category: 'Security',
66
+ options: { A: 'Buffer overflow', B: 'Phishing', C: 'SQL Injection', D: 'Port scanning' } },
67
+ { number: 27, text: 'What does the Linux command "grep" do?', category: 'Linux',
68
+ options: { A: 'Compresses files', B: 'Searches for text patterns in files', C: 'Shows active processes', D: 'Configures the network' } },
69
+ { number: 28, text: 'What is a VPN?', category: 'Network',
70
+ options: { A: 'A type of virus', B: 'A virtual private network that encrypts internet traffic', C: 'A file transfer protocol', D: 'A vulnerability scanner' } },
71
+ { number: 29, text: 'What is CSRF (Cross-Site Request Forgery)?', category: 'Web Security',
72
+ options: { A: 'An attack that forces a user\'s browser to perform unauthorized actions', B: 'A data encryption method', C: 'A type of network scanner', D: 'A file compression technique' } },
73
+ { number: 30, text: 'What is the best practice for storing passwords in a database?', category: 'Security',
74
+ options: { A: 'Plain text', B: 'Encrypted with AES', C: 'Hashed with salt', D: 'Encoded in Base64' } },
75
+ ];
package/dist/repl.js CHANGED
@@ -128,17 +128,26 @@ export async function startRepl(program, resumeMode) {
128
128
  if (connected) {
129
129
  console.log(chalk.green(` Welcome back, ${config.userName}!`) + ' ' + modeLabel);
130
130
  console.log(chalk.gray(` Connected to ${config.ctfdUrl}`));
131
- console.log(chalk.gray(' Switch mode: setup'));
131
+ console.log();
132
+ console.log(chalk.gray(' ─────────────────────────────────'));
133
+ console.log(chalk.white(' exam list ') + chalk.gray('View available exams'));
134
+ console.log(chalk.white(' exam demo ') + chalk.gray('Free practice (no login)'));
135
+ console.log(chalk.white(' help ') + chalk.gray('All commands'));
136
+ console.log(chalk.gray(' ─────────────────────────────────'));
132
137
  console.log();
133
138
  }
134
139
  else {
135
- console.log(' ' + modeLabel + chalk.gray(' (switch via: setup)'));
140
+ console.log(' ' + modeLabel);
136
141
  console.log();
137
- console.log(chalk.gray(' Quick Start'));
138
- console.log(chalk.gray(' ─────────────'));
139
- console.log(chalk.white(' join <url> ') + chalk.gray('Connect to exam server'));
140
- console.log(chalk.white(' exam list ') + chalk.gray('View available exams'));
141
- console.log(chalk.white(' help ') + chalk.gray('All commands'));
142
+ console.log(chalk.gray(' ─────────────────────────────────────────────'));
143
+ console.log(chalk.white(' Welcome! Type a command to get started:'));
144
+ console.log();
145
+ console.log(chalk.bold.cyan(' demo') + chalk.gray(' Free practice exam (30 questions)'));
146
+ console.log(chalk.gray(' No account needed. Try it now!'));
147
+ console.log();
148
+ console.log(chalk.white(' join <url>') + chalk.gray(' Connect to real exam server'));
149
+ console.log(chalk.gray(' Your proctor will provide credentials.'));
150
+ console.log(chalk.gray(' ─────────────────────────────────────────────'));
142
151
  console.log();
143
152
  }
144
153
  }
@@ -264,8 +273,8 @@ export async function startRepl(program, resumeMode) {
264
273
  }
265
274
  const cmd = input.split(/\s+/)[0].toLowerCase();
266
275
  // ─── Mode-based command filtering ───
267
- const selectionCommands = ['join', 'exam', 'setup', 'lang', 'ref', 'ctf'];
268
- const organizerCommands = ['join', 'exam', 'setup', 'lang', 'ref', 'ctf'];
276
+ const selectionCommands = ['join', 'exam', 'demo', 'setup', 'lang', 'ref', 'ctf'];
277
+ const organizerCommands = ['join', 'exam', 'demo', 'setup', 'lang', 'ref', 'ctf'];
269
278
  if (mode === 'selection' && !selectionCommands.includes(cmd)) {
270
279
  console.log(chalk.gray(' Not available in Selection mode. Switch via: setup'));
271
280
  console.log();
@@ -294,7 +303,7 @@ export async function startRepl(program, resumeMode) {
294
303
  'scoreboard', 'sb', 'status', 'time', 'hint', 'hint-b', 'hint-c',
295
304
  'hint-budget', 'ref', 'shell', 'files', 'connect', 'note',
296
305
  'log', 'lang', 'setup', 'env', 'ai4ctf', 'model', 'ctf',
297
- 'exam',
306
+ 'exam', 'demo',
298
307
  ];
299
308
  if (!knownCommands.includes(cmd)) {
300
309
  // Block dangerous commands
@@ -428,6 +437,7 @@ function mapCommand(input) {
428
437
  const cmd = parts[0].toLowerCase();
429
438
  const rest = parts.slice(1);
430
439
  const ctfShortcuts = {
440
+ 'demo': ['exam', 'demo'],
431
441
  'join': ['ctf', 'join', ...rest],
432
442
  'activate': ['ctf', 'activate', ...rest],
433
443
  'challenges': ['ctf', 'challenges'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "2.5.3",
3
+ "version": "2.6.1",
4
4
  "description": "ICOA CLI — The world's first CLI-native CTF competition terminal",
5
5
  "type": "module",
6
6
  "bin": {