icoa-cli 2.19.64 → 2.19.66

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.
@@ -610,7 +610,10 @@ export function registerAi4ctfCommand(program) {
610
610
  // token budget tracked in state.aiUsage.ai4ctf.
611
611
  const { getRealExamState } = await import('../lib/exam-state.js');
612
612
  const realExam = getRealExamState();
613
- if (realExam) {
613
+ // Only treat as real exam if state actually has the AI4CTF section (Q31-38).
614
+ // Stale/broken state with truncated questions falls back to demo behavior.
615
+ const hasAi4ctfSection = realExam && realExam.questions.some((qq) => qq.number >= 31 && qq.number <= 38);
616
+ if (realExam && hasAi4ctfSection) {
614
617
  const currentQ = realExam._lastQ || 1;
615
618
  if (currentQ < 31 || currentQ > 38) {
616
619
  console.log();
@@ -460,7 +460,10 @@ export function registerCtf4aiDemoCommand(program) {
460
460
  // tracked in state.aiUsage.ctf4ai.
461
461
  const { getRealExamState } = await import('../lib/exam-state.js');
462
462
  const realExam = getRealExamState();
463
- if (realExam) {
463
+ // Only treat as real exam if state actually has the CTF4AI section (Q39-40).
464
+ // Stale/broken state with truncated questions falls back to demo behavior.
465
+ const hasCtf4aiSection = realExam && realExam.questions.some((qq) => qq.number >= 39 && qq.number <= 40);
466
+ if (realExam && hasCtf4aiSection) {
464
467
  const currentQ = realExam._lastQ || 1;
465
468
  if (currentQ < 39) {
466
469
  console.log();
package/dist/index.js CHANGED
@@ -65,19 +65,53 @@ process.on('unhandledRejection', (reason) => {
65
65
  console.error(chalk.red('Error:'), msg);
66
66
  process.exit(1);
67
67
  });
68
+ // Pause for ms, skippable by any key press (so users can read the banner)
69
+ async function pauseWithSkip(ms) {
70
+ const stdin = process.stdin;
71
+ if (!stdin.isTTY || typeof stdin.setRawMode !== 'function') {
72
+ await new Promise((r) => setTimeout(r, ms));
73
+ return;
74
+ }
75
+ return new Promise((resolve) => {
76
+ let done = false;
77
+ const finish = () => {
78
+ if (done)
79
+ return;
80
+ done = true;
81
+ stdin.removeListener('data', onKey);
82
+ try {
83
+ stdin.setRawMode(false);
84
+ }
85
+ catch { }
86
+ stdin.pause();
87
+ resolve();
88
+ };
89
+ const onKey = () => finish();
90
+ stdin.setRawMode(true);
91
+ stdin.resume();
92
+ stdin.once('data', onKey);
93
+ console.log(chalk.gray(' (press any key to continue...)'));
94
+ setTimeout(finish, ms);
95
+ });
96
+ }
68
97
  const program = new Command();
69
98
  program
70
99
  .name('icoa')
71
100
  .version('1.2.0')
72
101
  .description('ICOA CLI — CLI-Native CTF Competition Terminal')
73
102
  .option('--resume', 'Resume previous session')
74
- .action((opts) => {
103
+ .action(async (opts) => {
75
104
  // Force hacker theme: black background + green text
76
105
  setTerminalTheme();
77
106
  checkForUpdates();
78
107
  console.log(BANNER);
79
108
  // If running interactively (no extra args or --resume), start REPL
80
109
  if (process.argv.length <= 2 || opts.resume) {
110
+ // Brief pause so users can read the banner + logo before mode selector appears
111
+ // Skip on --resume (user is coming back, doesn't need the intro)
112
+ if (!opts.resume) {
113
+ await pauseWithSkip(3000);
114
+ }
81
115
  startRepl(program, !!opts.resume);
82
116
  return;
83
117
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "2.19.64",
3
+ "version": "2.19.66",
4
4
  "description": "ICOA CLI — The world's first CLI-native CTF competition terminal",
5
5
  "type": "module",
6
6
  "bin": {