icoa-cli 2.19.110 → 2.19.111

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.
Files changed (47) hide show
  1. package/dist/commands/ai4ctf.d.ts +0 -6
  2. package/dist/commands/ai4ctf.js +1 -1
  3. package/dist/commands/connect.js +1 -1
  4. package/dist/commands/ctf.js +1 -1
  5. package/dist/commands/ctf4ai-demo.d.ts +0 -6
  6. package/dist/commands/ctf4ai-demo.js +1 -1
  7. package/dist/commands/env.js +1 -1
  8. package/dist/commands/exam.js +1 -1
  9. package/dist/commands/files.js +1 -1
  10. package/dist/commands/log.js +1 -1
  11. package/dist/commands/setup.js +1 -1
  12. package/dist/commands/theme.js +1 -1
  13. package/dist/index.js +1 -1
  14. package/dist/lib/access.d.ts +1 -11
  15. package/dist/lib/access.js +1 -1
  16. package/dist/lib/budget.d.ts +0 -13
  17. package/dist/lib/budget.js +1 -1
  18. package/dist/lib/colors.d.ts +0 -4
  19. package/dist/lib/colors.js +1 -1
  20. package/dist/lib/ctfd-client.js +1 -1
  21. package/dist/lib/demo-exam.d.ts +0 -1
  22. package/dist/lib/demo-exam.js +1 -1
  23. package/dist/lib/demo-stats.d.ts +2 -1
  24. package/dist/lib/exam-setup.d.ts +2 -1
  25. package/dist/lib/exam-state.d.ts +0 -2
  26. package/dist/lib/exam-state.js +1 -1
  27. package/dist/lib/gemini.d.ts +1 -11
  28. package/dist/lib/gemini.js +1 -1
  29. package/dist/lib/i18n.d.ts +0 -1
  30. package/dist/lib/i18n.js +1 -1
  31. package/dist/lib/logger.d.ts +1 -3
  32. package/dist/lib/logger.js +1 -1
  33. package/dist/lib/paper-upgrade.d.ts +0 -5
  34. package/dist/lib/paper-upgrade.js +1 -1
  35. package/dist/lib/platform.d.ts +13 -35
  36. package/dist/lib/platform.js +1 -1
  37. package/dist/lib/sandbox.d.ts +0 -1
  38. package/dist/lib/sandbox.js +1 -1
  39. package/dist/lib/theme.d.ts +2 -1
  40. package/dist/lib/ui.js +1 -1
  41. package/dist/lib/update-check.js +1 -1
  42. package/dist/postinstall.js +1 -1
  43. package/dist/repl.js +1 -1
  44. package/dist/types/index.d.ts +0 -11
  45. package/package.json +11 -2
  46. package/dist/lib/terminal.d.ts +0 -7
  47. package/dist/lib/terminal.js +0 -1
@@ -1,6 +1,4 @@
1
- import type { LogEntry, HintLevel } from '../types/index.js';
2
- export declare function logEntry(entry: LogEntry): void;
1
+ import type { LogEntry } from '../types/index.js';
3
2
  export declare function logCommand(command: string): void;
4
- export declare function logHint(level: HintLevel, question: string, challengeId?: number): void;
5
3
  export declare function logSubmission(challengeId: number, flag: string): void;
6
4
  export declare function getSessionLog(): LogEntry[];
@@ -1 +1 @@
1
- import{appendFileSync as t,readFileSync as n,existsSync as o}from"node:fs";import{join as e}from"node:path";import{getConfig as s,getIcoaDir as i}from"./config.js";function r(){return e(i(),"session.log")}export function logEntry(n){try{t(r(),JSON.stringify(n)+"\n")}catch{}}export function logCommand(t){const n=s();logEntry({timestamp:(new Date).toISOString(),level:"command",input:t,sessionId:n.sessionId})}export function logHint(t,n,o){const e=s();logEntry({timestamp:(new Date).toISOString(),level:t,input:n,challengeId:o,sessionId:e.sessionId})}export function logSubmission(t,n){const o=s();logEntry({timestamp:(new Date).toISOString(),level:"submit",input:n,challengeId:t,sessionId:o.sessionId})}export function getSessionLog(){const t=r();if(!o(t))return[];try{return n(t,"utf-8").trim().split("\n").filter(Boolean).map(t=>JSON.parse(t))}catch{return[]}}
1
+ import{appendFileSync as t,readFileSync as n,existsSync as o}from"node:fs";import{join as e}from"node:path";import{getConfig as i,getIcoaDir as s}from"./config.js";function r(){return e(s(),"session.log")}function m(n){try{t(r(),`${JSON.stringify(n)}\n`)}catch{}}export function logCommand(t){const n=i();m({timestamp:(new Date).toISOString(),level:"command",input:t,sessionId:n.sessionId})}export function logSubmission(t,n){const o=i();m({timestamp:(new Date).toISOString(),level:"submit",input:n,challengeId:t,sessionId:o.sessionId})}export function getSessionLog(){const t=r();if(!o(t))return[];try{return n(t,"utf-8").trim().split("\n").filter(Boolean).map(t=>JSON.parse(t))}catch{return[]}}
@@ -18,11 +18,6 @@
18
18
  * organizer. This prevents the CLI from implying self-service token
19
19
  * issuance.
20
20
  */
21
- /**
22
- * True when the just-submitted exam is a C paper (MCQ entry funnel).
23
- * Matches `ua-2026-c`, `pe-2026-c`, `cn-2026-c`, etc. Case-insensitive.
24
- */
25
- export declare function isCPaper(examId: string | undefined): boolean;
26
21
  /**
27
22
  * Renders the C→B upgrade prompt to stdout. No-op if conditions aren't met.
28
23
  *
@@ -1 +1 @@
1
- import chalk from"chalk";import{isNativeWindowsCmd as o,isInWSL as e,hasPython as l}from"./platform.js";export function isCPaper(o){return!!o&&/-2026-c$/i.test(o.trim())}export function showCToBUpgradePrompt(n,t){if(!isCPaper(n))return;if(!t)return;const s=o(),a=e(),r=l();console.log(chalk.cyan(" ─────────────────────────────────────────────")),console.log(),console.log(chalk.bold.white(" ★ Ready for more? Paper B — K-12 with AI")),console.log(),console.log(chalk.gray(" Paper B adds ")+chalk.white("AI4CTF")+chalk.gray(" (chat with AI to find hidden flags) and ")+chalk.white("CTF4AI")+chalk.gray(" (break AI security).")),console.log(chalk.gray(" 150 points total · 90 minutes · same command interface you already know.")),console.log(),s?(console.log(chalk.bold.white(" To take Paper B on Windows, install WSL2 + Ubuntu 22:")),console.log(),console.log(chalk.white(" 1. Open PowerShell as Administrator")),console.log(chalk.gray(' (right-click PowerShell → "Run as administrator")')),console.log(chalk.white(" 2. Run: ")+chalk.cyan("wsl --install -d Ubuntu-22.04")),console.log(chalk.white(" 3. Reboot when prompted, create a Linux username + password")),console.log(chalk.white(" 4. Inside Ubuntu, install Node.js 22 and this CLI:")),console.log(chalk.gray(" ")+chalk.cyan("curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash -")),console.log(chalk.gray(" ")+chalk.cyan("sudo apt install -y nodejs")),console.log(chalk.gray(" ")+chalk.cyan("sudo npm install -g icoa-cli")),console.log(),console.log(chalk.gray(" Setup takes 30-60 min the first time. You only do this once."))):a?(console.log(chalk.bold.white(" You are on WSL2 — your setup is almost ready:")),console.log(),r?(console.log(chalk.green(" ✓ Python 3 already installed. You are ready.")),console.log()):(console.log(chalk.white(" Install Python 3 (for Paper B practical questions):")),console.log(chalk.gray(" ")+chalk.cyan("sudo apt install -y python3 python3-pip")),console.log())):"darwin"===process.platform?(console.log(chalk.bold.white(" On macOS, install Python 3 for Paper B practicals:")),console.log(),console.log(chalk.white(" With Homebrew:")),console.log(chalk.gray(" ")+chalk.cyan("brew install python@3.12")),console.log(),console.log(chalk.white(" Without Homebrew: download from ")+chalk.cyan.underline("https://python.org")),r&&(console.log(),console.log(chalk.green(" ✓ Python 3 already detected. You are ready."))),console.log()):(console.log(chalk.bold.white(" On Linux, install Python 3 for Paper B practicals:")),console.log(),console.log(chalk.white(" Ubuntu / Debian:")),console.log(chalk.gray(" ")+chalk.cyan("sudo apt install -y python3 python3-pip")),console.log(chalk.white(" Fedora / RHEL:")),console.log(chalk.gray(" ")+chalk.cyan("sudo dnf install -y python3 python3-pip")),r&&(console.log(),console.log(chalk.green(" ✓ Python 3 already detected. You are ready."))),console.log()),console.log(chalk.bold.white(" Then get a Paper B token from your organizer.")),console.log(chalk.gray(" Each token is one-shot and bound to one device.")),console.log(),console.log(chalk.gray(" Detailed step-by-step guide: ")+chalk.cyan.underline("https://icoa2026.au/selectionguide/")),console.log()}
1
+ import chalk from"chalk";import{isNativeWindowsCmd as o,isInWSL as e,hasPython as l}from"./platform.js";export function showCToBUpgradePrompt(n,t){if(!function(o){return!!o&&/-2026-c$/i.test(o.trim())}(n))return;if(!t)return;const s=o(),a=e(),r=l();console.log(chalk.cyan(" ─────────────────────────────────────────────")),console.log(),console.log(chalk.bold.white(" ★ Ready for more? Paper B — K-12 with AI")),console.log(),console.log(chalk.gray(" Paper B adds ")+chalk.white("AI4CTF")+chalk.gray(" (chat with AI to find hidden flags) and ")+chalk.white("CTF4AI")+chalk.gray(" (break AI security).")),console.log(chalk.gray(" 150 points total · 90 minutes · same command interface you already know.")),console.log(),s?(console.log(chalk.bold.white(" To take Paper B on Windows, install WSL2 + Ubuntu 22:")),console.log(),console.log(chalk.white(" 1. Open PowerShell as Administrator")),console.log(chalk.gray(' (right-click PowerShell → "Run as administrator")')),console.log(chalk.white(" 2. Run: ")+chalk.cyan("wsl --install -d Ubuntu-22.04")),console.log(chalk.white(" 3. Reboot when prompted, create a Linux username + password")),console.log(chalk.white(" 4. Inside Ubuntu, install Node.js 22 and this CLI:")),console.log(chalk.gray(" ")+chalk.cyan("curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash -")),console.log(chalk.gray(" ")+chalk.cyan("sudo apt install -y nodejs")),console.log(chalk.gray(" ")+chalk.cyan("sudo npm install -g icoa-cli")),console.log(),console.log(chalk.gray(" Setup takes 30-60 min the first time. You only do this once."))):a?(console.log(chalk.bold.white(" You are on WSL2 — your setup is almost ready:")),console.log(),r?(console.log(chalk.green(" ✓ Python 3 already installed. You are ready.")),console.log()):(console.log(chalk.white(" Install Python 3 (for Paper B practical questions):")),console.log(chalk.gray(" ")+chalk.cyan("sudo apt install -y python3 python3-pip")),console.log())):"darwin"===process.platform?(console.log(chalk.bold.white(" On macOS, install Python 3 for Paper B practicals:")),console.log(),console.log(chalk.white(" With Homebrew:")),console.log(chalk.gray(" ")+chalk.cyan("brew install python@3.12")),console.log(),console.log(chalk.white(" Without Homebrew: download from ")+chalk.cyan.underline("https://python.org")),r&&(console.log(),console.log(chalk.green(" ✓ Python 3 already detected. You are ready."))),console.log()):(console.log(chalk.bold.white(" On Linux, install Python 3 for Paper B practicals:")),console.log(),console.log(chalk.white(" Ubuntu / Debian:")),console.log(chalk.gray(" ")+chalk.cyan("sudo apt install -y python3 python3-pip")),console.log(chalk.white(" Fedora / RHEL:")),console.log(chalk.gray(" ")+chalk.cyan("sudo dnf install -y python3 python3-pip")),r&&(console.log(),console.log(chalk.green(" ✓ Python 3 already detected. You are ready."))),console.log()),console.log(chalk.bold.white(" Then get a Paper B token from your organizer.")),console.log(chalk.gray(" Each token is one-shot and bound to one device.")),console.log(),console.log(chalk.gray(" Detailed step-by-step guide: ")+chalk.cyan.underline("https://icoa2026.au/selectionguide/")),console.log()}
@@ -1,24 +1,20 @@
1
1
  /**
2
- * Platform detection for Windows cmd.exe K-12 entry path.
2
+ * Platform detection primitives 3 exported probes:
3
+ * isInWSL() — running inside a WSL distribution
4
+ * isNativeWindowsCmd() — cmd.exe or PowerShell, NOT WSL
5
+ * hasPython() — any python3 / python / py binary reachable
3
6
  *
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).
7
+ * Callers (repl.ts menu, exam.ts setup gate, exam.ts peek logic) inline
8
+ * the policy decisions — e.g. `!isNativeWindowsCmd()` for "show exam setup"
9
+ * and `isNativeWindowsCmd()` for "recommend C paper". This file stays as a
10
+ * thin detection layer; policy lives with its caller.
8
11
  *
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.
12
+ * Context: Windows middle-school students (12-14 y/o) are an expected cohort
13
+ * for ICOA 2026. Observed in-field: students blocked by WSL install complexity.
14
+ * Windows native cmd + Node.js handles MCQ + AI chat but not Q31-36 practical
15
+ * (Unix grep / strings / pwntools). Decision (v2.19.97 + v2.19.110): C paper
16
+ * is the cmd / setup-less entry funnel. See the peek-based gate in exam.ts.
15
17
  */
16
- export interface PlatformInfo {
17
- platform: NodeJS.Platform;
18
- isWindowsNativeCmd: boolean;
19
- isInWSL: boolean;
20
- hasPython: boolean;
21
- }
22
18
  /**
23
19
  * Heuristic: true when running inside a WSL distribution. Node.js reports
24
20
  * `process.platform === 'linux'` in that case, but WSL sets specific env vars.
@@ -38,21 +34,3 @@ export declare function isNativeWindowsCmd(): boolean;
38
34
  * "not recognized as internal or external command" messages to stdout.
39
35
  */
40
36
  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;
@@ -1 +1 @@
1
- import{execFileSync as o}from"node:child_process";export function isInWSL(){return!(!process.env.WSL_DISTRO_NAME&&!process.env.WSLENV)}export function isNativeWindowsCmd(){return"win32"===process.platform&&!isInWSL()}export function hasPython(){const n=[["python3",["--version"]],["python",["--version"]],["py",["-3","--version"]]];for(const[t,e]of n)try{return o(t,e,{encoding:"utf-8",timeout:1500,stdio:["ignore","pipe","ignore"]}),!0}catch{}return!1}export function getPlatformInfo(){return{platform:process.platform,isWindowsNativeCmd:isNativeWindowsCmd(),isInWSL:isInWSL(),hasPython:hasPython()}}export function shouldShowExamSetup(){return!isNativeWindowsCmd()}export function shouldRecommendCPaper(){return isNativeWindowsCmd()}
1
+ import{execFileSync as o}from"node:child_process";export function isInWSL(){return!(!process.env.WSL_DISTRO_NAME&&!process.env.WSLENV)}export function isNativeWindowsCmd(){return"win32"===process.platform&&!isInWSL()}export function hasPython(){const n=[["python3",["--version"]],["python",["--version"]],["py",["-3","--version"]]];for(const[r,t]of n)try{return o(r,t,{encoding:"utf-8",timeout:1500,stdio:["ignore","pipe","ignore"]}),!0}catch{}return!1}
@@ -1,5 +1,4 @@
1
1
  export declare function isDockerAvailable(): boolean;
2
- export declare function isSandboxRunning(): boolean;
3
2
  export declare function ensureSandbox(): Promise<boolean>;
4
3
  export declare function runInSandbox(command: string, rl: {
5
4
  pause: () => void;
@@ -1 +1 @@
1
- import{execSync as o,spawn as e}from"node:child_process";import chalk from"chalk";const r="icoa/sandbox:2026",n="icoa-sandbox";export function isDockerAvailable(){try{return o("docker info",{stdio:"ignore"}),!0}catch{return!1}}export function isSandboxRunning(){try{return"true"===o(`docker inspect -f '{{.State.Running}}' ${n} 2>/dev/null`,{encoding:"utf-8"}).trim()}catch{return!1}}export async function ensureSandbox(){if(!isDockerAvailable())return console.log(chalk.yellow(" Docker not found. Install Docker Desktop to use sandbox tools.")),console.log(chalk.gray(" https://www.docker.com/products/docker-desktop")),!1;if(isSandboxRunning())return!0;try{return o(`docker start ${n}`,{stdio:"ignore"}),!0}catch{}try{o(`docker image inspect ${r}`,{stdio:"ignore"})}catch{console.log(chalk.gray(" Pulling sandbox image (first time only)..."));try{o(`docker pull ${r}`,{stdio:"inherit"})}catch{console.log(chalk.gray(" Building sandbox from local Dockerfile..."));try{const e=new URL("../../docker",import.meta.url).pathname;o(`docker build -t ${r} ${e}`,{stdio:"inherit"})}catch{return console.log(chalk.red(" Failed to set up sandbox.")),!1}}}try{return o(`docker run -d --name ${n} -v icoa-challenges:/home/competitor/challenges --network host ${r} sleep infinity`,{stdio:"ignore"}),!0}catch{return console.log(chalk.red(" Failed to start sandbox container.")),!1}}export function runInSandbox(o,r){return new Promise(t=>{r.pause();const c=e("docker",["exec","-it",n,"bash","-c",o],{stdio:"inherit",shell:!0});c.on("close",()=>{r.resume(),t()}),c.on("error",()=>{r.resume(),t()})})}
1
+ import{execSync as o,spawn as e}from"node:child_process";import chalk from"chalk";const r="icoa/sandbox:2026",t="icoa-sandbox";export function isDockerAvailable(){try{return o("docker info",{stdio:"ignore"}),!0}catch{return!1}}export async function ensureSandbox(){if(!isDockerAvailable())return console.log(chalk.yellow(" Docker not found. Install Docker Desktop to use sandbox tools.")),console.log(chalk.gray(" https://www.docker.com/products/docker-desktop")),!1;if(function(){try{return"true"===o(`docker inspect -f '{{.State.Running}}' ${t} 2>/dev/null`,{encoding:"utf-8"}).trim()}catch{return!1}}())return!0;try{return o(`docker start ${t}`,{stdio:"ignore"}),!0}catch{}try{o(`docker image inspect ${r}`,{stdio:"ignore"})}catch{console.log(chalk.gray(" Pulling sandbox image (first time only)..."));try{o(`docker pull ${r}`,{stdio:"inherit"})}catch{console.log(chalk.gray(" Building sandbox from local Dockerfile..."));try{const e=new URL("../../docker",import.meta.url).pathname;o(`docker build -t ${r} ${e}`,{stdio:"inherit"})}catch{return console.log(chalk.red(" Failed to set up sandbox.")),!1}}}try{return o(`docker run -d --name ${t} -v icoa-challenges:/home/competitor/challenges --network host ${r} sleep infinity`,{stdio:"ignore"}),!0}catch{return console.log(chalk.red(" Failed to start sandbox container.")),!1}}export function runInSandbox(o,r){return new Promise(n=>{r.pause();const c=e("docker",["exec","-it",t,"bash","-c",o],{stdio:"inherit",shell:!0});c.on("close",()=>{r.resume(),n()}),c.on("error",()=>{r.resume(),n()})})}
@@ -1,3 +1,4 @@
1
- export type ThemeVariant = 'dark' | 'high-contrast';
1
+ type ThemeVariant = 'dark' | 'high-contrast';
2
2
  export declare function setTerminalTheme(variant?: ThemeVariant): void;
3
3
  export declare function resetTerminalTheme(): void;
4
+ export {};
package/dist/lib/ui.js CHANGED
@@ -1 +1 @@
1
- import chalk from"chalk";import o from"cli-table3";import ora from"ora";import{Marked as t}from"marked";import{markedTerminal as n}from"marked-terminal";import{c as e}from"./colors.js";const r=new t(n());export function printSuccess(o){console.log(chalk.green("✓ ")+e.fg(o))}export function printError(o){console.log(chalk.red("✗ ")+e.fg(o))}export function printWarning(o){console.log(chalk.yellow("⚠ ")+e.fg(o))}export function printInfo(o){console.log(chalk.blue("ℹ ")+e.fg(o))}export function printTable(t,n){const e=new o({head:t.map(o=>chalk.cyan.bold(o)),style:{head:[],border:[]}});for(const o of n)e.push(o);console.log(e.toString())}export function printMarkdown(o){const t=r.parse(o);"string"==typeof t&&console.log(t)}let l=!1;export function setReplMode(o){l=o}export function createSpinner(o){if(l){const t={text:o,start:()=>(console.log(chalk.cyan(" "+o)),t),stop:()=>t,succeed:o=>(console.log(chalk.green(""+o)),t),fail:o=>(console.log(chalk.red(""+o)),t),info:o=>(console.log(chalk.blue(""+o)),t),warn:o=>(console.log(chalk.yellow(""+o)),t)};return t}return ora({text:o,color:"cyan"})}export function formatCountdown(o){const t=new Date,n=o.getTime()-t.getTime();if(n<=0)return"00:00:00";const e=Math.floor(n/36e5),r=Math.floor(n%36e5/6e4),l=Math.floor(n%6e4/1e3);return[e.toString().padStart(2,"0"),r.toString().padStart(2,"0"),l.toString().padStart(2,"0")].join(":")}export function printHeader(o){console.log(),console.log(chalk.cyan.bold(` ${o}`)),console.log(chalk.cyan(" "+"─".repeat(o.length+4)))}export function printKeyValue(o,t){console.log(` ${chalk.gray(o+":")} ${e.fg(t)}`)}
1
+ import chalk from"chalk";import o from"cli-table3";import ora from"ora";import{Marked as t}from"marked";import{markedTerminal as n}from"marked-terminal";import{c as e}from"./colors.js";const r=new t(n());export function printSuccess(o){console.log(chalk.green("✓ ")+e.fg(o))}export function printError(o){console.log(chalk.red("✗ ")+e.fg(o))}export function printWarning(o){console.log(chalk.yellow("⚠ ")+e.fg(o))}export function printInfo(o){console.log(chalk.blue("ℹ ")+e.fg(o))}export function printTable(t,n){const e=new o({head:t.map(o=>chalk.cyan.bold(o)),style:{head:[],border:[]}});for(const o of n)e.push(o);console.log(e.toString())}export function printMarkdown(o){const t=r.parse(o);"string"==typeof t&&console.log(t)}let l=!1;export function setReplMode(o){l=o}export function createSpinner(o){if(l){const t={text:o,start:()=>(console.log(chalk.cyan(` ${o}`)),t),stop:()=>t,succeed:o=>(console.log(chalk.green(`${o}`)),t),fail:o=>(console.log(chalk.red(`${o}`)),t),info:o=>(console.log(chalk.blue(`${o}`)),t),warn:o=>(console.log(chalk.yellow(`${o}`)),t)};return t}return ora({text:o,color:"cyan"})}export function formatCountdown(o){const t=new Date,n=o.getTime()-t.getTime();if(n<=0)return"00:00:00";const e=Math.floor(n/36e5),r=Math.floor(n%36e5/6e4),l=Math.floor(n%6e4/1e3);return[e.toString().padStart(2,"0"),r.toString().padStart(2,"0"),l.toString().padStart(2,"0")].join(":")}export function printHeader(o){console.log(),console.log(chalk.cyan.bold(` ${o}`)),console.log(chalk.cyan(` ${"─".repeat(o.length+4)}`))}export function printKeyValue(o,t){console.log(` ${chalk.gray(`${o}:`)} ${e.fg(t)}`)}
@@ -1 +1 @@
1
- import{readFileSync as o,writeFileSync as t,existsSync as e}from"node:fs";import{join as n,dirname as s}from"node:path";import{fileURLToPath as r}from"node:url";import{platform as l}from"node:os";import chalk from"chalk";import{getIcoaDir as i}from"./config.js";const a="win32"===l()||"darwin"===l()?"npm install -g icoa-cli@latest":"sudo npm install -g icoa-cli@latest",c=s(r(import.meta.url));function p(o,t){const e=function(o,t){const e=o.split(".").map(Number),n=t.split(".").map(Number);return e[0]!==n[0]||e[1]!==n[1]?-1:(e[2]??0)-(n[2]??0)}(t,o),n=e>0?chalk.gray(` (${e} version${1===e?"":"s"} behind)`):"",s=chalk.yellow(" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");console.log(),console.log(s),console.log(chalk.bold.yellow(" ⬆ New version available!")),console.log(),console.log(chalk.white(" Current: ")+chalk.gray("v"+o)),console.log(chalk.white(" Latest: ")+chalk.bold.green("v"+t)+n),console.log(),console.log(chalk.white(" Update: ")+chalk.bold.cyan(a)),console.log(s),console.log()}function m(o,t){const e=o.split(".").map(Number),n=t.split(".").map(Number);for(let o=0;o<Math.max(e.length,n.length);o++){const t=e[o]??0,s=n[o]??0;if(t>s)return!0;if(t<s)return!1}return!1}export function checkForUpdates(){setTimeout(async()=>{try{const s=n(c,"..","..","package.json"),r=JSON.parse(o(s,"utf-8")).version,l=n(i(),"update-check.json");if(e(l))try{const t=JSON.parse(o(l,"utf-8"));if(Date.now()-t.lastCheck<216e5)return void(t.latestVersion&&m(t.latestVersion,r)&&p(r,t.latestVersion))}catch{}const a=new AbortController,u=setTimeout(()=>a.abort(),5e3),g=await fetch("https://registry.npmjs.org/icoa-cli/latest",{signal:a.signal,headers:{Accept:"application/json"}});if(clearTimeout(u),!g.ok)return;const f=(await g.json()).version;if(!f)return;const h={lastCheck:Date.now(),latestVersion:f};t(l,JSON.stringify(h,null,2)),m(f,r)&&p(r,f)}catch{}},0)}
1
+ import{readFileSync as o,writeFileSync as t,existsSync as e}from"node:fs";import{join as n,dirname as s}from"node:path";import{fileURLToPath as r}from"node:url";import{platform as l}from"node:os";import chalk from"chalk";import{getIcoaDir as i}from"./config.js";const a="win32"===l()||"darwin"===l()?"npm install -g icoa-cli@latest":"sudo npm install -g icoa-cli@latest",c=s(r(import.meta.url));function p(o,t){const e=function(o,t){const e=o.split(".").map(Number),n=t.split(".").map(Number);return e[0]!==n[0]||e[1]!==n[1]?-1:(e[2]??0)-(n[2]??0)}(t,o),n=e>0?chalk.gray(` (${e} version${1===e?"":"s"} behind)`):"",s=chalk.yellow(" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");console.log(),console.log(s),console.log(chalk.bold.yellow(" ⬆ New version available!")),console.log(),console.log(chalk.white(" Current: ")+chalk.gray(`v${o}`)),console.log(chalk.white(" Latest: ")+chalk.bold.green(`v${t}`)+n),console.log(),console.log(chalk.white(" Update: ")+chalk.bold.cyan(a)),console.log(s),console.log()}function m(o,t){const e=o.split(".").map(Number),n=t.split(".").map(Number);for(let o=0;o<Math.max(e.length,n.length);o++){const t=e[o]??0,s=n[o]??0;if(t>s)return!0;if(t<s)return!1}return!1}export function checkForUpdates(){setTimeout(async()=>{try{const s=n(c,"..","..","package.json"),r=JSON.parse(o(s,"utf-8")).version,l=n(i(),"update-check.json");if(e(l))try{const t=JSON.parse(o(l,"utf-8"));if(Date.now()-t.lastCheck<216e5)return void(t.latestVersion&&m(t.latestVersion,r)&&p(r,t.latestVersion))}catch{}const a=new AbortController,u=setTimeout(()=>a.abort(),5e3),g=await fetch("https://registry.npmjs.org/icoa-cli/latest",{signal:a.signal,headers:{Accept:"application/json"}});if(clearTimeout(u),!g.ok)return;const f=(await g.json()).version;if(!f)return;const h={lastCheck:Date.now(),latestVersion:f};t(l,JSON.stringify(h,null,2)),m(f,r)&&p(r,f)}catch{}},0)}
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- const o=["Initializing ICOA CLI...","Loading competition modules...","Configuring exam system...","Setting up references...","Finalizing installation..."];function l(o,l){const e=Math.round(o/100*30),n=30-e,m=""+"█".repeat(e)+""+"░".repeat(n)+"",t=String(o).padStart(3)+"%";process.stdout.write(`\r ${m} ${t} ${l}`)}(async function(){console.log(),console.log(" ██╗ ██████╗ ██████╗ █████╗"),console.log(" ██║██╔════╝██╔═══██╗██╔══██╗"),console.log(" ██║██║ ██║ ██║███████║"),console.log(" ██║██║ ██║ ██║██╔══██║"),console.log(" ██║╚██████╗╚██████╔╝██║ ██║"),console.log(" ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝"),console.log();for(let e=0;e<o.length;e++){const n=Math.round(e/o.length*100),m=Math.round((e+1)/o.length*100);for(let t=n;t<=m;t++)l(t,o[e]),await new Promise(o=>setTimeout(o,15))}console.log(),console.log(),console.log(" ✓ ICOA CLI installed successfully!"),console.log(),console.log(" Get started:"),console.log(" icoa Launch and select your mode"),console.log(" icoa --help Show all commands"),console.log()})().catch(()=>{});export{};
2
+ const o=["Initializing ICOA CLI...","Loading competition modules...","Configuring exam system...","Setting up references...","Finalizing installation..."];function l(o,l){const e=Math.round(o/100*30),n=30-e,m=`${"█".repeat(e)}${"░".repeat(n)}`,t=`${String(o).padStart(3)}%`;process.stdout.write(`\r ${m} ${t} ${l}`)}(async function(){console.log(),console.log(" ██╗ ██████╗ ██████╗ █████╗"),console.log(" ██║██╔════╝██╔═══██╗██╔══██╗"),console.log(" ██║██║ ██║ ██║███████║"),console.log(" ██║██║ ██║ ██║██╔══██║"),console.log(" ██║╚██████╗╚██████╔╝██║ ██║"),console.log(" ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝"),console.log();for(let e=0;e<o.length;e++){const n=Math.round(e/o.length*100),m=Math.round((e+1)/o.length*100);for(let t=n;t<=m;t++)l(t,o[e]),await new Promise(o=>setTimeout(o,15))}console.log(),console.log(),console.log(" ✓ ICOA CLI installed successfully!"),console.log(),console.log(" Get started:"),console.log(" icoa Launch and select your mode"),console.log(" icoa --help Show all commands"),console.log()})().catch(()=>{});export{};
package/dist/repl.js CHANGED
@@ -1 +1 @@
1
- import{createInterface as o}from"node:readline";import{spawn as e,execSync as t}from"node:child_process";import chalk from"chalk";import{isConnected as l,getConfig as n,saveConfig as s}from"./lib/config.js";import{isActivated as a,activateToken as r,isFreeCommand as i,isDeviceMatch as c,recordExit as g,recordResume as y,isFirstRunOrUpgrade as m,markVersionSeen as p}from"./lib/access.js";import{setReplMode as d}from"./lib/ui.js";import{isChatActive as u,handleChatMessage as h}from"./commands/ai4ctf.js";import{isCtf4aiActive as w,handleCtf4aiMessage as f}from"./commands/ctf4ai-demo.js";import{getExamState as b,getRealExamState as x,getDemoState as v}from"./lib/exam-state.js";import{getDemoStats as C}from"./lib/demo-stats.js";import{isExamSetupComplete as k}from"./lib/exam-setup.js";import{DEMO_PICK_SIZE as I,DEMO_POOL_SIZE as A}from"./lib/demo-exam.js";import{isNativeWindowsCmd as T}from"./lib/platform.js";import{resetTerminalTheme as S}from"./lib/theme.js";import{ensureSandbox as $,runInSandbox as O,isDockerAvailable as q}from"./lib/sandbox.js";import{logCommand as L}from"./lib/logger.js";import{startLogSync as j,stopLogSync as E}from"./lib/log-sync.js";import{existsSync as P,mkdirSync as R,writeFileSync as N}from"node:fs";import{join as D}from"node:path";import{homedir as F}from"node:os";function M(){return x()?chalk.cyan("exam> "):v()?chalk.yellow("demo> "):chalk.green("icoa> ")}const U=D(F(),"icoa-workspace");function z(){return P(U)||R(U,{recursive:!0}),U}const B=new Set(["sudo","su","doas","pkexec","brew","apt","apt-get","yum","choco","npm","npx","pip","pip3","shutdown","reboot","halt","mkfs","fdisk","dd","iptables","ufw"]),W="__REPL_NO_EXIT__",Q="2.5.1";function Y(){const o=C(),e=k(),l=`Free practice — ${I} questions (from pool of ${A})`,n=T();if(console.log(),console.log(" "+chalk.cyan.bold("[Selection Mode]")),console.log(),n)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)")),console.log();else if(o.attempts>0){const o=function(){const o=["python3.12 --version","/opt/homebrew/opt/python@3.12/bin/python3.12 --version","/usr/local/opt/python@3.12/bin/python3.12 --version","python3 --version","python --version","py -3.12 --version","py -3 --version"];let e="",l="missing";for(const n of o)try{const o=t(n,{encoding:"utf-8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).trim().replace("Python ",""),[s,a]=o.split(".").map(Number);if(3===s&&12===a)return{ok:!0,version:o,status:"ok"};e=o,l=3===s&&a>=10&&a<12?"old":3===s&&a>12?"new":"missing"}catch{}return{ok:"missing"!==l,version:e,status:l}}();"missing"===o.status?(console.log(chalk.yellow(" ⚠ Python not detected. For exam practical questions:")),console.log(chalk.gray(" → ")+chalk.bold.cyan("env python")+chalk.gray(" (platform install guide)")),console.log()):"new"===o.status&&(console.log(chalk.yellow(` ⚠ Python ${o.version} may lack CTF wheels. Python 3.12 recommended:`)),console.log(chalk.gray(" → ")+chalk.bold.cyan("env python")+chalk.gray(" (install guide)")),console.log())}if(0===o.attempts)console.log(chalk.white(" New here? Start with ")+chalk.bold.cyan("demo")+chalk.white(" — it takes a few minutes.")),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.bold.cyan(" demo")+chalk.gray(` ${l}`)),console.log(chalk.white(" lang")+chalk.gray(" List all supported languages")),console.log(chalk.white(" lang es")+chalk.gray(" Switch language (e.g. lang es, lang zh, lang fr)")),console.log(chalk.gray(" ─────────────────────────────────────────────"));else if(e||n){const e=1===o.attempts?"attempt":"attempts";o.attempts>0&&console.log(chalk.green(" ✓ Demo completed ")+chalk.gray(`(${o.attempts} ${e})`)),n||console.log(chalk.green(" ✓ Environment ready")),console.log(chalk.yellow(" → Enter your exam token to begin.")),console.log(chalk.gray(" (10-char code from your organizer, starts with your country code like ")+chalk.cyan("UA")+chalk.gray(" — case-insensitive)")),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.bold.yellow(" exam <token>")+chalk.gray(" Enter exam (primary action — use your organizer-issued token)")),console.log(chalk.gray(" format: ")+chalk.white("exam UAxxxxxxxx")+chalk.gray(" (2-letter country prefix + 8 chars)")),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.gray(" Other commands:")),console.log(chalk.white(" demo")+chalk.gray(` ${l}`)),n||console.log(chalk.white(" exam setup")+chalk.gray(" Re-verify tool environment")),console.log(chalk.white(" lang")+chalk.gray(" List all supported languages")),console.log(chalk.white(" lang es")+chalk.gray(" Switch language (e.g. lang es, lang zh, lang fr)")),console.log(chalk.gray(" ─────────────────────────────────────────────"))}else{const e=1===o.attempts?"attempt":"attempts";console.log(chalk.green(" ✓ Demo completed ")+chalk.gray(`(${o.attempts} ${e}${o.bestPercentage>0?` · best ${o.bestPercentage}%`:""})`)),console.log(chalk.yellow(" → Next: prepare your environment for the real exam.")),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.white(" demo")+chalk.gray(` ${l}`)),console.log(chalk.bold.yellow(" exam setup")+chalk.gray(" Install tools for national selection (~150MB)")),console.log(chalk.white(" lang")+chalk.gray(" List all supported languages")),console.log(chalk.white(" lang es")+chalk.gray(" Switch language (e.g. lang es, lang zh, lang fr)")),console.log(chalk.gray(" ─────────────────────────────────────────────"))}console.log(chalk.gray(" ")+chalk.gray("Tip: ")+chalk.cyan("help")+chalk.gray(" for commands · ")+chalk.cyan("Ctrl+C")+chalk.gray(" pauses · ")+chalk.cyan("quit")+chalk.gray(" closes")),console.log()}export async function startRepl(e,x){const v=n(),C=l(),k=process.exit.bind(process),I=a();if(v.demoCleanedForVersion!==Q){try{const{existsSync:o,unlinkSync:e}=await import("node:fs"),{join:t}=await import("node:path"),{getIcoaDir:l}=await import("./lib/config.js"),n=t(l(),"demo-state.json");o(n)&&e(n)}catch{}s({demoCleanedForVersion:Q})}const{select:A,confirm:T}=await import("@inquirer/prompts"),V=v.mode||"",G=[{name:` ${chalk.bold("National Selection")} ${chalk.gray("—")} ${chalk.gray("demo, exam (lightweight)")}`,value:"selection"},{name:` ${chalk.bold("International Olympiad")} ${chalk.gray("—")} ${chalk.gray("CTF × AI (~500MB, advanced)")}`,value:"olympiad"},{name:` ${chalk.bold("National/Regional Partner")} ${chalk.gray("—")} ${chalk.gray("organizer tools (tokens, competitions)")}`,value:"organizer"},{name:` ${chalk.gray("About ICOA")} ${chalk.gray("·")} ${chalk.gray("Info & contact")}`,value:"about"}];console.log(chalk.gray(" Use ")+chalk.yellow("↑")+chalk.gray(" or ")+chalk.yellow("↓")+chalk.gray(" to select, ")+chalk.yellow("Enter")+chalk.gray(" to confirm.")),console.log();let J="";for(;!J;){const o=await A({message:"Mode",choices:G,default:V||"selection"});"about"!==o?J=o:(console.clear(),console.log(),console.log(chalk.cyan(" ═══════════════════════════════════════════════════")),console.log(chalk.bold.yellow(" ICOA")+chalk.white(" — AI-Native CLI OS for Cyber & AI Security")),console.log(chalk.gray(" Olympiad & Competition · K-12 to University")),console.log(chalk.cyan(" ───────────────────────────────────────────────────")),console.log(),console.log(chalk.bold.white(" What Makes ICOA Different")),console.log(chalk.gray(" · AI-native AI teammate, AI adversary, AI translation")),console.log(chalk.gray(" · CLI OS Complete competition environment in terminal")),console.log(chalk.gray(" · 110 tools pwntools, z3, gdb, nmap, sleuthkit... pre-configured")),console.log(chalk.gray(" · Global scale 15,000+ concurrent exams · 15 languages")),console.log(),console.log(chalk.bold.white(" Competition Format")),console.log(" "+chalk.green.bold("AI4CTF")+chalk.gray(" [Day 1] AI as teammate — 5hr jeopardy CTF")),console.log(" "+chalk.red.bold("CTF4AI")+chalk.gray(" [Day 2] Challenge AI — adversarial ML, red-team")),console.log(),console.log(chalk.white(" Sydney, Australia")+chalk.gray(" · Jun 27 - Jul 2, 2026 · 40+ countries")),console.log(),console.log(chalk.bold.white(" Organized by")+chalk.gray(" ASRA (Australia) · ICO Foundation Inc")),console.log(chalk.bold.white(" Contact ")+chalk.cyan(" australia@icoa2026.au · accreditation@icoa2026.au")),console.log(chalk.bold.white(" Website ")+chalk.cyan.underline(" https://icoa2026.au")),console.log(chalk.cyan(" ═══════════════════════════════════════════════════")),console.log(),console.log(chalk.gray(" Press ")+chalk.yellow("Enter")+chalk.gray(" to return...")),await new Promise(o=>{const e=t=>{process.stdin.removeListener("data",e),process.stdin.isTTY&&process.stdin.setRawMode&&process.stdin.setRawMode(!1),process.stdin.pause(),o()};process.stdin.isTTY&&process.stdin.setRawMode&&process.stdin.setRawMode(!0),process.stdin.resume(),process.stdin.once("data",e)}),console.clear())}if("olympiad"===J&&"olympiad"!==V&&(console.log(),console.log(chalk.yellow(" This mode will download ~500MB of CTF tools and AI models.")),await T({message:"Continue?",default:!0})||(J="selection",console.log(chalk.gray(" Switched to National Selection mode.")))),J!==V&&s({mode:J}),console.log(),"olympiad"===J&&m(Q)){p(Q),console.log(chalk.gray(" Checking competition environment..."));const{execSync:o}=await import("node:child_process"),e=[{name:"pwntools",cmd:'python3 -c "import pwn"'},{name:"z3-solver",cmd:'python3 -c "import z3"'},{name:"numpy",cmd:'python3 -c "import numpy"'},{name:"requests",cmd:'python3 -c "import requests"'}];let t=0;for(const l of e)try{o(l.cmd,{stdio:"ignore"})}catch{t++}if(t>0){console.log(chalk.yellow(` ${t} core libraries missing.`));try{const{confirm:o}=await import("@inquirer/prompts");if(await o({message:" Install competition Python libraries now?",default:!0,theme:{prefix:"",style:{message:o=>chalk.green(o),defaultAnswer:o=>chalk.green(o)}}})){console.log();const{execSync:o}=await import("node:child_process");o("icoa env setup",{stdio:"inherit"})}}catch{console.log(chalk.gray(" Run ")+chalk.white("env setup")+chalk.gray(" later to install."))}console.log()}else console.log(chalk.green(" All core libraries ready.")),console.log()}if(x){const o=y();if(o){const e=Math.floor(o.awaySeconds/60),t=o.awaySeconds%60;console.log(chalk.yellow(` Session resumed. Away: ${e}m ${t}s | Total exits: ${o.exitCount}`)),console.log()}}"selection"===J?Y():"organizer"===J?(console.log(chalk.yellow.bold(" [National/Regional Partner]")),console.log(),console.log(chalk.bold.white(" ██╗ ██████╗ ██████╗ █████╗")),console.log(chalk.bold.white(" ██║██╔════╝██╔═══██╗██╔══██╗")),console.log(chalk.bold.white(" ██║██║ ██║ ██║███████║")),console.log(chalk.bold.white(" ██║██║ ██║ ██║██╔══██║")),console.log(chalk.bold.white(" ██║╚██████╗╚██████╔╝██║ ██║")),console.log(chalk.bold.white(" ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝")),console.log(),console.log(chalk.yellow(" International Cyber Olympiad in AI 2026")),console.log(chalk.bold.magenta(" The World's First AI-Native CLI Operating System")),console.log(chalk.bold.magenta(" for Cybersecurity & AI Security Competition")),console.log(chalk.bold.magenta(" and Olympiad for K-12")),console.log(chalk.gray(" Sydney, Australia · Jun 27 - Jul 2, 2026")),console.log(),console.log(chalk.white(" Vision")),console.log(chalk.gray(" Building a global pipeline for youth cyber & AI")),console.log(chalk.gray(" security talent through education and competition.")),console.log(),console.log(chalk.white(" Capacity")),console.log(chalk.gray(" 15,000+ concurrent online examinations")),console.log(chalk.gray(" National selection, training, and education support")),console.log(),console.log(chalk.white(" Olympic Spirit")),console.log(chalk.gray(" Excellence · Friendship · Respect")),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.white(" New country accreditation & support:")),console.log(chalk.cyan(" australia@icoa2026.au")),console.log(chalk.cyan(" accreditation@icoa2026.au")),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(),C?(console.log(chalk.green(` Logged in as ${v.userName}`)),console.log(chalk.white(" exam list")+chalk.gray(" Manage exams")),console.log(chalk.white(" logout")+chalk.gray(" Disconnect"))):console.log(chalk.white(" join <url>")+chalk.gray(" Connect to manage exams")),console.log()):I&&!c()?(console.log(chalk.red(" Token was activated on a different device.")),console.log(chalk.gray(" Contact organizer for assistance.")),console.log()):C?(console.log(chalk.green.bold(` Welcome back, ${v.userName}!`)),console.log(chalk.gray(` Connected to ${v.ctfdUrl}`)),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.white(" Ready to compete? Start here:")),console.log(),console.log(chalk.bold.cyan(" challenges")+chalk.gray(" Browse challenges by category")),console.log(chalk.white(" status")+chalk.gray(" Your score & hint budget")),console.log(chalk.white(" scoreboard")+chalk.gray(" Live rankings")),console.log(chalk.white(" help")+chalk.gray(" Full command list")),console.log(),console.log(chalk.gray(" Tool environment:")),console.log(chalk.white(" env")+chalk.gray(" See which of the 110 CTF tools are installed")),console.log(chalk.white(" env setup")+chalk.gray(" Install anything missing (~5 min, one-time)")),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.gray(" Tip: ")+chalk.cyan("help")+chalk.gray(" · ")+chalk.cyan("Ctrl+C")+chalk.gray(" pauses · ")+chalk.cyan("quit")+chalk.gray(" closes")),console.log()):I?(z(),console.log(chalk.green.bold(" Welcome, competitor!")),console.log(chalk.gray(` Workspace: ${U}`)),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.white(" Get started:")),console.log(),console.log(chalk.white(" Step 1 ")+chalk.bold.cyan("join <url>")+chalk.gray(" Connect to competition server")),console.log(chalk.white(" Step 2 ")+chalk.bold.cyan("challenges")+chalk.gray(" Browse & solve challenges")),console.log(chalk.white(" Step 3 ")+chalk.bold.cyan("ai4ctf")+chalk.gray(" Ask AI when stuck")),console.log(),console.log(chalk.gray(" Before Step 1 — make sure your tools are ready:")),console.log(chalk.white(" env")+chalk.gray(" See which of the 110 CTF tools are installed")),console.log(chalk.white(" env setup")+chalk.gray(" Install anything missing (~5 min, one-time)")),console.log(),console.log(chalk.gray(" Also: ")+chalk.white("help")+chalk.gray(" all commands")),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.gray(" Tip: ")+chalk.cyan("Ctrl+C")+chalk.gray(" pauses · ")+chalk.cyan("exit")+chalk.gray(" → menu · ")+chalk.cyan("quit")+chalk.gray(" closes CLI")),console.log()):(console.log(chalk.bold.white(" Welcome to ICOA CLI — International Olympiad")),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.white(" To begin, activate your competition token:")),console.log(),console.log(chalk.bold.cyan(" activate <token>")),console.log(),console.log(chalk.gray(" While waiting, explore:")),console.log(chalk.white(" ref linux")+chalk.gray(" Quick reference for Linux")),console.log(chalk.white(" ref web")+chalk.gray(" Quick reference for Web")),console.log(chalk.white(" env")+chalk.gray(" See which of the 110 CTF tools are installed")),console.log(chalk.white(" env setup")+chalk.gray(" Install anything missing (~5 min, one-time)")),console.log(chalk.white(" help")+chalk.gray(" All available commands")),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.gray(" Tip: ")+chalk.cyan("Ctrl+C")+chalk.gray(" pauses · ")+chalk.cyan("exit")+chalk.gray(" → menu · ")+chalk.cyan("quit")+chalk.gray(" closes CLI")),console.log()),e.exitOverride(),e.configureOutput({writeErr:()=>{},writeOut:o=>{console.log(o)}});const K=o({input:process.stdin,output:process.stdout,prompt:M(),terminal:!0});let Z=!1;d(!0),j();const H=K.prompt.bind(K);K.prompt=o=>{u()||w()||K.setPrompt(M()),H(o)},K.prompt(),K.on("line",async o=>{if(Z)return;const l=o.trim();if(!l)return K.setPrompt(u()?chalk.magenta("ai4ctf> "):M()),void K.prompt();if(u()){Z=!0;const o=await h(l);return Z=!1,"exit"===o&&K.setPrompt(M()),void K.prompt()}if(w()){Z=!0;const o=await f(l);return Z=!1,"exit"!==o&&"solved"!==o||K.setPrompt(M()),void K.prompt()}if(L(l),"exit"===l)return b()?(console.log(),console.log(chalk.yellow(" ⚠ An exam is in progress.")),console.log(chalk.white(" To return to menu without losing progress, type: ")+chalk.bold.cyan("back")),console.log(chalk.white(" To fully close ICOA CLI, type: ")+chalk.bold.cyan("quit")),console.log(chalk.gray(" Your progress is auto-saved either way.")),console.log(),void K.prompt()):(console.log(),console.log(chalk.gray(" ")+chalk.white("exit")+chalk.gray(" returns to the main menu. To fully close ICOA CLI, type ")+chalk.bold.cyan("quit")+chalk.gray(".")),"selection"===J&&Y(),void K.prompt());if("quit"===l||"q"===l||"quit confirm"===l){const o=b();return o&&"demo-free"!==o.session.examId&&"quit confirm"!==l?(console.log(),console.log(chalk.yellow(" ⚠ A real exam is in progress.")),console.log(chalk.gray(" Your answers are auto-saved on the server, but the exam timer keeps ticking")),console.log(chalk.gray(" on the server side even if you close the CLI.")),console.log(),console.log(chalk.white(" To leave the CLI but keep the exam alive, type: ")+chalk.bold.cyan("back")),console.log(chalk.gray(" (recommended — you can resume with ")+chalk.cyan("exam q 1")+chalk.gray(" after relaunching icoa)")),console.log(),console.log(chalk.white(" To really close ICOA CLI, type: ")+chalk.bold.cyan("quit confirm")),console.log(),void K.prompt()):(o&&"demo-free"===o.session.examId&&(console.log(),console.log(chalk.gray(" Demo paused. Resume with: ")+chalk.white("demo")+chalk.gray(" (fresh) or ")+chalk.white("exam q 1")+chalk.gray(" (continue)."))),E(),g(),console.log(chalk.gray(" Session saved. Use ")+chalk.white("icoa --resume")+chalk.gray(" to continue.")),S(),void k(0))}if("back"===l||"menu"===l){const o=b(),e=o&&"demo-free"!==o.session.examId,t=o&&"demo-free"===o.session.examId&&(()=>{const e=new Date(o.session.startedAt||0).getTime();return Date.now()-e<18e5})();if(e)console.log(),console.log(chalk.gray(" Exam paused. Your progress is saved.")),console.log(chalk.white(" Resume: exam q 1")+chalk.gray(" · ")+chalk.white("exam review")+chalk.gray(" · ")+chalk.white("exam submit")),console.log();else if(t){const e=Object.keys(o.answers).length,t=o.session.questionCount;console.log(),console.log(chalk.gray(` Demo paused (${e}/${t} answered). Resume with: `)+chalk.white("exam q 1")),console.log(chalk.gray(" Or type ")+chalk.white("demo")+chalk.gray(" to restart.")),console.log()}else{if(o&&"demo-free"===o.session.examId){const{clearExamState:o}=await import("./lib/exam-state.js");o("demo-free")}const e=n();fetch("https://practice.icoa2026.au/api/icoa/demo-stats",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"post-report-back",lang:e.language||"en",timestamp:(new Date).toISOString()}),signal:AbortSignal.timeout(5e3)}).catch(()=>{}),"selection"===J?Y():console.log(chalk.gray(" Already at main menu."))}return void K.prompt()}if("help"===l||"?"===l){if(b()){Z=!0;try{await e.parseAsync(["node","icoa","exam","help"])}catch{}return Z=!1,void K.prompt()}return function(o,e="olympiad"){console.log(),"selection"===e||"organizer"===e?(console.log(chalk.bold.white(" Exam")),console.log(chalk.white(" join <url> ")+chalk.gray("Connect to exam server")),console.log(chalk.white(" exam list ")+chalk.gray("Available exams")),console.log(chalk.white(" exam start <id> ")+chalk.gray("Begin an exam")),console.log(chalk.white(" exam q [n] ")+chalk.gray("View questions")),console.log(chalk.white(" exam answer <n> <X> ")+chalk.gray("Answer question")),console.log(chalk.white(" exam review ")+chalk.gray("Review all answers")),console.log(chalk.white(" exam submit ")+chalk.gray("Submit for grading")),console.log(chalk.white(" exam result ")+chalk.gray("View your score")),console.log(),console.log(chalk.bold.white(" System")),console.log(chalk.white(" ref [topic] ")+chalk.gray("Quick reference")),console.log(chalk.white(" setup ")+chalk.gray("Settings / switch mode")),console.log(chalk.white(" lang [code] ")+chalk.gray("Switch language")),console.log(chalk.white(" clear ")+chalk.gray("Clear screen")),console.log(chalk.white(" exit ")+chalk.gray("Quit")),console.log()):o?(console.log(chalk.cyan(" ═══════════════════════════════════════════════")),console.log(chalk.bold.white(" How it works")),console.log(),console.log(chalk.gray(" 1. Browse ")+chalk.white("challenges")+chalk.gray(" and pick one")),console.log(chalk.gray(" 2. ")+chalk.white("open <id>")+chalk.gray(" to read the challenge")),console.log(chalk.gray(" 3. Use ")+chalk.white("ai4ctf")+chalk.gray(" to chat with AI when stuck")),console.log(chalk.gray(" 4. ")+chalk.white("submit <id> icoa{flag}")+chalk.gray(" to score points")),console.log(chalk.gray(" 5. Check ")+chalk.white("scoreboard")+chalk.gray(" to track your rank")),console.log(chalk.cyan(" ═══════════════════════════════════════════════")),console.log(),console.log(chalk.bold.white(" Competition")),console.log(chalk.white(" join <url> ")+chalk.gray("Connect to CTFd")),console.log(chalk.white(" challenges (ch) ")+chalk.gray("List challenges by category")),console.log(chalk.white(" open <id> ")+chalk.gray("Read challenge + get next steps")),console.log(chalk.white(" submit <id> <flag> ")+chalk.gray("Submit a flag")),console.log(chalk.white(" scoreboard (sb) ")+chalk.gray("Live rankings")),console.log(chalk.white(" status ")+chalk.gray("Your score, budget & timer")),console.log(chalk.white(" time ")+chalk.gray("Countdown timer")),console.log(),console.log(chalk.bold.white(" AI Teammate")+chalk.gray(" — 3 levels, use wisely")),console.log(chalk.white(' hint "question" ')+chalk.gray("Level A — General guidance (50 uses)")),console.log(chalk.white(' hint-b "question" ')+chalk.gray("Level B — Deep analysis (10 uses)")),console.log(chalk.white(' hint-c "question" ')+chalk.gray("Level C — Critical assist (2 uses)")),console.log(chalk.white(" hint budget ")+chalk.gray("Check remaining uses")),console.log(chalk.white(" ai4ctf ")+chalk.gray("Free-chat with AI (no limit)")),console.log(),console.log(chalk.bold.white(" Tools")),console.log(chalk.white(" ref [topic] ")+chalk.gray("Quick reference (linux, web, crypto...)")),console.log(chalk.white(" shell ")+chalk.gray("Docker sandbox")),console.log(chalk.white(" files <id> ")+chalk.gray("Download challenge files")),console.log(chalk.white(" connect <id> ")+chalk.gray("Connect to remote target")),console.log(chalk.white(" note [text] ")+chalk.gray("Personal notepad")),console.log(chalk.white(" log ")+chalk.gray("Session history")),console.log(),console.log(chalk.bold.white(" System")),console.log(chalk.white(" setup ")+chalk.gray("Configure settings")),console.log(chalk.white(" lang [code] ")+chalk.gray("Switch language (15 supported)")),console.log(chalk.white(" logout ")+chalk.gray("Disconnect")),console.log(chalk.white(" clear ")+chalk.gray("Clear screen")),console.log(chalk.white(" exit ")+chalk.gray("Quit (session saved)")),console.log()):(console.log(chalk.bold.yellow(" Restricted Mode — activate with a token to unlock all commands")),console.log(),console.log(chalk.white(" activate <token> ")+chalk.gray("Unlock full access")),console.log(chalk.white(" ref [topic] ")+chalk.gray("Quick reference")),console.log(chalk.white(" exit ")+chalk.gray("Quit")),console.log())}(a(),J),void K.prompt()}if("more help"===l.toLowerCase()&&b()){Z=!0;try{await e.parseAsync(["node","icoa","exam","more-help"])}catch{}return Z=!1,void K.prompt()}if("continue"===l.toLowerCase())return console.log(),console.log(chalk.green.bold(" ═══ AI4CTF — AI as Your Teammate ═══")),console.log(),console.log(chalk.white(" In AI4CTF, you solve cybersecurity challenges")),console.log(chalk.white(" with AI by your side.")),console.log(),console.log(chalk.white(" In competition, you get AI help at 3 levels:")),console.log(chalk.yellow(" hint a")+chalk.gray(" General guidance (50 uses)")),console.log(chalk.yellow(" hint b")+chalk.gray(" Deep analysis (10 uses)")),console.log(chalk.yellow(" hint c")+chalk.gray(" Critical assist (2 uses)")),console.log(),console.log(chalk.white(" Try it now! Type: ")+chalk.bold.green("ai4ctf")),console.log(chalk.gray(' Chat freely with your AI teammate. Type "exit" when done.')),console.log(),console.log(chalk.gray(" After ai4ctf, try: ")+chalk.bold.red("ctf4ai")+chalk.gray(' — trick the AI into saying "koala"')),console.log(),void K.prompt();if(/^ICOA-[A-Z]{2,3}-\d{1,6}$/i.test(l.trim())){Z=!0;try{await e.parseAsync(["node","icoa","exam","token",l.trim()])}catch{}return Z=!1,void K.prompt()}if(/^[A-Z]{2}[0-9A-HJKMNP-TV-Z]{8}$/i.test(l.trim())){Z=!0;try{await e.parseAsync(["node","icoa","exam","token",l.trim().toUpperCase()])}catch{}return Z=!1,void K.prompt()}const s=l.match(/^exam\s+([A-Z]{2}[0-9A-HJKMNP-TV-Z]{8})$/i);if(s){Z=!0;try{await e.parseAsync(["node","icoa","exam","token",s[1].toUpperCase()])}catch{}return Z=!1,void K.prompt()}const y=l.match(/^exam\s+([A-Z]{2,3})$/i);if(y){Z=!0;try{await e.parseAsync(["node","icoa","exam","list",y[1]])}catch{}return Z=!1,void K.prompt()}if("clear"===l||"cls"===l)return console.clear(),void K.prompt();if(l.startsWith("activate ")){const o=l.slice(9).trim(),e=r(o);return"ok"===e?console.log(chalk.green(" Access granted! Token bound to this device.")):"already_bound"===e?(console.log(),console.log(chalk.red(" Token already activated on a different device.")),console.log(chalk.gray(" Each token binds to the first device that uses it. If you lost the device,")),console.log(chalk.gray(" contact your proctor to have the token re-issued for a new device."))):(console.log(),console.log(chalk.red(" Token not recognized.")),console.log(chalk.gray(" Possible reasons:")),console.log(chalk.white(" • ")+chalk.gray("Typo — tokens are case-insensitive, 10 chars, start with a 2-letter country code (e.g. ")+chalk.cyan("UAK7M2R9Q4")+chalk.gray(")")),console.log(chalk.white(" • ")+chalk.gray("Expired — ask your proctor or organizer for a fresh token")),console.log(chalk.white(" • ")+chalk.gray("Network — verify connection to ")+chalk.cyan("practice.icoa2026.au")),console.log(chalk.gray(" Still stuck? type ")+chalk.cyan("help")+chalk.gray(" or try ")+chalk.cyan("exam demo")+chalk.gray(" for a free practice round."))),console.log(),void K.prompt()}if("activate"===l)return console.log(chalk.gray(" Usage: ")+chalk.white("activate <token>")),console.log(),void K.prompt();const m=b();if(m){const o=l.toUpperCase().trim(),t=o=>{const e=m.questions.find(e=>e.number===o);return!!e&&("ai4ctf"===e.type||"ctf4ai"===e.type||e.options&&!e.options.A&&!e.options.B)},n=o=>{const e="demo-free"!==m.session.examId,t=e&&o>=39?"ctf4ai":e&&o>=31?"ai4ctf":null;console.log(),console.log(chalk.yellow(` Q${o} is a practical question — letters (A/B/C/D) don't apply here.`)),t?(console.log(chalk.white(" Enter the AI chat for this question: ")+chalk.bold.cyan(t)),console.log(chalk.gray(" Or submit a flag directly: ")+chalk.green(`exam answer ${o} ICOA{your_flag}`))):console.log(chalk.gray(" Submit a flag: ")+chalk.green(`exam answer ${o} ICOA{your_flag}`)),console.log()};if(/^[ABCD]$/.test(o)){const l=m._lastQ||1;if(t(l))return n(l),void K.prompt();Z=!0;try{await e.parseAsync(["node","icoa","exam","answer",String(l),o])}catch{}return Z=!1,void K.prompt()}const s=o.match(/^(\d+)\s+([ABCD])$/);if(s){const o=parseInt(s[1],10);if(t(o))return n(o),void K.prompt();Z=!0;try{await e.parseAsync(["node","icoa","exam","answer",s[1],s[2]])}catch{}return Z=!1,void K.prompt()}}const p=l.split(/\s+/)[0].toLowerCase(),d=/^python3?(\.\d+)?$/.test(p),x=l.startsWith("!")||p.startsWith("!")||d;if("selection"===J&&!x&&!["exam","demo","retry","nations","next","prev","continue","setup","lang","ref","ai4ctf","ctf4ai","mark","unmark","review","submit","env"].includes(p)){if(console.log(chalk.gray(" Not available in Selection mode.")),m){const o=m._lastQ||1;console.log(chalk.white(` Resume exam: exam q ${o}`)+chalk.gray(" · ")+chalk.white("A/B/C/D")+chalk.gray(" to answer"))}else console.log(chalk.gray(" Try: demo · setup to switch mode"));return console.log(),void K.prompt()}if("organizer"===J&&!["join","exam","demo","retry","next","prev","logout","setup","lang","ref","ctf","mark","unmark","review","submit"].includes(p))return console.log(chalk.gray(" Not available in Organizer mode. Switch via: setup")),console.log(),void K.prompt();if(!("olympiad"!==J||a()&&c()||i(p)))return console.log(chalk.yellow(" Restricted mode. ")+chalk.gray("Enter your access token:")),console.log(chalk.white(" activate <token>")),console.log(),console.log(chalk.gray(" Free commands: ")+chalk.white("ref [topic]")+chalk.gray(", ")+chalk.white("help")+chalk.gray(", ")+chalk.white("exit")),console.log(),void K.prompt();if(!["join","activate","challenges","ch","open","submit","flag","scoreboard","sb","status","time","ref","shell","files","connect","note","log","lang","setup","env","ai4ctf","model","ctf","exam","demo","retry","nations","next","prev","continue","logout","ctf4ai","mark","unmark","review","submit"].includes(p)){if(B.has(p))return console.log(chalk.red(` Blocked: ${p} is not allowed during competition.`)),console.log(),void K.prompt();if(/(?:^|\s)(?:\/(?!home\/|Users\/|tmp\/)|\.\.\/|~\/)/.test(l)&&!l.startsWith("cd ")){const o=/(?:^|\s)\/(?!home\/\w+\/icoa-workspace|Users\/\w+\/icoa-workspace|tmp\/)/.test(l),e=/\.\./.test(l);if(o||e)return console.log(chalk.red(" Blocked: access outside workspace is not allowed.")),console.log(chalk.gray(` Workspace: ${U}`)),console.log(),void K.prompt()}let o=l.startsWith("!")?l.slice(1).trim():l;if("darwin"===process.platform){const e="/opt/homebrew/opt/python@3.12/bin/python3.12";o=o.replace(/^python3?\s/,`${e} `).replace(/^(python3|python)$/,e)}else if("win32"===process.platform){const e=(()=>{try{return t("py -3 --version",{stdio:["ignore","ignore","ignore"],timeout:1500}),"py -3"}catch{}return"python"})();o=o.replace(/^python3?(\.\d+)?\s/,`${e} `).replace(/^python3?(\.\d+)?$/,e)}else{const e=(()=>{try{return t("which python3.12",{stdio:"ignore"}),"python3.12"}catch{return"python3"}})();o=o.replace(/^python\s/,`${e} `).replace(/^python$/,e)}const e=z();/^(\S*python3?(\.\d+)?)\s*$/.test(o)&&(o=`PYTHONSTARTUP="${function(){const o=D(F(),".icoa");P(o)||R(o,{recursive:!0});const e=D(o,"python-startup.py");return P(e)||N(e,"# ICOA exam interactive startup — auto-loaded by PYTHONSTARTUP\nimport base64, struct, hashlib, re, json, os, sys, binascii\ntry: import requests\nexcept ImportError: pass\ntry: from Crypto.Cipher import AES\nexcept ImportError: pass\ntry: from Crypto.Util.Padding import pad, unpad\nexcept ImportError: pass\ntry: from pwn import xor, p32, u32, p64, u64\nexcept ImportError: pass\ntry: import bs4\nexcept ImportError: pass\ntry: import numpy as np\nexcept ImportError: pass\n"),e}()}" ${o}`,console.log(),console.log(chalk.cyan(" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")),console.log(chalk.bold.white(" Python ready — ICOA exam toolkit pre-loaded")),console.log(chalk.cyan(" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")),console.log(),console.log(chalk.white(" Already imported: ")+chalk.gray("base64, struct, hashlib, re, json, binascii")),console.log(chalk.white(" Also available: ")+chalk.gray("requests, bs4, numpy, AES, pad/unpad, xor, p32/u32/p64/u64")),console.log(),console.log(chalk.yellow(" Quick examples:")),console.log(chalk.gray(' base64.b64decode("aGVsbG8=") ')+chalk.gray("# decode base64")),console.log(chalk.gray(' bytes.fromhex("48656c6c6f") ')+chalk.gray("# hex → bytes")),console.log(chalk.gray(' "ICOA{x}".encode() ')+chalk.gray("# str → bytes")),console.log(chalk.gray(" [chr(c) for c in [73,67,79,65]] ")+chalk.gray("# ASCII codes")),console.log(chalk.gray(' xor(bytes.fromhex("0a2b"), b"IC") ')+chalk.gray("# pwntools XOR")),console.log(),console.log(chalk.gray(" Exit: ")+chalk.white("exit()")+chalk.gray(" or Ctrl-D")),console.log(chalk.cyan(" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")),console.log()),Z=!0;try{q()&&await $()?await O(o,K):await _(o,K,e)}catch{console.log(chalk.yellow(` Command failed: ${p}`))}return Z=!1,console.log(),void K.prompt()}Z=!0;const v=m&&"submit"===l.toLowerCase()?["exam","submit"]:function(o){const e=o.split(/\s+/),t=e[0].toLowerCase(),l=e.slice(1),n={demo:["exam","demo"],retry:["exam","demo-retry"],nations:["exam","nations"],next:["exam","next"],prev:["exam","prev"],mark:["exam","mark",...l],unmark:["exam","unmark",...l],review:["exam","review"],logout:["ctf","logout"],join:["ctf","join",...l],activate:["ctf","activate",...l],challenges:["ctf","challenges"],ch:["ctf","challenges"],open:["ctf","open",...l],submit:["ctf","submit",...l],flag:["ctf","submit",...l],scoreboard:["ctf","scoreboard",...l],sb:["ctf","scoreboard",...l],status:["ctf","status"],time:["ctf","time"]};return n[t]?n[t]:["ref","shell","files","connect","note","log","lang","setup","env","ai4ctf","model","ctf","exam","ctf4ai"].includes(t)?[t,...l]:e}(l),C="ctf"===v[0]&&"join"===v[1];C&&K.pause(),process.exit=()=>{throw new Error(W)};try{await e.parseAsync(["node","icoa",...v])}catch(o){const e=o instanceof Error?o.message:String(o);if(e===W);else if(e.includes("commander.unknownCommand")){const{distance:o}=await import("fastest-levenshtein"),e=["ctf","ref","shell","files","connect","note","log","lang","setup","env","ai4ctf","exam","ctf4ai","theme","clear","cls","quit","exit","back","menu","help","continue","activate","demo","challenges","status","scoreboard","join","logout"],t=p.split(/\s+/)[0]||p;let l={word:"",dist:1/0};for(const n of e){const e=o(t.toLowerCase(),n);e<l.dist&&(l={word:n,dist:e})}console.log(chalk.yellow(` Unknown command: ${p}.`)),l.dist>0&&l.dist<=2&&console.log(chalk.gray(" Did you mean: ")+chalk.bold.cyan(l.word)+chalk.gray("?")),console.log(chalk.gray(" Type ")+chalk.cyan("help")+chalk.gray(" for the full command list."))}else e.includes("commander.")||(e.includes("fetch failed")||e.includes("ECONNREFUSED")||e.includes("ETIMEDOUT"))&&console.log(chalk.yellow(" Network error. Check your connection."))}finally{process.exit=k,Z=!1,C&&K.resume()}u()?K.setPrompt(chalk.magenta("ai4ctf> ")):w()&&K.setPrompt(chalk.red("ctf4ai> ")),console.log(),K.prompt()}),K.on("SIGINT",()=>{if(console.log(),u()||w())console.log(chalk.yellow(" Ctrl+C did not close ICOA CLI — you are still in the AI chat.")),console.log(chalk.white(" Type ")+chalk.bold.cyan("exit")+chalk.white(" to leave the chat and return to the menu."));else if(b()){const o="demo-free"!==b().session.examId;console.log(chalk.yellow(" Ctrl+C did NOT close ICOA CLI.")),console.log(chalk.gray(` Your ${o?"exam":"demo"} is paused and every answer is auto-saved.`)),console.log(),console.log(chalk.white(" Resume: ")+chalk.cyan("exam q 1")+chalk.gray(" · Back to menu: ")+chalk.cyan("back")+chalk.gray(" · Close CLI: ")+chalk.cyan(o?"quit confirm":"quit"))}else console.log(chalk.yellow(" Ctrl+C did not close ICOA CLI — you are still at the ")+chalk.cyan("icoa>")+chalk.yellow(" prompt.")),console.log(chalk.gray(" Keep typing — ")+chalk.cyan("help")+chalk.gray(" lists commands. (Only ")+chalk.cyan("quit")+chalk.gray(" or Ctrl+D actually close the CLI.)"));console.log(),K.prompt()}),K.on("close",()=>{E(),g(),S(),k(0)})}function _(o,t,l){return new Promise(n=>{const s=process.stdin,a=!!s.isTTY&&!!s.isRaw;if(t.pause(),s.isTTY&&"function"==typeof s.setRawMode)try{s.setRawMode(!1)}catch{}const r=e(o,{shell:!0,stdio:"inherit",cwd:l||process.cwd()}),i=()=>{if(s.isTTY&&"function"==typeof s.setRawMode&&a)try{s.setRawMode(!0)}catch{}t.resume(),n()};r.on("close",i),r.on("error",i)})}
1
+ import{createInterface as o}from"node:readline";import{spawn as e,execSync as t}from"node:child_process";import chalk from"chalk";import{isConnected as l,getConfig as n,saveConfig as s}from"./lib/config.js";import{isActivated as a,activateToken as r,isFreeCommand as i,isDeviceMatch as c,recordExit as g,recordResume as y,isFirstRunOrUpgrade as m,markVersionSeen as p}from"./lib/access.js";import{setReplMode as d}from"./lib/ui.js";import{isChatActive as u,handleChatMessage as h}from"./commands/ai4ctf.js";import{isCtf4aiActive as w,handleCtf4aiMessage as f}from"./commands/ctf4ai-demo.js";import{getExamState as b,getRealExamState as x,getDemoState as v}from"./lib/exam-state.js";import{getDemoStats as C}from"./lib/demo-stats.js";import{isExamSetupComplete as k}from"./lib/exam-setup.js";import{DEMO_PICK_SIZE as I,DEMO_POOL_SIZE as A}from"./lib/demo-exam.js";import{isNativeWindowsCmd as T}from"./lib/platform.js";import{resetTerminalTheme as $}from"./lib/theme.js";import{ensureSandbox as S,runInSandbox as O,isDockerAvailable as q}from"./lib/sandbox.js";import{logCommand as L}from"./lib/logger.js";import{startLogSync as j,stopLogSync as E}from"./lib/log-sync.js";import{existsSync as P,mkdirSync as R,writeFileSync as N}from"node:fs";import{join as D}from"node:path";import{homedir as F}from"node:os";function M(){return x()?chalk.cyan("exam> "):v()?chalk.yellow("demo> "):chalk.green("icoa> ")}const U=D(F(),"icoa-workspace");function z(){return P(U)||R(U,{recursive:!0}),U}const B=new Set(["sudo","su","doas","pkexec","brew","apt","apt-get","yum","choco","npm","npx","pip","pip3","shutdown","reboot","halt","mkfs","fdisk","dd","iptables","ufw"]),W="__REPL_NO_EXIT__",Q="2.5.1";function Y(){const o=C(),e=k(),l=`Free practice — ${I} questions (from pool of ${A})`,n=T();if(console.log(),console.log(` ${chalk.cyan.bold("[Selection Mode]")}`),console.log(),n)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)")),console.log();else if(o.attempts>0){const o=function(){const o=["python3.12 --version","/opt/homebrew/opt/python@3.12/bin/python3.12 --version","/usr/local/opt/python@3.12/bin/python3.12 --version","python3 --version","python --version","py -3.12 --version","py -3 --version"];let e="",l="missing";for(const n of o)try{const o=t(n,{encoding:"utf-8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).trim().replace("Python ",""),[s,a]=o.split(".").map(Number);if(3===s&&12===a)return{ok:!0,version:o,status:"ok"};e=o,l=3===s&&a>=10&&a<12?"old":3===s&&a>12?"new":"missing"}catch{}return{ok:"missing"!==l,version:e,status:l}}();"missing"===o.status?(console.log(chalk.yellow(" ⚠ Python not detected. For exam practical questions:")),console.log(chalk.gray(" → ")+chalk.bold.cyan("env python")+chalk.gray(" (platform install guide)")),console.log()):"new"===o.status&&(console.log(chalk.yellow(` ⚠ Python ${o.version} may lack CTF wheels. Python 3.12 recommended:`)),console.log(chalk.gray(" → ")+chalk.bold.cyan("env python")+chalk.gray(" (install guide)")),console.log())}if(0===o.attempts)console.log(chalk.white(" New here? Start with ")+chalk.bold.cyan("demo")+chalk.white(" — it takes a few minutes.")),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.bold.cyan(" demo")+chalk.gray(` ${l}`)),console.log(chalk.white(" lang")+chalk.gray(" List all supported languages")),console.log(chalk.white(" lang es")+chalk.gray(" Switch language (e.g. lang es, lang zh, lang fr)")),console.log(chalk.gray(" ─────────────────────────────────────────────"));else if(e||n){const e=1===o.attempts?"attempt":"attempts";o.attempts>0&&console.log(chalk.green(" ✓ Demo completed ")+chalk.gray(`(${o.attempts} ${e})`)),n||console.log(chalk.green(" ✓ Environment ready")),console.log(chalk.yellow(" → Enter your exam token to begin.")),console.log(chalk.gray(" (10-char code from your organizer, starts with your country code like ")+chalk.cyan("UA")+chalk.gray(" — case-insensitive)")),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.bold.yellow(" exam <token>")+chalk.gray(" Enter exam (primary action — use your organizer-issued token)")),console.log(chalk.gray(" format: ")+chalk.white("exam UAxxxxxxxx")+chalk.gray(" (2-letter country prefix + 8 chars)")),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.gray(" Other commands:")),console.log(chalk.white(" demo")+chalk.gray(` ${l}`)),n||console.log(chalk.white(" exam setup")+chalk.gray(" Re-verify tool environment")),console.log(chalk.white(" lang")+chalk.gray(" List all supported languages")),console.log(chalk.white(" lang es")+chalk.gray(" Switch language (e.g. lang es, lang zh, lang fr)")),console.log(chalk.gray(" ─────────────────────────────────────────────"))}else{const e=1===o.attempts?"attempt":"attempts";console.log(chalk.green(" ✓ Demo completed ")+chalk.gray(`(${o.attempts} ${e}${o.bestPercentage>0?` · best ${o.bestPercentage}%`:""})`)),console.log(chalk.yellow(" → Next: prepare your environment for the real exam.")),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.white(" demo")+chalk.gray(` ${l}`)),console.log(chalk.bold.yellow(" exam setup")+chalk.gray(" Install tools for national selection (~150MB)")),console.log(chalk.white(" lang")+chalk.gray(" List all supported languages")),console.log(chalk.white(" lang es")+chalk.gray(" Switch language (e.g. lang es, lang zh, lang fr)")),console.log(chalk.gray(" ─────────────────────────────────────────────"))}console.log(chalk.gray(" ")+chalk.gray("Tip: ")+chalk.cyan("help")+chalk.gray(" for commands · ")+chalk.cyan("Ctrl+C")+chalk.gray(" pauses · ")+chalk.cyan("quit")+chalk.gray(" closes")),console.log()}export async function startRepl(e,x){const v=n(),C=l(),k=process.exit.bind(process),I=a();if(v.demoCleanedForVersion!==Q){try{const{existsSync:o,unlinkSync:e}=await import("node:fs"),{join:t}=await import("node:path"),{getIcoaDir:l}=await import("./lib/config.js"),n=t(l(),"demo-state.json");o(n)&&e(n)}catch{}s({demoCleanedForVersion:Q})}const{select:A,confirm:T}=await import("@inquirer/prompts"),V=v.mode||"",G=[{name:` ${chalk.bold("National Selection")} ${chalk.gray("—")} ${chalk.gray("demo, exam (lightweight)")}`,value:"selection"},{name:` ${chalk.bold("International Olympiad")} ${chalk.gray("—")} ${chalk.gray("CTF × AI (~500MB, advanced)")}`,value:"olympiad"},{name:` ${chalk.bold("National/Regional Partner")} ${chalk.gray("—")} ${chalk.gray("organizer tools (tokens, competitions)")}`,value:"organizer"},{name:` ${chalk.gray("About ICOA")} ${chalk.gray("·")} ${chalk.gray("Info & contact")}`,value:"about"}];console.log(chalk.gray(" Use ")+chalk.yellow("↑")+chalk.gray(" or ")+chalk.yellow("↓")+chalk.gray(" to select, ")+chalk.yellow("Enter")+chalk.gray(" to confirm.")),console.log();let J="";for(;!J;){const o=await A({message:"Mode",choices:G,default:V||"selection"});"about"!==o?J=o:(console.clear(),console.log(),console.log(chalk.cyan(" ═══════════════════════════════════════════════════")),console.log(chalk.bold.yellow(" ICOA")+chalk.white(" — AI-Native CLI OS for Cyber & AI Security")),console.log(chalk.gray(" Olympiad & Competition · K-12 to University")),console.log(chalk.cyan(" ───────────────────────────────────────────────────")),console.log(),console.log(chalk.bold.white(" What Makes ICOA Different")),console.log(chalk.gray(" · AI-native AI teammate, AI adversary, AI translation")),console.log(chalk.gray(" · CLI OS Complete competition environment in terminal")),console.log(chalk.gray(" · 110 tools pwntools, z3, gdb, nmap, sleuthkit... pre-configured")),console.log(chalk.gray(" · Global scale 15,000+ concurrent exams · 15 languages")),console.log(),console.log(chalk.bold.white(" Competition Format")),console.log(` ${chalk.green.bold("AI4CTF")}${chalk.gray(" [Day 1] AI as teammate — 5hr jeopardy CTF")}`),console.log(` ${chalk.red.bold("CTF4AI")}${chalk.gray(" [Day 2] Challenge AI — adversarial ML, red-team")}`),console.log(),console.log(chalk.white(" Sydney, Australia")+chalk.gray(" · Jun 27 - Jul 2, 2026 · 40+ countries")),console.log(),console.log(chalk.bold.white(" Organized by")+chalk.gray(" ASRA (Australia) · ICO Foundation Inc")),console.log(chalk.bold.white(" Contact ")+chalk.cyan(" australia@icoa2026.au · accreditation@icoa2026.au")),console.log(chalk.bold.white(" Website ")+chalk.cyan.underline(" https://icoa2026.au")),console.log(chalk.cyan(" ═══════════════════════════════════════════════════")),console.log(),console.log(chalk.gray(" Press ")+chalk.yellow("Enter")+chalk.gray(" to return...")),await new Promise(o=>{const e=t=>{process.stdin.removeListener("data",e),process.stdin.isTTY&&process.stdin.setRawMode&&process.stdin.setRawMode(!1),process.stdin.pause(),o()};process.stdin.isTTY&&process.stdin.setRawMode&&process.stdin.setRawMode(!0),process.stdin.resume(),process.stdin.once("data",e)}),console.clear())}if("olympiad"===J&&"olympiad"!==V&&(console.log(),console.log(chalk.yellow(" This mode will download ~500MB of CTF tools and AI models.")),await T({message:"Continue?",default:!0})||(J="selection",console.log(chalk.gray(" Switched to National Selection mode.")))),J!==V&&s({mode:J}),console.log(),"olympiad"===J&&m(Q)){p(Q),console.log(chalk.gray(" Checking competition environment..."));const{execSync:o}=await import("node:child_process"),e=[{name:"pwntools",cmd:'python3 -c "import pwn"'},{name:"z3-solver",cmd:'python3 -c "import z3"'},{name:"numpy",cmd:'python3 -c "import numpy"'},{name:"requests",cmd:'python3 -c "import requests"'}];let t=0;for(const l of e)try{o(l.cmd,{stdio:"ignore"})}catch{t++}if(t>0){console.log(chalk.yellow(` ${t} core libraries missing.`));try{const{confirm:o}=await import("@inquirer/prompts");if(await o({message:" Install competition Python libraries now?",default:!0,theme:{prefix:"",style:{message:o=>chalk.green(o),defaultAnswer:o=>chalk.green(o)}}})){console.log();const{execSync:o}=await import("node:child_process");o("icoa env setup",{stdio:"inherit"})}}catch{console.log(chalk.gray(" Run ")+chalk.white("env setup")+chalk.gray(" later to install."))}console.log()}else console.log(chalk.green(" All core libraries ready.")),console.log()}if(x){const o=y();if(o){const e=Math.floor(o.awaySeconds/60),t=o.awaySeconds%60;console.log(chalk.yellow(` Session resumed. Away: ${e}m ${t}s | Total exits: ${o.exitCount}`)),console.log()}}"selection"===J?Y():"organizer"===J?(console.log(chalk.yellow.bold(" [National/Regional Partner]")),console.log(),console.log(chalk.bold.white(" ██╗ ██████╗ ██████╗ █████╗")),console.log(chalk.bold.white(" ██║██╔════╝██╔═══██╗██╔══██╗")),console.log(chalk.bold.white(" ██║██║ ██║ ██║███████║")),console.log(chalk.bold.white(" ██║██║ ██║ ██║██╔══██║")),console.log(chalk.bold.white(" ██║╚██████╗╚██████╔╝██║ ██║")),console.log(chalk.bold.white(" ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝")),console.log(),console.log(chalk.yellow(" International Cyber Olympiad in AI 2026")),console.log(chalk.bold.magenta(" The World's First AI-Native CLI Operating System")),console.log(chalk.bold.magenta(" for Cybersecurity & AI Security Competition")),console.log(chalk.bold.magenta(" and Olympiad for K-12")),console.log(chalk.gray(" Sydney, Australia · Jun 27 - Jul 2, 2026")),console.log(),console.log(chalk.white(" Vision")),console.log(chalk.gray(" Building a global pipeline for youth cyber & AI")),console.log(chalk.gray(" security talent through education and competition.")),console.log(),console.log(chalk.white(" Capacity")),console.log(chalk.gray(" 15,000+ concurrent online examinations")),console.log(chalk.gray(" National selection, training, and education support")),console.log(),console.log(chalk.white(" Olympic Spirit")),console.log(chalk.gray(" Excellence · Friendship · Respect")),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.white(" New country accreditation & support:")),console.log(chalk.cyan(" australia@icoa2026.au")),console.log(chalk.cyan(" accreditation@icoa2026.au")),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(),C?(console.log(chalk.green(` Logged in as ${v.userName}`)),console.log(chalk.white(" exam list")+chalk.gray(" Manage exams")),console.log(chalk.white(" logout")+chalk.gray(" Disconnect"))):console.log(chalk.white(" join <url>")+chalk.gray(" Connect to manage exams")),console.log()):I&&!c()?(console.log(chalk.red(" Token was activated on a different device.")),console.log(chalk.gray(" Contact organizer for assistance.")),console.log()):C?(console.log(chalk.green.bold(` Welcome back, ${v.userName}!`)),console.log(chalk.gray(` Connected to ${v.ctfdUrl}`)),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.white(" Ready to compete? Start here:")),console.log(),console.log(chalk.bold.cyan(" challenges")+chalk.gray(" Browse challenges by category")),console.log(chalk.white(" status")+chalk.gray(" Your score & hint budget")),console.log(chalk.white(" scoreboard")+chalk.gray(" Live rankings")),console.log(chalk.white(" help")+chalk.gray(" Full command list")),console.log(),console.log(chalk.gray(" Tool environment:")),console.log(chalk.white(" env")+chalk.gray(" See which of the 110 CTF tools are installed")),console.log(chalk.white(" env setup")+chalk.gray(" Install anything missing (~5 min, one-time)")),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.gray(" Tip: ")+chalk.cyan("help")+chalk.gray(" · ")+chalk.cyan("Ctrl+C")+chalk.gray(" pauses · ")+chalk.cyan("quit")+chalk.gray(" closes")),console.log()):I?(z(),console.log(chalk.green.bold(" Welcome, competitor!")),console.log(chalk.gray(` Workspace: ${U}`)),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.white(" Get started:")),console.log(),console.log(chalk.white(" Step 1 ")+chalk.bold.cyan("join <url>")+chalk.gray(" Connect to competition server")),console.log(chalk.white(" Step 2 ")+chalk.bold.cyan("challenges")+chalk.gray(" Browse & solve challenges")),console.log(chalk.white(" Step 3 ")+chalk.bold.cyan("ai4ctf")+chalk.gray(" Ask AI when stuck")),console.log(),console.log(chalk.gray(" Before Step 1 — make sure your tools are ready:")),console.log(chalk.white(" env")+chalk.gray(" See which of the 110 CTF tools are installed")),console.log(chalk.white(" env setup")+chalk.gray(" Install anything missing (~5 min, one-time)")),console.log(),console.log(chalk.gray(" Also: ")+chalk.white("help")+chalk.gray(" all commands")),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.gray(" Tip: ")+chalk.cyan("Ctrl+C")+chalk.gray(" pauses · ")+chalk.cyan("exit")+chalk.gray(" → menu · ")+chalk.cyan("quit")+chalk.gray(" closes CLI")),console.log()):(console.log(chalk.bold.white(" Welcome to ICOA CLI — International Olympiad")),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.white(" To begin, activate your competition token:")),console.log(),console.log(chalk.bold.cyan(" activate <token>")),console.log(),console.log(chalk.gray(" While waiting, explore:")),console.log(chalk.white(" ref linux")+chalk.gray(" Quick reference for Linux")),console.log(chalk.white(" ref web")+chalk.gray(" Quick reference for Web")),console.log(chalk.white(" env")+chalk.gray(" See which of the 110 CTF tools are installed")),console.log(chalk.white(" env setup")+chalk.gray(" Install anything missing (~5 min, one-time)")),console.log(chalk.white(" help")+chalk.gray(" All available commands")),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.gray(" Tip: ")+chalk.cyan("Ctrl+C")+chalk.gray(" pauses · ")+chalk.cyan("exit")+chalk.gray(" → menu · ")+chalk.cyan("quit")+chalk.gray(" closes CLI")),console.log()),e.exitOverride(),e.configureOutput({writeErr:()=>{},writeOut:o=>{console.log(o)}});const K=o({input:process.stdin,output:process.stdout,prompt:M(),terminal:!0});let Z=!1;d(!0),j();const H=K.prompt.bind(K);K.prompt=o=>{u()||w()||K.setPrompt(M()),H(o)},K.prompt(),K.on("line",async o=>{if(Z)return;const l=o.trim();if(!l)return K.setPrompt(u()?chalk.magenta("ai4ctf> "):M()),void K.prompt();if(u()){Z=!0;const o=await h(l);return Z=!1,"exit"===o&&K.setPrompt(M()),void K.prompt()}if(w()){Z=!0;const o=await f(l);return Z=!1,"exit"!==o&&"solved"!==o||K.setPrompt(M()),void K.prompt()}if(L(l),"exit"===l)return b()?(console.log(),console.log(chalk.yellow(" ⚠ An exam is in progress.")),console.log(chalk.white(" To return to menu without losing progress, type: ")+chalk.bold.cyan("back")),console.log(chalk.white(" To fully close ICOA CLI, type: ")+chalk.bold.cyan("quit")),console.log(chalk.gray(" Your progress is auto-saved either way.")),console.log(),void K.prompt()):(console.log(),console.log(chalk.gray(" ")+chalk.white("exit")+chalk.gray(" returns to the main menu. To fully close ICOA CLI, type ")+chalk.bold.cyan("quit")+chalk.gray(".")),"selection"===J&&Y(),void K.prompt());if("quit"===l||"q"===l||"quit confirm"===l){const o=b();return o&&"demo-free"!==o.session.examId&&"quit confirm"!==l?(console.log(),console.log(chalk.yellow(" ⚠ A real exam is in progress.")),console.log(chalk.gray(" Your answers are auto-saved on the server, but the exam timer keeps ticking")),console.log(chalk.gray(" on the server side even if you close the CLI.")),console.log(),console.log(chalk.white(" To leave the CLI but keep the exam alive, type: ")+chalk.bold.cyan("back")),console.log(chalk.gray(" (recommended — you can resume with ")+chalk.cyan("exam q 1")+chalk.gray(" after relaunching icoa)")),console.log(),console.log(chalk.white(" To really close ICOA CLI, type: ")+chalk.bold.cyan("quit confirm")),console.log(),void K.prompt()):(o&&"demo-free"===o.session.examId&&(console.log(),console.log(chalk.gray(" Demo paused. Resume with: ")+chalk.white("demo")+chalk.gray(" (fresh) or ")+chalk.white("exam q 1")+chalk.gray(" (continue)."))),E(),g(),console.log(chalk.gray(" Session saved. Use ")+chalk.white("icoa --resume")+chalk.gray(" to continue.")),$(),void k(0))}if("back"===l||"menu"===l){const o=b(),e=o&&"demo-free"!==o.session.examId,t=o&&"demo-free"===o.session.examId&&(()=>{const e=new Date(o.session.startedAt||0).getTime();return Date.now()-e<18e5})();if(e)console.log(),console.log(chalk.gray(" Exam paused. Your progress is saved.")),console.log(chalk.white(" Resume: exam q 1")+chalk.gray(" · ")+chalk.white("exam review")+chalk.gray(" · ")+chalk.white("exam submit")),console.log();else if(t){const e=Object.keys(o.answers).length,t=o.session.questionCount;console.log(),console.log(chalk.gray(` Demo paused (${e}/${t} answered). Resume with: `)+chalk.white("exam q 1")),console.log(chalk.gray(" Or type ")+chalk.white("demo")+chalk.gray(" to restart.")),console.log()}else{if(o&&"demo-free"===o.session.examId){const{clearExamState:o}=await import("./lib/exam-state.js");o("demo-free")}const e=n();fetch("https://practice.icoa2026.au/api/icoa/demo-stats",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"post-report-back",lang:e.language||"en",timestamp:(new Date).toISOString()}),signal:AbortSignal.timeout(5e3)}).catch(()=>{}),"selection"===J?Y():console.log(chalk.gray(" Already at main menu."))}return void K.prompt()}if("help"===l||"?"===l){if(b()){Z=!0;try{await e.parseAsync(["node","icoa","exam","help"])}catch{}return Z=!1,void K.prompt()}return function(o,e="olympiad"){console.log(),"selection"===e||"organizer"===e?(console.log(chalk.bold.white(" Exam")),console.log(chalk.white(" join <url> ")+chalk.gray("Connect to exam server")),console.log(chalk.white(" exam list ")+chalk.gray("Available exams")),console.log(chalk.white(" exam start <id> ")+chalk.gray("Begin an exam")),console.log(chalk.white(" exam q [n] ")+chalk.gray("View questions")),console.log(chalk.white(" exam answer <n> <X> ")+chalk.gray("Answer question")),console.log(chalk.white(" exam review ")+chalk.gray("Review all answers")),console.log(chalk.white(" exam submit ")+chalk.gray("Submit for grading")),console.log(chalk.white(" exam result ")+chalk.gray("View your score")),console.log(),console.log(chalk.bold.white(" System")),console.log(chalk.white(" ref [topic] ")+chalk.gray("Quick reference")),console.log(chalk.white(" setup ")+chalk.gray("Settings / switch mode")),console.log(chalk.white(" lang [code] ")+chalk.gray("Switch language")),console.log(chalk.white(" clear ")+chalk.gray("Clear screen")),console.log(chalk.white(" exit ")+chalk.gray("Quit")),console.log()):o?(console.log(chalk.cyan(" ═══════════════════════════════════════════════")),console.log(chalk.bold.white(" How it works")),console.log(),console.log(chalk.gray(" 1. Browse ")+chalk.white("challenges")+chalk.gray(" and pick one")),console.log(chalk.gray(" 2. ")+chalk.white("open <id>")+chalk.gray(" to read the challenge")),console.log(chalk.gray(" 3. Use ")+chalk.white("ai4ctf")+chalk.gray(" to chat with AI when stuck")),console.log(chalk.gray(" 4. ")+chalk.white("submit <id> icoa{flag}")+chalk.gray(" to score points")),console.log(chalk.gray(" 5. Check ")+chalk.white("scoreboard")+chalk.gray(" to track your rank")),console.log(chalk.cyan(" ═══════════════════════════════════════════════")),console.log(),console.log(chalk.bold.white(" Competition")),console.log(chalk.white(" join <url> ")+chalk.gray("Connect to CTFd")),console.log(chalk.white(" challenges (ch) ")+chalk.gray("List challenges by category")),console.log(chalk.white(" open <id> ")+chalk.gray("Read challenge + get next steps")),console.log(chalk.white(" submit <id> <flag> ")+chalk.gray("Submit a flag")),console.log(chalk.white(" scoreboard (sb) ")+chalk.gray("Live rankings")),console.log(chalk.white(" status ")+chalk.gray("Your score, budget & timer")),console.log(chalk.white(" time ")+chalk.gray("Countdown timer")),console.log(),console.log(chalk.bold.white(" AI Teammate")+chalk.gray(" — 3 levels, use wisely")),console.log(chalk.white(' hint "question" ')+chalk.gray("Level A — General guidance (50 uses)")),console.log(chalk.white(' hint-b "question" ')+chalk.gray("Level B — Deep analysis (10 uses)")),console.log(chalk.white(' hint-c "question" ')+chalk.gray("Level C — Critical assist (2 uses)")),console.log(chalk.white(" hint budget ")+chalk.gray("Check remaining uses")),console.log(chalk.white(" ai4ctf ")+chalk.gray("Free-chat with AI (no limit)")),console.log(),console.log(chalk.bold.white(" Tools")),console.log(chalk.white(" ref [topic] ")+chalk.gray("Quick reference (linux, web, crypto...)")),console.log(chalk.white(" shell ")+chalk.gray("Docker sandbox")),console.log(chalk.white(" files <id> ")+chalk.gray("Download challenge files")),console.log(chalk.white(" connect <id> ")+chalk.gray("Connect to remote target")),console.log(chalk.white(" note [text] ")+chalk.gray("Personal notepad")),console.log(chalk.white(" log ")+chalk.gray("Session history")),console.log(),console.log(chalk.bold.white(" System")),console.log(chalk.white(" setup ")+chalk.gray("Configure settings")),console.log(chalk.white(" lang [code] ")+chalk.gray("Switch language (15 supported)")),console.log(chalk.white(" logout ")+chalk.gray("Disconnect")),console.log(chalk.white(" clear ")+chalk.gray("Clear screen")),console.log(chalk.white(" exit ")+chalk.gray("Quit (session saved)")),console.log()):(console.log(chalk.bold.yellow(" Restricted Mode — activate with a token to unlock all commands")),console.log(),console.log(chalk.white(" activate <token> ")+chalk.gray("Unlock full access")),console.log(chalk.white(" ref [topic] ")+chalk.gray("Quick reference")),console.log(chalk.white(" exit ")+chalk.gray("Quit")),console.log())}(a(),J),void K.prompt()}if("more help"===l.toLowerCase()&&b()){Z=!0;try{await e.parseAsync(["node","icoa","exam","more-help"])}catch{}return Z=!1,void K.prompt()}if("continue"===l.toLowerCase())return console.log(),console.log(chalk.green.bold(" ═══ AI4CTF — AI as Your Teammate ═══")),console.log(),console.log(chalk.white(" In AI4CTF, you solve cybersecurity challenges")),console.log(chalk.white(" with AI by your side.")),console.log(),console.log(chalk.white(" In competition, you get AI help at 3 levels:")),console.log(chalk.yellow(" hint a")+chalk.gray(" General guidance (50 uses)")),console.log(chalk.yellow(" hint b")+chalk.gray(" Deep analysis (10 uses)")),console.log(chalk.yellow(" hint c")+chalk.gray(" Critical assist (2 uses)")),console.log(),console.log(chalk.white(" Try it now! Type: ")+chalk.bold.green("ai4ctf")),console.log(chalk.gray(' Chat freely with your AI teammate. Type "exit" when done.')),console.log(),console.log(chalk.gray(" After ai4ctf, try: ")+chalk.bold.red("ctf4ai")+chalk.gray(' — trick the AI into saying "koala"')),console.log(),void K.prompt();if(/^ICOA-[A-Z]{2,3}-\d{1,6}$/i.test(l.trim())){Z=!0;try{await e.parseAsync(["node","icoa","exam","token",l.trim()])}catch{}return Z=!1,void K.prompt()}if(/^[A-Z]{2}[0-9A-HJKMNP-TV-Z]{8}$/i.test(l.trim())){Z=!0;try{await e.parseAsync(["node","icoa","exam","token",l.trim().toUpperCase()])}catch{}return Z=!1,void K.prompt()}const s=l.match(/^exam\s+([A-Z]{2}[0-9A-HJKMNP-TV-Z]{8})$/i);if(s){Z=!0;try{await e.parseAsync(["node","icoa","exam","token",s[1].toUpperCase()])}catch{}return Z=!1,void K.prompt()}const y=l.match(/^exam\s+([A-Z]{2,3})$/i);if(y){Z=!0;try{await e.parseAsync(["node","icoa","exam","list",y[1]])}catch{}return Z=!1,void K.prompt()}if("clear"===l||"cls"===l)return console.clear(),void K.prompt();if(l.startsWith("activate ")){const o=l.slice(9).trim(),e=r(o);return"ok"===e?console.log(chalk.green(" Access granted! Token bound to this device.")):"already_bound"===e?(console.log(),console.log(chalk.red(" Token already activated on a different device.")),console.log(chalk.gray(" Each token binds to the first device that uses it. If you lost the device,")),console.log(chalk.gray(" contact your proctor to have the token re-issued for a new device."))):(console.log(),console.log(chalk.red(" Token not recognized.")),console.log(chalk.gray(" Possible reasons:")),console.log(chalk.white(" • ")+chalk.gray("Typo — tokens are case-insensitive, 10 chars, start with a 2-letter country code (e.g. ")+chalk.cyan("UAK7M2R9Q4")+chalk.gray(")")),console.log(chalk.white(" • ")+chalk.gray("Expired — ask your proctor or organizer for a fresh token")),console.log(chalk.white(" • ")+chalk.gray("Network — verify connection to ")+chalk.cyan("practice.icoa2026.au")),console.log(chalk.gray(" Still stuck? type ")+chalk.cyan("help")+chalk.gray(" or try ")+chalk.cyan("exam demo")+chalk.gray(" for a free practice round."))),console.log(),void K.prompt()}if("activate"===l)return console.log(chalk.gray(" Usage: ")+chalk.white("activate <token>")),console.log(),void K.prompt();const m=b();if(m){const o=l.toUpperCase().trim(),t=o=>{const e=m.questions.find(e=>e.number===o);return!!e&&("ai4ctf"===e.type||"ctf4ai"===e.type||e.options&&!e.options.A&&!e.options.B)},n=o=>{const e="demo-free"!==m.session.examId,t=e&&o>=39?"ctf4ai":e&&o>=31?"ai4ctf":null;console.log(),console.log(chalk.yellow(` Q${o} is a practical question — letters (A/B/C/D) don't apply here.`)),t?(console.log(chalk.white(" Enter the AI chat for this question: ")+chalk.bold.cyan(t)),console.log(chalk.gray(" Or submit a flag directly: ")+chalk.green(`exam answer ${o} ICOA{your_flag}`))):console.log(chalk.gray(" Submit a flag: ")+chalk.green(`exam answer ${o} ICOA{your_flag}`)),console.log()};if(/^[ABCD]$/.test(o)){const l=m._lastQ||1;if(t(l))return n(l),void K.prompt();Z=!0;try{await e.parseAsync(["node","icoa","exam","answer",String(l),o])}catch{}return Z=!1,void K.prompt()}const s=o.match(/^(\d+)\s+([ABCD])$/);if(s){const o=parseInt(s[1],10);if(t(o))return n(o),void K.prompt();Z=!0;try{await e.parseAsync(["node","icoa","exam","answer",s[1],s[2]])}catch{}return Z=!1,void K.prompt()}}const p=l.split(/\s+/)[0].toLowerCase(),d=/^python3?(\.\d+)?$/.test(p),x=l.startsWith("!")||p.startsWith("!")||d;if("selection"===J&&!x&&!["exam","demo","retry","nations","next","prev","continue","setup","lang","ref","ai4ctf","ctf4ai","mark","unmark","review","submit","env"].includes(p)){if(console.log(chalk.gray(" Not available in Selection mode.")),m){const o=m._lastQ||1;console.log(chalk.white(` Resume exam: exam q ${o}`)+chalk.gray(" · ")+chalk.white("A/B/C/D")+chalk.gray(" to answer"))}else console.log(chalk.gray(" Try: demo · setup to switch mode"));return console.log(),void K.prompt()}if("organizer"===J&&!["join","exam","demo","retry","next","prev","logout","setup","lang","ref","ctf","mark","unmark","review","submit"].includes(p))return console.log(chalk.gray(" Not available in Organizer mode. Switch via: setup")),console.log(),void K.prompt();if(!("olympiad"!==J||a()&&c()||i(p)))return console.log(chalk.yellow(" Restricted mode. ")+chalk.gray("Enter your access token:")),console.log(chalk.white(" activate <token>")),console.log(),console.log(chalk.gray(" Free commands: ")+chalk.white("ref [topic]")+chalk.gray(", ")+chalk.white("help")+chalk.gray(", ")+chalk.white("exit")),console.log(),void K.prompt();if(!["join","activate","challenges","ch","open","submit","flag","scoreboard","sb","status","time","ref","shell","files","connect","note","log","lang","setup","env","ai4ctf","model","ctf","exam","demo","retry","nations","next","prev","continue","logout","ctf4ai","mark","unmark","review","submit"].includes(p)){if(B.has(p))return console.log(chalk.red(` Blocked: ${p} is not allowed during competition.`)),console.log(),void K.prompt();if(/(?:^|\s)(?:\/(?!home\/|Users\/|tmp\/)|\.\.\/|~\/)/.test(l)&&!l.startsWith("cd ")){const o=/(?:^|\s)\/(?!home\/\w+\/icoa-workspace|Users\/\w+\/icoa-workspace|tmp\/)/.test(l),e=/\.\./.test(l);if(o||e)return console.log(chalk.red(" Blocked: access outside workspace is not allowed.")),console.log(chalk.gray(` Workspace: ${U}`)),console.log(),void K.prompt()}let o=l.startsWith("!")?l.slice(1).trim():l;if("darwin"===process.platform){const e="/opt/homebrew/opt/python@3.12/bin/python3.12";o=o.replace(/^python3?\s/,`${e} `).replace(/^(python3|python)$/,e)}else if("win32"===process.platform){const e=(()=>{try{return t("py -3 --version",{stdio:["ignore","ignore","ignore"],timeout:1500}),"py -3"}catch{}return"python"})();o=o.replace(/^python3?(\.\d+)?\s/,`${e} `).replace(/^python3?(\.\d+)?$/,e)}else{const e=(()=>{try{return t("which python3.12",{stdio:"ignore"}),"python3.12"}catch{return"python3"}})();o=o.replace(/^python\s/,`${e} `).replace(/^python$/,e)}const e=z();/^(\S*python3?(\.\d+)?)\s*$/.test(o)&&(o=`PYTHONSTARTUP="${function(){const o=D(F(),".icoa");P(o)||R(o,{recursive:!0});const e=D(o,"python-startup.py");return P(e)||N(e,"# ICOA exam interactive startup — auto-loaded by PYTHONSTARTUP\nimport base64, struct, hashlib, re, json, os, sys, binascii\ntry: import requests\nexcept ImportError: pass\ntry: from Crypto.Cipher import AES\nexcept ImportError: pass\ntry: from Crypto.Util.Padding import pad, unpad\nexcept ImportError: pass\ntry: from pwn import xor, p32, u32, p64, u64\nexcept ImportError: pass\ntry: import bs4\nexcept ImportError: pass\ntry: import numpy as np\nexcept ImportError: pass\n"),e}()}" ${o}`,console.log(),console.log(chalk.cyan(" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")),console.log(chalk.bold.white(" Python ready — ICOA exam toolkit pre-loaded")),console.log(chalk.cyan(" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")),console.log(),console.log(chalk.white(" Already imported: ")+chalk.gray("base64, struct, hashlib, re, json, binascii")),console.log(chalk.white(" Also available: ")+chalk.gray("requests, bs4, numpy, AES, pad/unpad, xor, p32/u32/p64/u64")),console.log(),console.log(chalk.yellow(" Quick examples:")),console.log(chalk.gray(' base64.b64decode("aGVsbG8=") ')+chalk.gray("# decode base64")),console.log(chalk.gray(' bytes.fromhex("48656c6c6f") ')+chalk.gray("# hex → bytes")),console.log(chalk.gray(' "ICOA{x}".encode() ')+chalk.gray("# str → bytes")),console.log(chalk.gray(" [chr(c) for c in [73,67,79,65]] ")+chalk.gray("# ASCII codes")),console.log(chalk.gray(' xor(bytes.fromhex("0a2b"), b"IC") ')+chalk.gray("# pwntools XOR")),console.log(),console.log(chalk.gray(" Exit: ")+chalk.white("exit()")+chalk.gray(" or Ctrl-D")),console.log(chalk.cyan(" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")),console.log()),Z=!0;try{q()&&await S()?await O(o,K):await _(o,K,e)}catch{console.log(chalk.yellow(` Command failed: ${p}`))}return Z=!1,console.log(),void K.prompt()}Z=!0;const v=m&&"submit"===l.toLowerCase()?["exam","submit"]:function(o){const e=o.split(/\s+/),t=e[0].toLowerCase(),l=e.slice(1),n={demo:["exam","demo"],retry:["exam","demo-retry"],nations:["exam","nations"],next:["exam","next"],prev:["exam","prev"],mark:["exam","mark",...l],unmark:["exam","unmark",...l],review:["exam","review"],logout:["ctf","logout"],join:["ctf","join",...l],activate:["ctf","activate",...l],challenges:["ctf","challenges"],ch:["ctf","challenges"],open:["ctf","open",...l],submit:["ctf","submit",...l],flag:["ctf","submit",...l],scoreboard:["ctf","scoreboard",...l],sb:["ctf","scoreboard",...l],status:["ctf","status"],time:["ctf","time"]};return n[t]?n[t]:["ref","shell","files","connect","note","log","lang","setup","env","ai4ctf","model","ctf","exam","ctf4ai"].includes(t)?[t,...l]:e}(l),C="ctf"===v[0]&&"join"===v[1];C&&K.pause(),process.exit=()=>{throw new Error(W)};try{await e.parseAsync(["node","icoa",...v])}catch(o){const e=o instanceof Error?o.message:String(o);if(e===W);else if(e.includes("commander.unknownCommand")){const{distance:o}=await import("fastest-levenshtein"),e=["ctf","ref","shell","files","connect","note","log","lang","setup","env","ai4ctf","exam","ctf4ai","theme","clear","cls","quit","exit","back","menu","help","continue","activate","demo","challenges","status","scoreboard","join","logout"],t=p.split(/\s+/)[0]||p;let l={word:"",dist:1/0};for(const n of e){const e=o(t.toLowerCase(),n);e<l.dist&&(l={word:n,dist:e})}console.log(chalk.yellow(` Unknown command: ${p}.`)),l.dist>0&&l.dist<=2&&console.log(chalk.gray(" Did you mean: ")+chalk.bold.cyan(l.word)+chalk.gray("?")),console.log(chalk.gray(" Type ")+chalk.cyan("help")+chalk.gray(" for the full command list."))}else e.includes("commander.")||(e.includes("fetch failed")||e.includes("ECONNREFUSED")||e.includes("ETIMEDOUT"))&&console.log(chalk.yellow(" Network error. Check your connection."))}finally{process.exit=k,Z=!1,C&&K.resume()}u()?K.setPrompt(chalk.magenta("ai4ctf> ")):w()&&K.setPrompt(chalk.red("ctf4ai> ")),console.log(),K.prompt()}),K.on("SIGINT",()=>{if(console.log(),u()||w())console.log(chalk.yellow(" Ctrl+C did not close ICOA CLI — you are still in the AI chat.")),console.log(chalk.white(" Type ")+chalk.bold.cyan("exit")+chalk.white(" to leave the chat and return to the menu."));else if(b()){const o="demo-free"!==b().session.examId;console.log(chalk.yellow(" Ctrl+C did NOT close ICOA CLI.")),console.log(chalk.gray(` Your ${o?"exam":"demo"} is paused and every answer is auto-saved.`)),console.log(),console.log(chalk.white(" Resume: ")+chalk.cyan("exam q 1")+chalk.gray(" · Back to menu: ")+chalk.cyan("back")+chalk.gray(" · Close CLI: ")+chalk.cyan(o?"quit confirm":"quit"))}else console.log(chalk.yellow(" Ctrl+C did not close ICOA CLI — you are still at the ")+chalk.cyan("icoa>")+chalk.yellow(" prompt.")),console.log(chalk.gray(" Keep typing — ")+chalk.cyan("help")+chalk.gray(" lists commands. (Only ")+chalk.cyan("quit")+chalk.gray(" or Ctrl+D actually close the CLI.)"));console.log(),K.prompt()}),K.on("close",()=>{E(),g(),$(),k(0)})}function _(o,t,l){return new Promise(n=>{const s=process.stdin,a=!!s.isTTY&&!!s.isRaw;if(t.pause(),s.isTTY&&"function"==typeof s.setRawMode)try{s.setRawMode(!1)}catch{}const r=e(o,{shell:!0,stdio:"inherit",cwd:l||process.cwd()}),i=()=>{if(s.isTTY&&"function"==typeof s.setRawMode&&a)try{s.setRawMode(!0)}catch{}t.resume(),n()};r.on("close",i),r.on("error",i)})}
@@ -70,16 +70,6 @@ export interface CTFdApiResponse<T> {
70
70
  data: T;
71
71
  errors?: string[];
72
72
  }
73
- export interface CTFdTokenResponse {
74
- success: boolean;
75
- data: {
76
- id: number;
77
- type: string;
78
- value: string;
79
- created: string;
80
- expiration: string;
81
- };
82
- }
83
73
  export interface IcoaConfig {
84
74
  ctfdUrl: string;
85
75
  token: string;
@@ -133,7 +123,6 @@ export interface ChallengeContext {
133
123
  export declare const DEFAULT_BUDGET: HintBudget;
134
124
  export declare const DEFAULT_CONFIG: IcoaConfig;
135
125
  export declare const SUPPORTED_LANGUAGES: readonly ["en", "zh", "ja", "ko", "es", "ar", "fr", "pt", "ru", "hi", "de", "id", "th", "vi", "tr", "uk", "ht", "sw", "uz", "lo"];
136
- export type SupportedLanguage = typeof SUPPORTED_LANGUAGES[number];
137
126
  export interface ExamListItem {
138
127
  id: string;
139
128
  name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "2.19.110",
3
+ "version": "2.19.111",
4
4
  "description": "ICOA CLI — The world's first CLI-native CTF competition terminal",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,8 +17,14 @@
17
17
  "dev": "tsc --watch",
18
18
  "start": "node dist/index.js",
19
19
  "test": "tsc && node --test test/*.test.js",
20
+ "smoke": "tsc && node --test test/smoke/*.test.js",
21
+ "smoke:full": "tsc && SMOKE_LIFECYCLE=1 node --test test/*.test.js test/smoke/*.test.js",
22
+ "smoke:offline": "tsc && SMOKE_NO_NETWORK=1 node --test test/smoke/*.test.js",
23
+ "ux-audit": "node panda/ux-audit.js",
24
+ "ux-record": "bash panda/ux-record.sh",
20
25
  "postinstall": "node -e \"try{require('./dist/postinstall.js')}catch(e){}\"",
21
- "prepublishOnly": "npm run build:release"
26
+ "prepublishOnly": "npm run build:release",
27
+ "prepare": "husky"
22
28
  },
23
29
  "keywords": [
24
30
  "ctf",
@@ -40,8 +46,11 @@
40
46
  "ora": "^8.2.0"
41
47
  },
42
48
  "devDependencies": {
49
+ "@biomejs/biome": "^2.4.13",
43
50
  "@types/node": "^22.15.3",
51
+ "husky": "^9.1.7",
44
52
  "javascript-obfuscator": "^5.4.1",
53
+ "knip": "^6.6.2",
45
54
  "terser": "^5.46.1",
46
55
  "typescript": "^5.8.3"
47
56
  },
@@ -1,7 +0,0 @@
1
- interface TerminalInfo {
2
- name: string;
3
- allowed: boolean;
4
- }
5
- export declare function detectTerminal(): TerminalInfo;
6
- export declare function checkTerminal(): void;
7
- export {};
@@ -1 +0,0 @@
1
- import chalk from"chalk";export function detectTerminal(){const e=process.env.TERM_PROGRAM||"",n=process.platform;return"1"===process.env.ICOA_TERMINAL?{name:"ICOA Terminal",allowed:!0}:"darwin"===n?"Apple_Terminal"===e?{name:"macOS Terminal",allowed:!0}:{name:e||"Unknown macOS terminal",allowed:!1}:"linux"===n?"gnome-terminal"===e||void 0!==process.env.GNOME_TERMINAL_SERVICE?{name:"GNOME Terminal",allowed:!0}:e||"linux"!==process.env.TERM&&"xterm"!==process.env.TERM?{name:e||"Unknown Linux terminal",allowed:!1}:{name:"Linux Console",allowed:!0}:"win32"===n?process.env.PSModulePath?{name:"PowerShell",allowed:!0}:{name:"Windows CMD or other",allowed:!1}:{name:"Unknown",allowed:!1}}export function checkTerminal(){const e=detectTerminal();e.allowed||(console.log(chalk.yellow(` ⚠ Unsupported terminal: ${e.name}`)),console.log(chalk.gray(" Competition requires:")),console.log(chalk.gray(" macOS → Terminal.app (system default)")),console.log(chalk.gray(" Linux → GNOME Terminal (system default)")),console.log(chalk.gray(" Windows → PowerShell (system default)")),console.log())}