icoa-cli 2.7.1 → 2.8.0

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.
@@ -85,13 +85,9 @@ function printQuestion(q, answer) {
85
85
  }
86
86
  console.log();
87
87
  // Navigation hint
88
- const nav = [];
89
- if (q.number > 1)
90
- nav.push('exam prev');
91
- if (q.number < total)
92
- nav.push('exam next');
93
- nav.push(`exam answer ${q.number} <A-D>`);
94
- console.log(chalk.gray(` ${nav.join(' · ')}`));
88
+ console.log(chalk.gray(' Type ') + chalk.white('A') + chalk.gray('/') + chalk.white('B') + chalk.gray('/') + chalk.white('C') + chalk.gray('/') + chalk.white('D') + chalk.gray(' to answer') +
89
+ (q.number > 1 ? chalk.gray(' · ') + chalk.white('prev') : '') +
90
+ (q.number < total ? chalk.gray(' · ') + chalk.white('next') : ''));
95
91
  }
96
92
  export function registerExamCommand(program) {
97
93
  const exam = program.command('exam').description('National selection exam');
@@ -153,8 +149,9 @@ export function registerExamCommand(program) {
153
149
  if (!client)
154
150
  return;
155
151
  const proceed = await confirm({
156
- message: 'Start the exam? The timer will begin immediately.',
152
+ message: chalk.white('Start the exam? The timer will begin immediately.'),
157
153
  default: true,
154
+ theme: { prefix: '', style: { message: (t) => t, defaultAnswer: (t) => chalk.green(t) } },
158
155
  });
159
156
  if (!proceed)
160
157
  return;
@@ -209,6 +206,8 @@ export function registerExamCommand(program) {
209
206
  printError(`Question ${n} not found (1-${state.questions.length}).`);
210
207
  return;
211
208
  }
209
+ state._lastQ = num;
210
+ saveExamState(state);
212
211
  printQuestion(q, state.answers[num]);
213
212
  }
214
213
  else {
@@ -369,7 +368,7 @@ export function registerExamCommand(program) {
369
368
  msg += chalk.yellow(` ${unanswered} unanswered.`);
370
369
  }
371
370
  msg += ' This cannot be undone.';
372
- const proceed = await confirm({ message: msg, default: false });
371
+ const proceed = await confirm({ message: chalk.white(msg), default: false, theme: { prefix: '', style: { message: (t) => t, defaultAnswer: (t) => chalk.green(t) } } });
373
372
  if (!proceed)
374
373
  return;
375
374
  console.log();
@@ -511,8 +510,9 @@ export function registerExamCommand(program) {
511
510
  console.log(chalk.gray(' └─────────────────────────────────────────────────┘'));
512
511
  console.log();
513
512
  const proceed = await confirm({
514
- message: 'Start demo exam now?',
513
+ message: chalk.white('Start demo exam now?'),
515
514
  default: true,
515
+ theme: { prefix: '', style: { message: (t) => t, defaultAnswer: (t) => chalk.green(t) } },
516
516
  });
517
517
  if (!proceed)
518
518
  return;
package/dist/repl.js CHANGED
@@ -5,6 +5,7 @@ import { isConnected, getConfig, saveConfig } from './lib/config.js';
5
5
  import { isActivated, activateToken, isFreeCommand, isDeviceMatch, recordExit, recordResume, isFirstRunOrUpgrade, markVersionSeen } from './lib/access.js';
6
6
  import { setReplMode } from './lib/ui.js';
7
7
  import { isChatActive, handleChatMessage } from './commands/ai4ctf.js';
8
+ import { getExamState } from './lib/exam-state.js';
8
9
  import { resetTerminalTheme } from './lib/theme.js';
9
10
  import { ensureSandbox, runInSandbox, isDockerAvailable } from './lib/sandbox.js';
10
11
  import { logCommand } from './lib/logger.js';
@@ -271,6 +272,37 @@ export async function startRepl(program, resumeMode) {
271
272
  rl.prompt();
272
273
  return;
273
274
  }
275
+ // ─── Quick exam answer shortcuts ───
276
+ // "A" / "B" / "C" / "D" → answer current question
277
+ // "2 C" / "5 A" → answer specific question
278
+ const examState = getExamState();
279
+ if (examState) {
280
+ const upper = input.toUpperCase().trim();
281
+ // Single letter: A, B, C, D → answer current question
282
+ if (/^[ABCD]$/.test(upper)) {
283
+ const currentQ = examState._lastQ || 1;
284
+ processing = true;
285
+ try {
286
+ await program.parseAsync(['node', 'icoa', 'exam', 'answer', String(currentQ), upper]);
287
+ }
288
+ catch { }
289
+ processing = false;
290
+ rl.prompt();
291
+ return;
292
+ }
293
+ // "N X" pattern: e.g. "2 C", "15 A"
294
+ const match = upper.match(/^(\d+)\s+([ABCD])$/);
295
+ if (match) {
296
+ processing = true;
297
+ try {
298
+ await program.parseAsync(['node', 'icoa', 'exam', 'answer', match[1], match[2]]);
299
+ }
300
+ catch { }
301
+ processing = false;
302
+ rl.prompt();
303
+ return;
304
+ }
305
+ }
274
306
  const cmd = input.split(/\s+/)[0].toLowerCase();
275
307
  // ─── Mode-based command filtering ───
276
308
  const selectionCommands = ['join', 'exam', 'demo', 'next', 'prev', 'setup', 'lang', 'ref', 'ctf'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "2.7.1",
3
+ "version": "2.8.0",
4
4
  "description": "ICOA CLI — The world's first CLI-native CTF competition terminal",
5
5
  "type": "module",
6
6
  "bin": {