icoa-cli 1.9.1 → 2.0.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.
@@ -274,13 +274,19 @@ export function registerEnvCommand(program) {
274
274
  function showStatus() {
275
275
  console.log();
276
276
  const os = platform();
277
- const pm = os === 'darwin' ? 'brew' : os === 'linux' ? 'apt' : 'choco';
277
+ const pm = os === 'darwin' ? 'brew' : os === 'linux' ? 'apt' : 'winget';
278
278
  console.log(chalk.bold.white(' ICOA Competition Environment'));
279
279
  console.log(chalk.gray(' ─────────────────────────────────────────────'));
280
280
  console.log(chalk.gray(' OS: ') + chalk.white(getOsInfo()));
281
281
  console.log(chalk.gray(' Node: ') + chalk.white(getNodeInfo()));
282
282
  console.log(chalk.gray(' Package: ') + chalk.white(pm));
283
283
  console.log(chalk.gray(' Target: ') + chalk.white(`Python ${PYTHON_TARGET}.x`));
284
+ if (os === 'win32') {
285
+ console.log();
286
+ console.log(chalk.yellow(' Windows: recommend WSL (Ubuntu) for full CTF tool support'));
287
+ console.log(chalk.gray(' Install WSL: ') + chalk.white('wsl --install'));
288
+ console.log(chalk.gray(' Then run icoa inside WSL for 100% tool compatibility'));
289
+ }
284
290
  console.log(chalk.gray(' ─────────────────────────────────────────────'));
285
291
  // Python version check — use brew 3.12 on macOS
286
292
  const pyVer = getPythonMajorMinor();
@@ -316,7 +322,7 @@ function showStatus() {
316
322
  }
317
323
  }
318
324
  currentCategory = '';
319
- const pipExe = os === 'darwin' ? '/opt/homebrew/opt/python@3.12/bin/pip3.12' : 'pip3';
325
+ const pipExe = os === 'darwin' ? '/opt/homebrew/opt/python@3.12/bin/pip3.12' : os === 'win32' ? 'pip' : 'pip3';
320
326
  for (const lib of PYTHON_LIBS) {
321
327
  if (lib.category !== currentCategory) {
322
328
  currentCategory = lib.category;
@@ -324,12 +330,12 @@ function showStatus() {
324
330
  }
325
331
  const ok = isInstalled(lib.check);
326
332
  if (ok) {
327
- // Get actual installed version — try pip3.12, then pip3
333
+ // Get actual installed version
328
334
  let ver = '';
329
- for (const pip of [pipExe, 'pip3']) {
335
+ for (const pip of [pipExe, 'pip3', 'pip']) {
330
336
  try {
331
- const pkgName = lib.name.replace('python-magic', 'python_magic').replace('beautifulsoup4', 'beautifulsoup4');
332
- const out = execSync(`${pip} show ${pkgName} 2>/dev/null | grep Version`, { encoding: 'utf-8', timeout: 3000 });
337
+ const pkgName = lib.name.replace('python-magic', 'python_magic');
338
+ const out = execSync(`${pip} show ${pkgName}`, { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'ignore'] });
333
339
  const m = out.match(/Version:\s*(\S+)/);
334
340
  if (m) {
335
341
  ver = m[1];
@@ -381,13 +387,20 @@ async function installAll() {
381
387
  console.log(chalk.gray(' Set default: sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1'));
382
388
  }
383
389
  else {
384
- execSync('choco install -y python312', { stdio: 'inherit' });
390
+ // Windows: try winget first, then choco
391
+ try {
392
+ execSync('winget install Python.Python.3.12 --accept-package-agreements --accept-source-agreements', { stdio: 'inherit' });
393
+ }
394
+ catch {
395
+ execSync('choco install -y python312', { stdio: 'inherit' });
396
+ }
385
397
  console.log(chalk.green(' ✓ Python 3.12 installed'));
386
398
  }
387
399
  }
388
400
  catch {
389
401
  console.log(chalk.red(' ✗ Failed to install Python 3.12'));
390
- console.log(chalk.gray(` Manual install: ${os === 'darwin' ? 'brew install python@3.12' : os === 'linux' ? 'sudo apt install python3.12' : 'choco install python312'}`));
402
+ const hint = os === 'darwin' ? 'brew install python@3.12' : os === 'linux' ? 'sudo apt install python3.12' : 'winget install Python.Python.3.12';
403
+ console.log(chalk.gray(` Manual install: ${hint}`));
391
404
  }
392
405
  console.log();
393
406
  }
@@ -395,7 +408,14 @@ async function installAll() {
395
408
  console.log(chalk.green(` ✓ Python 3.12 (${getVersion('python3')})`));
396
409
  console.log();
397
410
  }
398
- // Install system tools via brew (macOS) / apt (Linux) / choco (Windows)
411
+ // Windows: recommend WSL instead of installing tools natively
412
+ if (os === 'win32') {
413
+ console.log(chalk.yellow(' Windows: Most CTF tools require Linux. Recommended:'));
414
+ console.log(chalk.white(' wsl --install'));
415
+ console.log(chalk.gray(' Then run icoa inside WSL Ubuntu for full tool support.'));
416
+ console.log();
417
+ }
418
+ // Install system tools via brew (macOS) / apt (Linux)
399
419
  const missingSystem = [];
400
420
  for (const tool of SYSTEM_TOOLS) {
401
421
  if (!isInstalled(tool.check)) {
@@ -404,8 +424,8 @@ async function installAll() {
404
424
  missingSystem.push(tool);
405
425
  }
406
426
  }
407
- if (missingSystem.length > 0) {
408
- const pmName = os === 'darwin' ? 'brew' : os === 'linux' ? 'apt' : 'choco';
427
+ if (missingSystem.length > 0 && os !== 'win32') {
428
+ const pmName = os === 'darwin' ? 'brew' : 'apt';
409
429
  console.log(chalk.bold.white(` System Tools via ${pmName} (${missingSystem.length} missing)`));
410
430
  console.log(chalk.gray(' ─────────────────────────────────────────────'));
411
431
  for (const tool of missingSystem) {
@@ -456,6 +476,9 @@ async function installAll() {
456
476
  }
457
477
  catch { /* fall back to pip3 */ }
458
478
  }
479
+ else if (os === 'win32') {
480
+ pipCmd = 'pip';
481
+ }
459
482
  // Pre-install system dependencies for packages that need them
460
483
  if (os === 'darwin') {
461
484
  console.log(chalk.gray(' Installing build dependencies...'));
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ ${LINE}
36
36
  ${chalk.white('Sydney, Australia')} ${chalk.gray('Jun 27 - Jul 2, 2026')}
37
37
  ${chalk.cyan.underline('https://icoa2026.au')}
38
38
 
39
- ${chalk.gray('CLI-Native Competition Terminal v1.9.1')}
39
+ ${chalk.gray('CLI-Native Competition Terminal v2.0.1')}
40
40
 
41
41
  ${LINE}
42
42
  `;
@@ -143,10 +143,8 @@ function getDeviceFingerprint() {
143
143
  }
144
144
  }
145
145
  else if (platform() === 'win32') {
146
- const out = execSync('wmic csproduct get uuid', { encoding: 'utf-8' });
147
- const lines = out.trim().split('\n');
148
- if (lines.length > 1)
149
- parts.push(lines[1].trim());
146
+ const out = execSync('powershell -Command "(Get-CimInstance Win32_ComputerSystemProduct).UUID"', { encoding: 'utf-8' });
147
+ parts.push(out.trim());
150
148
  }
151
149
  }
152
150
  catch {
package/dist/repl.js CHANGED
@@ -7,8 +7,27 @@ import { resetTerminalTheme } from './lib/theme.js';
7
7
  import { ensureSandbox, runInSandbox, isDockerAvailable } from './lib/sandbox.js';
8
8
  import { logCommand } from './lib/logger.js';
9
9
  import { startLogSync, stopLogSync } from './lib/log-sync.js';
10
+ import { existsSync, mkdirSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { homedir } from 'node:os';
13
+ // Competition workspace — all system commands restricted here
14
+ const WORKSPACE = join(homedir(), 'icoa-workspace');
15
+ function ensureWorkspace() {
16
+ if (!existsSync(WORKSPACE))
17
+ mkdirSync(WORKSPACE, { recursive: true });
18
+ return WORKSPACE;
19
+ }
20
+ // Blocked commands — security risk
21
+ const BLOCKED_COMMANDS = new Set([
22
+ 'sudo', 'su', 'doas', 'pkexec', // privilege escalation
23
+ 'brew', 'apt', 'apt-get', 'yum', 'choco', // package managers (use env setup)
24
+ 'npm', 'npx', 'pip', 'pip3', // package install (use env setup)
25
+ 'shutdown', 'reboot', 'halt', // system control
26
+ 'mkfs', 'fdisk', 'dd', // disk operations
27
+ 'iptables', 'ufw', // firewall
28
+ ]);
10
29
  const INTERCEPT = '__REPL_NO_EXIT__';
11
- const VERSION = '1.9.1';
30
+ const VERSION = '2.0.1';
12
31
  export async function startRepl(program, resumeMode) {
13
32
  const config = getConfig();
14
33
  const connected = isConnected();
@@ -48,7 +67,9 @@ export async function startRepl(program, resumeMode) {
48
67
  console.log();
49
68
  // Trigger env setup
50
69
  const { execSync: ex } = await import('node:child_process');
51
- ex('node ' + new URL('../index.js', import.meta.url).pathname + ' env setup', { stdio: 'inherit' });
70
+ const { fileURLToPath } = await import('node:url');
71
+ const indexPath = fileURLToPath(new URL('../index.js', import.meta.url));
72
+ ex(`node "${indexPath}" env setup`, { stdio: 'inherit' });
52
73
  }
53
74
  }
54
75
  catch {
@@ -83,7 +104,9 @@ export async function startRepl(program, resumeMode) {
83
104
  console.log();
84
105
  }
85
106
  else if (activated) {
107
+ ensureWorkspace();
86
108
  console.log(chalk.green(' Welcome, competitor! Ready to hack.'));
109
+ console.log(chalk.gray(` Workspace: ${WORKSPACE}`));
87
110
  console.log();
88
111
  console.log(chalk.gray(' Quick Start'));
89
112
  console.log(chalk.gray(' ─────────────'));
@@ -193,20 +216,24 @@ export async function startRepl(program, resumeMode) {
193
216
  'log', 'lang', 'setup', 'env', 'model', 'ctf',
194
217
  ];
195
218
  if (!knownCommands.includes(cmd)) {
196
- // Force Python 3.12 — rewrite python3/python/pip3 to 3.12 binaries
219
+ // Block dangerous commands
220
+ if (BLOCKED_COMMANDS.has(cmd)) {
221
+ console.log(chalk.red(` Blocked: ${cmd} is not allowed during competition.`));
222
+ console.log();
223
+ rl.prompt();
224
+ return;
225
+ }
226
+ // Force Python 3.12 — rewrite python3/python to 3.12 binaries
197
227
  let resolvedInput = input;
198
228
  if (process.platform === 'darwin') {
199
229
  const py12 = '/opt/homebrew/opt/python@3.12/bin/python3.12';
200
- const pip12 = '/opt/homebrew/opt/python@3.12/bin/pip3.12';
201
230
  resolvedInput = resolvedInput
202
231
  .replace(/^python3?\s/, `${py12} `)
203
- .replace(/^pip3?\s/, `${pip12} `);
204
- if (resolvedInput === 'python3' || resolvedInput === 'python')
205
- resolvedInput = py12;
206
- if (resolvedInput === 'pip3' || resolvedInput === 'pip')
207
- resolvedInput = pip12;
232
+ .replace(/^(python3|python)$/, py12);
208
233
  }
209
- // Route to Docker sandbox if available, otherwise system shell
234
+ // Ensure workspace directory
235
+ const cwd = ensureWorkspace();
236
+ // Route to Docker sandbox if available, otherwise system shell (in workspace)
210
237
  processing = true;
211
238
  try {
212
239
  if (isDockerAvailable()) {
@@ -215,11 +242,11 @@ export async function startRepl(program, resumeMode) {
215
242
  await runInSandbox(resolvedInput, rl);
216
243
  }
217
244
  else {
218
- await runSystemCommand(resolvedInput, rl);
245
+ await runSystemCommand(resolvedInput, rl, cwd);
219
246
  }
220
247
  }
221
248
  else {
222
- await runSystemCommand(resolvedInput, rl);
249
+ await runSystemCommand(resolvedInput, rl, cwd);
223
250
  }
224
251
  }
225
252
  catch {
@@ -264,13 +291,13 @@ export async function startRepl(program, resumeMode) {
264
291
  realExit(0);
265
292
  });
266
293
  }
267
- function runSystemCommand(input, rl) {
294
+ function runSystemCommand(input, rl, cwd) {
268
295
  return new Promise((resolve) => {
269
- // Pause readline so the child process gets full terminal control
270
296
  rl.pause();
271
297
  const child = spawn(input, {
272
298
  shell: true,
273
299
  stdio: 'inherit',
300
+ cwd: cwd || process.cwd(),
274
301
  });
275
302
  child.on('close', () => {
276
303
  rl.resume();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "1.9.1",
3
+ "version": "2.0.1",
4
4
  "description": "ICOA CLI — The world's first CLI-native CTF competition terminal",
5
5
  "type": "module",
6
6
  "bin": {