icoa-cli 2.19.94 → 2.19.96

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/index.js CHANGED
@@ -117,10 +117,35 @@ program
117
117
  // wondering why "哪个命令..." appears as "????..." on their machine.
118
118
  const envLang = process.env.LANG || process.env.LC_ALL || process.env.LC_CTYPE || '';
119
119
  if (!/UTF-?8/i.test(envLang)) {
120
- console.log(chalk.yellow('⚠ Your terminal locale is not UTF-8 (LANG=' + (envLang || '(unset)') + ').'));
121
- console.log(chalk.gray(' Non-English text and box characters may display as "?" or garbled glyphs.'));
122
- console.log(chalk.gray(' Fix: ') + chalk.cyan('export LANG=en_US.UTF-8') + chalk.gray(' (or your locale, e.g. ') + chalk.cyan('zh_CN.UTF-8') + chalk.gray(', ') + chalk.cyan('uk_UA.UTF-8') + chalk.gray(')'));
123
- console.log();
120
+ if (process.platform === 'win32') {
121
+ // Windows cmd.exe default code page (437 US, 936 CN, etc.) mangles
122
+ // non-Latin script. Fix is `chcp 65001`, not Unix-style `export LANG`.
123
+ // Skip the warning if the code page is already 65001 (PowerShell +
124
+ // Windows Terminal often set it automatically).
125
+ let codepage = '';
126
+ try {
127
+ const { execFileSync } = await import('node:child_process');
128
+ codepage = execFileSync('chcp.com', [], {
129
+ encoding: 'utf-8',
130
+ timeout: 1500,
131
+ stdio: ['ignore', 'pipe', 'ignore'],
132
+ }).trim();
133
+ }
134
+ catch { /* chcp.com unavailable or timed out */ }
135
+ if (!codepage.includes('65001')) {
136
+ console.log(chalk.yellow('⚠ Windows terminal is not using UTF-8 (current: ' + (codepage || 'unknown') + ').'));
137
+ console.log(chalk.gray(' Non-English text (Ukrainian, Chinese, Japanese, etc.) may show as "?" or garbled glyphs.'));
138
+ console.log(chalk.gray(' Fix (run before ') + chalk.cyan('icoa') + chalk.gray('): ') + chalk.cyan('chcp 65001'));
139
+ console.log(chalk.gray(' Or stay in English inside the CLI: ') + chalk.cyan('lang en'));
140
+ console.log();
141
+ }
142
+ }
143
+ else {
144
+ console.log(chalk.yellow('⚠ Your terminal locale is not UTF-8 (LANG=' + (envLang || '(unset)') + ').'));
145
+ console.log(chalk.gray(' Non-English text and box characters may display as "?" or garbled glyphs.'));
146
+ console.log(chalk.gray(' Fix: ') + chalk.cyan('export LANG=en_US.UTF-8') + chalk.gray(' (or your locale, e.g. ') + chalk.cyan('zh_CN.UTF-8') + chalk.gray(', ') + chalk.cyan('uk_UA.UTF-8') + chalk.gray(')'));
147
+ console.log();
148
+ }
124
149
  }
125
150
  console.log(BANNER);
126
151
  // If running interactively (no extra args or --resume), start REPL
@@ -181,4 +206,21 @@ program
181
206
  console.log();
182
207
  }
183
208
  });
209
+ // Proctor-only escape hatch: `ICOA_RESET_STATE=1 icoa` wipes any abandoned
210
+ // exam state *before* any subcommand or REPL runs. Fires for ALL invocations
211
+ // (including `icoa exam <token>`) so proctors have a one-liner recovery that
212
+ // works regardless of which subcommand the student would run next.
213
+ // The token itself is untouched server-side.
214
+ if (process.env.ICOA_RESET_STATE === '1') {
215
+ try {
216
+ const { clearExamState } = await import('./lib/exam-state.js');
217
+ clearExamState();
218
+ console.log(chalk.yellow('⚠ ICOA_RESET_STATE=1 — local exam state wiped.'));
219
+ console.log(chalk.gray(' (Token NOT revoked server-side. Re-enter a fresh token with `exam <token>`.)'));
220
+ console.log();
221
+ }
222
+ catch (e) {
223
+ console.log(chalk.red('⚠ ICOA_RESET_STATE: could not clear state — ') + chalk.gray(String(e)));
224
+ }
225
+ }
184
226
  program.parse();
package/dist/repl.js CHANGED
@@ -106,17 +106,29 @@ function checkPython() {
106
106
  // Probe python3.12 first — macOS Homebrew installs python@3.12 alongside a
107
107
  // newer default python3, and we don't want to flag a 3.12-ready machine just
108
108
  // because the default alias moved to 3.13.
109
+ // On Windows (cmd/PowerShell), the binary is `python` not `python3`, and the
110
+ // Python launcher `py -3` is also common. Include these so Windows K-12
111
+ // students don't get a spurious "Python missing" warning when Python IS
112
+ // installed. We also stdio: ignore stderr so cmd.exe's "not recognized as
113
+ // internal or external command" error doesn't leak to the user.
109
114
  const probes = [
110
115
  'python3.12 --version',
111
116
  '/opt/homebrew/opt/python@3.12/bin/python3.12 --version',
112
117
  '/usr/local/opt/python@3.12/bin/python3.12 --version',
113
118
  'python3 --version',
119
+ 'python --version', // Windows default name
120
+ 'py -3.12 --version', // Windows Python Launcher
121
+ 'py -3 --version', // Windows Python Launcher (any 3.x)
114
122
  ];
115
123
  let lastVersion = '';
116
124
  let lastStatus = 'missing';
117
125
  for (const cmd of probes) {
118
126
  try {
119
- const out = execSyncFn(cmd, { encoding: 'utf-8', timeout: 2000 }).trim();
127
+ const out = execSyncFn(cmd, {
128
+ encoding: 'utf-8',
129
+ timeout: 2000,
130
+ stdio: ['ignore', 'pipe', 'ignore'],
131
+ }).trim();
120
132
  const version = out.replace('Python ', '');
121
133
  const [maj, min] = version.split('.').map(Number);
122
134
  if (maj === 3 && min === 12)
@@ -905,6 +917,24 @@ export async function startRepl(program, resumeMode) {
905
917
  .replace(/^python3?\s/, `${py12} `)
906
918
  .replace(/^(python3|python)$/, py12);
907
919
  }
920
+ else if (process.platform === 'win32') {
921
+ // Windows: the binary is `python` (not `python3`). Python Launcher `py -3`
922
+ // is also common. Rewrite `python3 xyz` → `python xyz` or `py -3 xyz` so
923
+ // students who followed Unix-oriented practical-question hints don't get
924
+ // "'python3' is not recognized". Prefer `py -3` if the launcher exists
925
+ // (handles multi-version installs); fall back to plain `python`.
926
+ const pyCmd = (() => {
927
+ try {
928
+ execSyncFn('py -3 --version', { stdio: ['ignore', 'ignore', 'ignore'], timeout: 1500 });
929
+ return 'py -3';
930
+ }
931
+ catch { }
932
+ return 'python';
933
+ })();
934
+ resolvedInput = resolvedInput
935
+ .replace(/^python3?(\.\d+)?\s/, `${pyCmd} `)
936
+ .replace(/^python3?(\.\d+)?$/, pyCmd);
937
+ }
908
938
  else {
909
939
  // Linux/WSL: python → python3 (or python3.12 if available)
910
940
  const py12 = (() => { try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "2.19.94",
3
+ "version": "2.19.96",
4
4
  "description": "ICOA CLI — The world's first CLI-native CTF competition terminal",
5
5
  "type": "module",
6
6
  "bin": {