icoa-cli 2.19.96 → 2.19.97

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.
@@ -1658,7 +1658,16 @@ export function registerExamCommand(program) {
1658
1658
  return;
1659
1659
  }
1660
1660
  // Gate: require exam setup
1661
- if (!isExamSetupComplete()) {
1661
+ // v2.19.97 — Windows cmd users bypass the exam-setup gate entirely.
1662
+ // Their recommended path is C paper (MCQ only, no Python / no Unix tools),
1663
+ // so forcing them through `exam setup` (which installs pip packages) would
1664
+ // be both unnecessary and likely to fail. Server enforces token→exam_id
1665
+ // binding (1:1 FK), so if they use a B/A token on cmd they'll still get
1666
+ // routed to the correct exam; but the missing tools may cost them points
1667
+ // on Q31-36. That's the trade-off they opted into.
1668
+ const { isNativeWindowsCmd: _isNativeWindowsCmd } = await import('../lib/platform.js');
1669
+ const cmdPath = _isNativeWindowsCmd();
1670
+ if (!cmdPath && !isExamSetupComplete()) {
1662
1671
  console.log();
1663
1672
  printWarning('Pre-exam setup required before entering a token.');
1664
1673
  console.log(chalk.gray(' → ') + chalk.bold.cyan('exam setup'));
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Platform detection for Windows cmd.exe K-12 entry path.
3
+ *
4
+ * Context: Windows middle-school students (12-14 y/o) are the expected largest
5
+ * cohort for ICOA 2026. Observed in-field: 4-5 students blocked by WSL install
6
+ * complexity. Their viable path is Windows native cmd + Node.js, which handles
7
+ * MCQ + AI chat perfectly but not Q31-36 (Unix grep / strings / pwntools).
8
+ *
9
+ * Decision (v2.19.97): C paper (ua-2026-c) is the cmd entry funnel — 30 MCQ
10
+ * only, 45 min, 70 pts, no tools. Non-cmd users get B paper (full 150 pts).
11
+ *
12
+ * This module is the single source of truth for "should we show exam setup?"
13
+ * and "should we route this user to C paper?". Used by src/repl.ts menu
14
+ * rendering and src/commands/exam.ts pre-token guards.
15
+ */
16
+ export interface PlatformInfo {
17
+ platform: NodeJS.Platform;
18
+ isWindowsNativeCmd: boolean;
19
+ isInWSL: boolean;
20
+ hasPython: boolean;
21
+ }
22
+ /**
23
+ * Heuristic: true when running inside a WSL distribution. Node.js reports
24
+ * `process.platform === 'linux'` in that case, but WSL sets specific env vars.
25
+ */
26
+ export declare function isInWSL(): boolean;
27
+ /**
28
+ * True when the user is on Windows native (cmd.exe or PowerShell), NOT a
29
+ * WSL distribution.
30
+ */
31
+ export declare function isNativeWindowsCmd(): boolean;
32
+ /**
33
+ * Probe whether any Python 3 binary is reachable. Silent — no stderr leaks
34
+ * to the user. Used to decide whether Q31-35 practical questions are even
35
+ * theoretically doable on this machine.
36
+ *
37
+ * Uses execFileSync (no shell) so probe failures don't leak cmd.exe's
38
+ * "not recognized as internal or external command" messages to stdout.
39
+ */
40
+ export declare function hasPython(): boolean;
41
+ export declare function getPlatformInfo(): PlatformInfo;
42
+ /**
43
+ * Should the Selection-mode menu show the `exam setup` row?
44
+ *
45
+ * Windows native cmd: NO. Setup installs pip packages for Q31-35 practical
46
+ * which don't apply on the C-paper (cmd-recommended) path. Showing setup
47
+ * sends them chasing Python installs that may fail for 12-year-olds.
48
+ *
49
+ * Everyone else (macOS, Linux, WSL): YES. They can reach B-paper and setup
50
+ * is a legit pre-step.
51
+ */
52
+ export declare function shouldShowExamSetup(): boolean;
53
+ /**
54
+ * Is this user recommended to take the C paper (MCQ-only) rather than B?
55
+ * Currently: Windows native cmd users. Future: might expand based on age
56
+ * or partner-country policy.
57
+ */
58
+ export declare function shouldRecommendCPaper(): boolean;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Platform detection for Windows cmd.exe K-12 entry path.
3
+ *
4
+ * Context: Windows middle-school students (12-14 y/o) are the expected largest
5
+ * cohort for ICOA 2026. Observed in-field: 4-5 students blocked by WSL install
6
+ * complexity. Their viable path is Windows native cmd + Node.js, which handles
7
+ * MCQ + AI chat perfectly but not Q31-36 (Unix grep / strings / pwntools).
8
+ *
9
+ * Decision (v2.19.97): C paper (ua-2026-c) is the cmd entry funnel — 30 MCQ
10
+ * only, 45 min, 70 pts, no tools. Non-cmd users get B paper (full 150 pts).
11
+ *
12
+ * This module is the single source of truth for "should we show exam setup?"
13
+ * and "should we route this user to C paper?". Used by src/repl.ts menu
14
+ * rendering and src/commands/exam.ts pre-token guards.
15
+ */
16
+ import { execFileSync } from 'node:child_process';
17
+ /**
18
+ * Heuristic: true when running inside a WSL distribution. Node.js reports
19
+ * `process.platform === 'linux'` in that case, but WSL sets specific env vars.
20
+ */
21
+ export function isInWSL() {
22
+ return !!(process.env.WSL_DISTRO_NAME || process.env.WSLENV);
23
+ }
24
+ /**
25
+ * True when the user is on Windows native (cmd.exe or PowerShell), NOT a
26
+ * WSL distribution.
27
+ */
28
+ export function isNativeWindowsCmd() {
29
+ return process.platform === 'win32' && !isInWSL();
30
+ }
31
+ /**
32
+ * Probe whether any Python 3 binary is reachable. Silent — no stderr leaks
33
+ * to the user. Used to decide whether Q31-35 practical questions are even
34
+ * theoretically doable on this machine.
35
+ *
36
+ * Uses execFileSync (no shell) so probe failures don't leak cmd.exe's
37
+ * "not recognized as internal or external command" messages to stdout.
38
+ */
39
+ export function hasPython() {
40
+ const probes = [
41
+ ['python3', ['--version']],
42
+ ['python', ['--version']],
43
+ ['py', ['-3', '--version']],
44
+ ];
45
+ for (const [bin, args] of probes) {
46
+ try {
47
+ execFileSync(bin, args, {
48
+ encoding: 'utf-8',
49
+ timeout: 1500,
50
+ stdio: ['ignore', 'pipe', 'ignore'],
51
+ });
52
+ return true;
53
+ }
54
+ catch { /* try next */ }
55
+ }
56
+ return false;
57
+ }
58
+ export function getPlatformInfo() {
59
+ return {
60
+ platform: process.platform,
61
+ isWindowsNativeCmd: isNativeWindowsCmd(),
62
+ isInWSL: isInWSL(),
63
+ hasPython: hasPython(),
64
+ };
65
+ }
66
+ /**
67
+ * Should the Selection-mode menu show the `exam setup` row?
68
+ *
69
+ * Windows native cmd: NO. Setup installs pip packages for Q31-35 practical
70
+ * which don't apply on the C-paper (cmd-recommended) path. Showing setup
71
+ * sends them chasing Python installs that may fail for 12-year-olds.
72
+ *
73
+ * Everyone else (macOS, Linux, WSL): YES. They can reach B-paper and setup
74
+ * is a legit pre-step.
75
+ */
76
+ export function shouldShowExamSetup() {
77
+ return !isNativeWindowsCmd();
78
+ }
79
+ /**
80
+ * Is this user recommended to take the C paper (MCQ-only) rather than B?
81
+ * Currently: Windows native cmd users. Future: might expand based on age
82
+ * or partner-country policy.
83
+ */
84
+ export function shouldRecommendCPaper() {
85
+ return isNativeWindowsCmd();
86
+ }
package/dist/repl.js CHANGED
@@ -10,6 +10,7 @@ import { getExamState, getRealExamState, getDemoState } from './lib/exam-state.j
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';
13
+ import { isNativeWindowsCmd } from './lib/platform.js';
13
14
  import { resetTerminalTheme } from './lib/theme.js';
14
15
  import { ensureSandbox, runInSandbox, isDockerAvailable } from './lib/sandbox.js';
15
16
  import { logCommand } from './lib/logger.js';
@@ -149,13 +150,21 @@ function printSelectionMenu() {
149
150
  const stats = getDemoStats();
150
151
  const setupDone = isExamSetupComplete();
151
152
  const demoLine = `Free practice — ${DEMO_PICK_SIZE} questions (from pool of ${DEMO_POOL_SIZE})`;
153
+ // v2.19.97 — Windows cmd K-12 entry path. Skip setup prompts + Python
154
+ // warnings for cmd users; they're routed to C paper (MCQ-only) which
155
+ // needs zero external tools.
156
+ const cmdMode = isNativeWindowsCmd();
152
157
  console.log();
153
158
  console.log(' ' + chalk.cyan.bold('[Selection Mode]'));
154
159
  console.log();
155
- // Proactive Python check: only warn when it actually matters (user past demo
156
- // and heading toward exam setup). Missing or version >= 3.13 shows yellow
157
- // note pointing at `env python`. Quiet when 3.10-3.12 are installed.
158
- if (stats.attempts > 0) {
160
+ if (cmdMode) {
161
+ console.log(chalk.gray(' Platform: ') + chalk.white('Windows cmd.exe') + chalk.gray(' routed to Paper C (MCQ-only, 45 min, 70 pts, zero extra tools)'));
162
+ console.log();
163
+ }
164
+ else if (stats.attempts > 0) {
165
+ // Proactive Python check only matters for non-cmd users heading toward
166
+ // exam setup. Missing or version >= 3.13 shows yellow note pointing at
167
+ // `env python`. Quiet when 3.10-3.12 are installed.
159
168
  const py = checkPython();
160
169
  if (py.status === 'missing') {
161
170
  console.log(chalk.yellow(' ⚠ Python not detected. For exam practical questions:'));
@@ -178,8 +187,9 @@ function printSelectionMenu() {
178
187
  console.log(chalk.white(' lang es') + chalk.gray(' Switch language (e.g. lang es, lang zh, lang fr)'));
179
188
  console.log(chalk.gray(' ─────────────────────────────────────────────'));
180
189
  }
181
- else if (!setupDone) {
190
+ else if (!setupDone && !cmdMode) {
182
191
  // State 1: demo done at least once, but exam environment not installed.
192
+ // Windows cmd users skip this state entirely — they don't need setup.
183
193
  const plural = stats.attempts === 1 ? 'attempt' : 'attempts';
184
194
  console.log(chalk.green(' ✓ Demo completed ') + chalk.gray(`(${stats.attempts} ${plural}${stats.bestPercentage > 0 ? ` · best ${stats.bestPercentage}%` : ''})`));
185
195
  console.log(chalk.yellow(' → Next: prepare your environment for the real exam.'));
@@ -192,13 +202,15 @@ function printSelectionMenu() {
192
202
  console.log(chalk.gray(' ─────────────────────────────────────────────'));
193
203
  }
194
204
  else {
195
- // State 2: fully prepared. Exam entry is the primary CTA list it FIRST
196
- // and make the token format + origin obvious. Students landing here have
197
- // already done demo; without a concrete example they stare at "<token>"
198
- // and don't know what to type or where the token comes from.
205
+ // State 2: ready to enter exam. For cmd users this is reached immediately
206
+ // (no setup gate); for others it requires exam setup completion.
199
207
  const plural = stats.attempts === 1 ? 'attempt' : 'attempts';
200
- console.log(chalk.green(' ✓ Demo completed ') + chalk.gray(`(${stats.attempts} ${plural})`));
201
- console.log(chalk.green(' ✓ Environment ready'));
208
+ if (stats.attempts > 0) {
209
+ console.log(chalk.green(' ✓ Demo completed ') + chalk.gray(`(${stats.attempts} ${plural})`));
210
+ }
211
+ if (!cmdMode) {
212
+ console.log(chalk.green(' ✓ Environment ready'));
213
+ }
202
214
  console.log(chalk.yellow(' → Enter your exam token to begin.'));
203
215
  console.log(chalk.gray(' (10-char code from your organizer, starts with your country code like ') + chalk.cyan('UA') + chalk.gray(' — case-insensitive)'));
204
216
  console.log();
@@ -208,7 +220,9 @@ function printSelectionMenu() {
208
220
  console.log(chalk.gray(' ─────────────────────────────────────────────'));
209
221
  console.log(chalk.gray(' Other commands:'));
210
222
  console.log(chalk.white(' demo') + chalk.gray(` ${demoLine}`));
211
- console.log(chalk.white(' exam setup') + chalk.gray(' Re-verify tool environment'));
223
+ if (!cmdMode) {
224
+ console.log(chalk.white(' exam setup') + chalk.gray(' Re-verify tool environment'));
225
+ }
212
226
  console.log(chalk.white(' lang') + chalk.gray(' List all supported languages'));
213
227
  console.log(chalk.white(' lang es') + chalk.gray(' Switch language (e.g. lang es, lang zh, lang fr)'));
214
228
  console.log(chalk.gray(' ─────────────────────────────────────────────'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "2.19.96",
3
+ "version": "2.19.97",
4
4
  "description": "ICOA CLI — The world's first CLI-native CTF competition terminal",
5
5
  "type": "module",
6
6
  "bin": {