icoa-cli 2.19.374 → 2.19.375
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/ai4ctf.js +1 -1
- package/dist/commands/ai4ioai.d.ts +7 -0
- package/dist/commands/ai4ioai.js +1 -0
- package/dist/commands/arena.js +1 -1
- package/dist/commands/board.d.ts +2 -0
- package/dist/commands/board.js +1 -0
- package/dist/commands/ctf4ai-demo.js +1 -1
- package/dist/commands/ctf4vla.js +1 -1
- package/dist/commands/exam.js +1 -1
- package/dist/commands/learn.js +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/board-render.d.ts +14 -0
- package/dist/lib/board-render.js +1 -0
- package/dist/repl.js +1 -1
- package/package.json +1 -1
package/dist/commands/learn.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import chalk from"chalk";import{Interface as e,createInterface as o}from"node:readline";import{spawn as n}from"node:child_process";import{getMainRl as r}from"../lib/main-rl.js";import{existsSync as t}from"node:fs";import{dirname as l,join as a}from"node:path";import{fileURLToPath as s}from"node:url";import{loadCurriculumById as c,validateEAToken as i,syncProgress as u,syncCardFeedback as d}from"../lib/learn-curricula.js";import{normalizeTokenBody as g}from"../lib/token-format.js";import{streamLearnTutor as m,submitPqs as y,viewPqsTop as h,viewHonors as f}from"../lib/learn-ai.js";import{getConfig as p,saveConfig as b}from"../lib/config.js";import{loadLearnState as w,saveLearnState as k,newLearnState as v,updateStreak as A,markCardComplete as C,recordMCQ as I,recordCheck as $,markPracticalComplete as E,addAchievement as _}from"../lib/learn-state.js";import{renderWelcome as T,renderKnowledgeCard as M,renderMCQCard as P,renderMCQFeedback as L,renderPracticalCard as O,renderPracticalSuccess as x,renderSimDemoCard as D,renderMilestone as S,renderArenaTaskCard as q,renderComingSoonCard as j,renderStatus as N,renderPhaseComplete as R,renderCurriculumComplete as W,setLearnAiEnabled as F,setLearnPqsViewCards as B,renderHonorWall as z}from"../lib/learn-render.js";import{printError as U}from"../lib/ui.js";export function registerLearnCommand(J){J.command("learn [token]").description("Enter learn mode (free demo, or team-issued EA/EI/AC/CA/IO/IA token for full curriculum)").action(async J=>{J&&J.trim()||(console.log(),console.log(chalk.gray(" No token given — starting free 11-card demo (")+chalk.bold.green("LEARNDEMO01")+chalk.gray(").")),console.log(chalk.gray(" Full curriculum: ")+chalk.bold.yellow("learn EA|EI|AC|CA|IO|IA + 8 chars")+chalk.gray(" — token from your country team leader.")),console.log(),J="LEARNDEMO01");let Q=J.trim().toUpperCase(),V=null;const H=/^(LEARNDEMO01|AI4CTFDEMO01|CTF4AIDEMO01)$/i.test(Q),K=/^[A-Z]{2}[A-Z0-9]{8}$/i.test(Q);let X=!1;if(H)console.log(),console.log(chalk.gray(" Loading demo curriculum...")),V=await c(Q,Q);else if(K){console.log(),console.log(chalk.gray(" Validating token..."));let e=await i(Q);if(!e.ok){const o=g(Q);if(o!==Q){const n=await i(o);n.ok&&(console.log(chalk.yellow(` Corrected look-alike characters: ${Q} → ${o}`)),e=n,Q=o)}}if(!e.ok)return U(`Token validation failed: ${e.message}`),console.log(),"device_bound"===e.code?(console.log(chalk.gray(" This learn token is bound to the first computer it was used on,")),console.log(chalk.gray(" and this is a different computer.")),console.log(),console.log(chalk.gray(" If you changed computers or reinstalled, ask the coordinator who")),console.log(chalk.gray(" issued your token to move it to this device, then try again."))):(console.log(chalk.gray(" Possible causes:")),console.log(chalk.gray(" · Token expired or revoked")),console.log(chalk.gray(" · Network down (check connection)")),console.log(chalk.gray(" · Typo in token (check 0/O, 1/I/L, V/U)"))),void console.log();if(console.log(chalk.green(` ✓ Token valid · curriculum: ${e.curriculumId} · status: ${e.status}`)+(!0===e.aiEnabled?chalk.bold.magenta(" · ⭐ AI tutor"):"")),X=!0===e.aiEnabled,console.log(chalk.gray(" Loading curriculum… (downloads ~1 MB; up to a minute on slow venue Wi-Fi — please wait)")),V=await c(e.curriculumId||"LEARNDEMO01",Q),!V)return U(`Could not open a learn curriculum for token '${Q}'.`),console.log(),console.log(chalk.gray(" If this is an ")+chalk.bold.yellow("EXAM token")+chalk.gray(", start it with ")+chalk.bold.green(`exam ${Q}`)+chalk.gray(" instead.")),console.log(chalk.gray(" Otherwise: the full curriculum is ~3 MB; on a slow link the download")),console.log(chalk.gray(" can take a while — stay on a stable connection and run ")+chalk.bold.yellow(`learn ${Q}`)+chalk.gray(" again")),console.log(chalk.gray(" (it caches after the first load). If it keeps failing, check ")+chalk.cyan("https://practice.icoa2026.au")+chalk.gray(" is reachable.")),void console.log()}if(!V)return U(`Unknown learn token: ${Q}`),console.log(),console.log(chalk.gray(" Available tokens:")),console.log(chalk.gray(" ")+chalk.bold.green("LEARNDEMO01")+chalk.gray(" free 11-card demo (anyone can use)")),console.log(chalk.gray(" ")+chalk.bold.yellow("EA|EI|AC|CA|IO|IA")+chalk.gray(" + 8 chars full curriculum (issued by team leader)")),console.log(),console.log(chalk.gray(" Tracks: ")+chalk.cyan("EA")+chalk.gray("=ctf4eai · ")+chalk.cyan("AC")+chalk.gray("=ai4ctf · ")+chalk.cyan("CA")+chalk.gray("=ctf4ai · ")+chalk.cyan("IO")+chalk.gray("=ioai · ")+chalk.cyan("IA")+chalk.gray("=iaio")),console.log(),console.log(chalk.gray(" To get a full curriculum, email ")),console.log(chalk.gray(" ")+chalk.cyan("australia@icoa2026.au")+chalk.gray(" or ask your country's team leader.")),void console.log();let Z=w(),G=!1;try{Z&&Z.token===Q?A(Z):(Z=v(Q,V.id,V.totalCards),G=!0),k(Z),T(V,Z,G)}catch{return U("Could not open this curriculum — it may be malformed or not a learn curriculum."),console.log(chalk.gray(" If this is an ")+chalk.bold.yellow("EXAM token")+chalk.gray(", start it with ")+chalk.bold.green(`exam ${Q}`)+chalk.gray(".")),void console.log()}const Y=r(),ee=null!==Y,oe=ee?Y.listeners("line").slice():[];ee&&Y.removeAllListeners("line");const ne=ee?Y:o({input:process.stdin,output:process.stdout,terminal:!0}),re=ee?ne.prompt:null;ee&&(ne.prompt=e.prototype.prompt);const te=()=>{ee&&re&&(ne.prompt=re)},le=()=>{ne.setPrompt(chalk.bold.cyan(ue?"icoa learn ai> ":"icoa learn> ")),ne.prompt()};let ae=null,se=null,ce=null,ie=null,ue=!1,de=!1;const ge=new Map,me=()=>{let e=ge.get(Z.currentCard);return e||(e=[],ge.set(Z.currentCard,e)),e};let ye=!1;const he=new Set;let fe=!1,pe=0;const be=(p().language||"en").toLowerCase().startsWith("zh");F(X),B(he);const we=[];let ke=null;const ve=new Promise(e=>{ke=e}),Ae=()=>{const e=ke;ke=null,e?.()},Ce="1"===process.env.ICOA_LEARN_EMBEDDED,Ie=e=>V.cards.find(o=>o.number===e),$e=e=>{const o=V.modules.find(o=>o.cardRange[1]===e);if(!o)return!1;const n=V.modules.find(e=>e.number===o.number+1);return R(V,Z,o,n),!0},Ee=()=>{fe=!0,console.log(chalk.gray(be?" 按 Enter 进入下一阶段…":" Press Enter to continue to the next phase…"))},_e=()=>{const e=Ie(Z.currentCard);if(!e)return console.log(),console.log(chalk.gray(" No more cards in this curriculum.")),console.log(chalk.gray(" Type ")+chalk.bold.green("status")+chalk.gray(" for the dashboard or ")+chalk.bold.green("quit")+chalk.gray(" to exit.")),void console.log();switch(e.type){case"knowledge":M(e,V),e.check?(ce=e.number,pe=Date.now()):(C(Z,e.number),k(Z));break;case"mcq":P(e,V),ae=e.number;break;case"practical":O(e,V),se=e.number;break;case"sim_demo":D(e,V),C(Z,e.number),k(Z);break;case"milestone":S(e,V),_(Z,e.badge),C(Z,e.number),k(Z);break;case"arena_task":q(e,V),C(Z,e.number),k(Z);break;case"coming_soon":j(e,V),C(Z,e.number),k(Z)}},Te=()=>{Q.endsWith("DEMO01")?console.log(chalk.yellow(be?" AI 导师需要开通 AI 的正式课程 token(demo 是共享的,没有 AI 额度)。":" The AI tutor needs an AI-enabled curriculum token — demos are shared and carry no AI budget.")):(console.log(chalk.yellow(be?" 此 token 未开通 AI 导师(高级功能)。":" This token does not include the AI tutor (premium).")),console.log(chalk.gray(be?" 找你的领队 / 协调人换开通 AI 的 token。":" Ask your team leader / coordinator for an AI-enabled token."))),console.log()},Me=async(e,o)=>{if(X){de=!0;try{console.log(),console.log(chalk.gray("deep"===o?be?" 🤖 AI 导师(深度思考中,思考过程为灰色小字)…":" 🤖 AI tutor (deepthink — reasoning shown in gray)…":be?" 🤖 AI 导师(快速模式)…":" 🤖 AI tutor (quick mode)…")),console.log();let n=!1,r=!1;const t=await m({token:Q,cardNumber:Z.currentCard,question:e,mode:o,history:me().slice(-8)},e=>{"reasoning"===e.kind?(n||(process.stdout.write(" "),n=!0),process.stdout.write(chalk.dim(e.text.replace(/\n/g,"\n ")))):(r||(process.stdout.write(n?"\n\n ":" "),r=!0),process.stdout.write(chalk.white(e.text.replace(/\n/g,"\n "))))});(n||r)&&console.log(),t.ok?t.offtopic?(console.log(),console.log(chalk.yellow(be?" (被判定与本卡主题无关 — 跑题的问题不会被回答,并会拉低本卡的提问评分)":" (judged unrelated to this card — off-topic questions aren't answered and will lower this card's prompt score)"))):(me().push({role:"user",content:e},{role:"assistant",content:t.answer}),null!=t.spent&&null!=t.cap&&t.spent/t.cap>.8&&console.log(chalk.gray(` (${be?"AI 额度":"AI budget"}: ${Math.round(t.spent/1e3)}k / ${Math.round(t.cap/1e3)}k tokens)`))):(console.log(),console.log(chalk.yellow(` ${t.message||(be?"AI 导师暂不可用。":"AI tutor unavailable.")}`))),null!==ce&&console.log(chalk.gray(be?" (本卡的判断题还等着你作答 — y 或 n)":" (the y/n check on this card is still waiting — answer y or n)")),console.log()}finally{de=!1}}else Te()},Pe=e=>e.match(/.{1,64}(\s|$)/g)?.map(e=>e.trim())??[e],Le=async e=>{const o=e.trim().toLowerCase();if(!de){if(null!==ie){const o=e.trim(),n=ie;if(ie=null,""===o)console.log(chalk.gray(be?" 已取消(未提交)。":" Cancelled (nothing submitted)."));else{const e=pe>0?Date.now()-pe:void 0;we.push(d(Q,{curriculum_id:V.id,card_number:n,feedback_text:o,time_on_card_ms:e}).catch(()=>{})),console.log(chalk.green(be?" ✓ 已收到,谢谢帮助完善这张卡。":" ✓ Thanks — your report was recorded."))}return console.log(),void le()}if(ye){ye=!1;const o=e.trim().toLowerCase();return"y"===o||"yes"===o?await(async()=>{de=!0;try{console.log(),console.log(chalk.gray(be?" 📊 提交中 — Panda 正在评审你的提问(通常几十秒,最长一两分钟,请稍候)…":" 📊 Submitting — Panda is reviewing how you asked (usually well under two minutes)…"));const e=await y({token:Q,cardNumber:Z.currentCard});if(console.log(),!e.ok)return console.log(chalk.yellow(` ${e.message||(be?"评审暂不可用。":"Review unavailable.")}`)),void console.log();(e=>{const o="green"===e.tier?chalk.bold.green:"blue"===e.tier?chalk.bold.cyan:"yellow"===e.tier?chalk.bold.yellow:chalk.bold.red;if(console.log(chalk.gray(" ─────────────────────────────────────────")),console.log(chalk.white(be?" 提问评分 ":" Prompt score ")+o(`${e.score??0} / 100`)+chalk.gray(" "+(e=>{switch(e){case"green":return"🟢 "+(be?"你把 AI 当作被审视的研究伙伴":"you used the AI as a research partner");case"blue":return"🔵 "+(be?"你把 AI 当作实验助手":"you used the AI as a lab assistant");case"yellow":return"🟡 "+(be?"你把 AI 当作讲师":"you used the AI as a lecturer");default:return"🔴 "+(be?"你把 AI 当作答案机":"you used the AI as an answer machine")}})(e.tier))),e.manipulation&&console.log(chalk.red(be?" ⚠ 检测到试图操纵评分,分数已按规则大幅降低":" ⚠ an attempt to manipulate the score was detected — the score was reduced sharply")),null!=e.rank&&null!=e.entrants){const o=!0===e.inTop3,n=be?` 本卡排名 #${e.rank} / ${e.entrants}`:` Rank on this card: #${e.rank} of ${e.entrants}`;console.log((o?chalk.bold.green(n):chalk.white(n))+(o?chalk.bold.magenta(be?" — 进入 TOP 3!输入 /view 看另两名的提问":" — TOP 3! type /view to read the other top prompts"):""))}if(console.log(chalk.gray(be?` 第 ${e.submissionIndex??"?"} / 2 次提交 · 剩余 ${e.submissionsLeft??0} 次(可先追加 /ai 对话再交)`:` Attempt ${e.submissionIndex??"?"} of 2 · ${e.submissionsLeft??0} left (extend the /ai conversation before your next one)`)),e.feedback){console.log();for(const o of Pe(e.feedback))console.log(chalk.white(" "+o))}e.improve&&(console.log(),console.log(chalk.cyan(be?" 改进提问: ":" Ask better: ")+chalk.gray(e.improve))),console.log(chalk.gray(" ─────────────────────────────────────────")),null!=e.spent&&null!=e.cap&&e.spent/e.cap>.8&&console.log(chalk.gray(` (${be?"AI 额度":"AI budget"}: ${Math.round(e.spent/1e3)}k / ${Math.round(e.cap/1e3)}k tokens)`)),console.log()})(e),!0===e.inTop3?he.add(Z.currentCard):he.delete(Z.currentCard);const o=await f({token:Q,curriculumId:V.id,cardNumber:Z.currentCard});o.ok&&o.totals&&o.chapters&&(z({totals:o.totals,chapters:o.chapters,floor:o.floor}),console.log())}finally{de=!1}})():(console.log(chalk.gray(be?" 已取消(未提交,不消耗次数)。":" Cancelled — nothing submitted, no attempt used.")),console.log()),null!==ce&&(console.log(chalk.gray(be?" (本卡的判断题还等着你作答 — y 或 n)":" (the y/n check on this card is still waiting — answer y or n)")),console.log()),void le()}if(fe)return fe=!1,_e(),void le();if(o){if(ue){if(["q","quit","exit","back","b","menu"].includes(o))return ue=!1,console.log(chalk.gray(be?" 已退出 AI 导师,回到卡片。":" Left the AI tutor — back to the card.")),console.log(),void le();const n=e.trim().startsWith("!"),r=n?e.trim().slice(1).trim():e.trim();return r&&await Me(r,n?"fast":"deep"),void le()}if("/ai"===o||"/ai!"===o||o.startsWith("/ai ")||o.startsWith("/ai! ")){if(!X)return Te(),void le();const n=o.startsWith("/ai!"),r=e.trim().slice(n?4:3).trim();if(!r){ue=!0;const e=Ie(Z.currentCard),o=e?e.title??e.badge??"":"";return console.log(),console.log(chalk.bold.magenta(" 🤖 AI ")+chalk.bold.white(be?"AI 导师":"AI tutor")+(o?chalk.gray(be?` — 主题「${o}」`:` — topic “${o}”`):"")),console.log(chalk.gray(be?" 直接输入问题(默认深度思考) · ! 开头 = 快答 · q = 返回卡片":" Type a question (deepthink by default) · prefix ! = quick answer · q = back to card")),console.log(),void le()}return await Me(r,n?"fast":"deep"),void le()}if("/submit"===o||o.startsWith("/submit ")||"/research"===o||o.startsWith("/research ")){if(!X)return Te(),void le();const e=Ie(Z.currentCard),o=e?e.title??e.badge??"":"";return console.log(),console.log(chalk.bold.magenta(" 📊 ")+chalk.bold.white(be?"Panda 提问评审":"Panda's prompt review")+(o?chalk.gray(be?` — 主题「${o}」`:` — topic “${o}”`):"")),console.log(chalk.gray(be?" 评的是你在本卡上「怎么问 AI」— 你与 /ai 导师的整段对话会被":" It scores HOW you asked the AI on this card — your whole /ai")),console.log(chalk.gray(be?" 整体评审,给出分数、名次和改进建议。":" conversation is reviewed for a score, a rank, and advice.")),console.log(chalk.gray(be?" 每张卡最多 2 次评分;评出的最好成绩进本卡 Top-3 榜。":" Max 2 graded attempts per card; your best enters the card’s top-3 board.")),console.log(),console.log(chalk.white(be?" 确认提交本卡对话评分?":" Submit this card’s conversation for grading?")+chalk.gray(be?" y = 提交 · 其他 = 取消":" y = submit · anything else = cancel")),console.log(),ye=!0,void le()}if("/view"===o)return X?(await(async()=>{de=!0;try{console.log();const e=await h({token:Q,cardNumber:Z.currentCard});if(!e.ok)return console.log(chalk.yellow(` ${e.message||(be?"暂时无法查看。":"View unavailable right now.")}`)),"not_top3"===e.code&&he.delete(Z.currentCard),void console.log();null!=e.myRank&&e.myRank<=3&&he.add(Z.currentCard),console.log(chalk.bold.white(be?` 🏆 本卡 Top-3 提问 — 第 ${Z.currentCard} 张 · ${e.entrants??0} 人上榜`:` 🏆 Top-3 prompts — card ${Z.currentCard} · ${e.entrants??0} entrant(s)`));for(const o of e.top3??[]){const e="green"===o.tier?"🟢":"blue"===o.tier?"🔵":"yellow"===o.tier?"🟡":"🔴";console.log(),console.log(chalk.bold.cyan(` ── Rank ${o.rank}`)+(o.me?chalk.bold.magenta(" (me)"):"")+chalk.white(` · ${o.score}`)+` ${e}`),o.questions.forEach((e,o)=>{const n=Pe(e);console.log(chalk.gray(` Q${o+1} `)+chalk.white(n[0]??""));for(const e of n.slice(1))console.log(chalk.white(" "+e))}),0===o.questions.length&&console.log(chalk.gray(be?" (无可显示的提问)":" (no questions to show)"))}console.log()}finally{de=!1}})(),void le()):(Te(),void le());if("/honors"===o||"honors"===o)return H?(console.log(),console.log(chalk.gray(be?" 荣誉榜属于正式 learn token — 用团队签发的 token 进来即可查看和参赛。":" The honor board belongs to full learn tokens — join with a team-issued token to view and compete.")),console.log(),void le()):(await(async()=>{de=!0;try{console.log();const e=await f({token:Q,curriculumId:V.id,cardNumber:Z.currentCard});if(!e.ok||!e.totals||!e.chapters)return console.log(chalk.yellow(` ${e.message||(be?"荣誉榜暂时不可用。":"Honor board unavailable right now.")}`)),void console.log();if(console.log(chalk.bold.white(be?` 🏆 本卡实时 Top-3 — 第 ${Z.currentCard} 张`:` 🏆 Live top-3 — card ${Z.currentCard}`)),e.board&&0!==e.board.length)for(const o of e.board){const e="green"===o.tier?"🟢":"blue"===o.tier?"🔵":"yellow"===o.tier?"🟡":"🔴";console.log(chalk.white(` ${o.icon} `)+chalk.bold.cyan(`#${o.rank}`)+chalk.white(` · ${o.score} `)+e+(o.me?chalk.bold.magenta(" (me)"):""))}else console.log(chalk.gray(be?" (本卡还没有人上榜)":" (no one has medaled on this card yet)"));console.log(),z({totals:e.totals,chapters:e.chapters,floor:e.floor}),console.log()}finally{de=!1}})(),void le());if("menu"!==o&&"menu confirm"!==o)if("quit"!==o&&"exit"!==o&&"q"!==o){if("status"===o)return N(V,Z),void le();if("lang"===o||o.startsWith("lang ")){const e=o.slice(4).trim();if(!e)return console.log(chalk.gray(" 当前语言 / current: ")+chalk.white(p().language||"en")+chalk.gray(" · ")+chalk.bold.green("lang zh")+chalk.gray(" / ")+chalk.bold.green("lang en")),void le();const n=V.cards.some(e=>null!=e._zh),r=e.toLowerCase().startsWith("zh")?"zh":e.toLowerCase(),t="en"===r||"zh"===r&&n,l=t?r:"en";return b({language:l}),t?console.log(chalk.green(" ✓ ")+chalk.gray("语言 / language → ")+chalk.white(l)):console.log(chalk.yellow(" ! ")+chalk.gray(`${e} 本课暂无翻译,已用英文 / not available — using English`)),ce=null,ae=null,se=null,ie=null,fe=!1,ye=!1,_e(),void le()}if("sim"===o){const e=Ie(Z.currentCard);return e&&"sim_demo"===e.type?(function(e){const o=function(){const e=l(s(import.meta.url)),o=[a(e,"..","..","panda","mujoco-launcher.py"),a(e,"..","..","..","panda","mujoco-launcher.py")];for(const e of o)if(t(e))return e;return null}();if(!o)return console.log(chalk.yellow(" MuJoCo launcher not found.")),console.log(chalk.gray(" Get it from: https://github.com/newaipanda/ICOA_CLI/blob/main/panda/mujoco-launcher.py")),void console.log(chalk.gray(" Or use the sandbox-vla docker image (Phase 3)."));const r={baseline:"baseline",prompt_injected:"prompt_inj",patch_attacked:"patch",modality_confused:"confused"}[e]||"baseline";console.log(chalk.gray(` Launching MuJoCo viewer (scenario: ${r})...`)),console.log(chalk.gray(" Close the window or press ESC to return to learn mode.")),n("python3",[o,r,"--seconds","5"],{stdio:"inherit"}).on("exit",e=>{0!==e?console.log(chalk.yellow(` MuJoCo exited with code ${e} (install: pip install mujoco)`)):console.log(chalk.gray(" Returned from sim."))})}(e.simAction),void le()):(console.log(chalk.gray(" (sim only available on simulation cards)")),void le())}if("bookmark"===o){const e=Z.currentCard;return Z.bookmarks.includes(e)||Z.bookmarks.push(e),k(Z),console.log(chalk.gray(` ✓ Card ${e} bookmarked.`)),void le()}if("e"===o)return ie=Z.currentCard,console.log(chalk.gray(be?" 描述问题(回车提交 / 空行取消):":" Describe the issue (Enter to submit / blank to cancel):")),void le();if("back"===o||"b"===o)return Z.currentCard>1&&(Z.currentCard-=1),ae=null,se=null,ce=null,k(Z),_e(),void le();if(o.startsWith("card ")){const e=o.slice(5).trim(),n=parseInt(e,10);return!Number.isInteger(n)||n<1||n>V.totalCards?(console.log(chalk.yellow(` Card number must be 1..${V.totalCards}. Try: `)+chalk.bold.green("card 1")),void le()):(Z.currentCard=n,ae=null,se=null,ce=null,k(Z),_e(),void le())}if(null!==ce&&["y","yes","n","no"].includes(o)){const e=Ie(ce);if(e&&"knowledge"===e.type&&e.check){const n=o.startsWith("y")?"y":"n",r=n===e.check.answer,t=Date.now()-pe;C(Z,e.number),$(Z,e.number,{answer:n,correct:r,submittedAt:(new Date).toISOString()}),k(Z);const{renderCheckFeedback:l}=await import("../lib/learn-render.js");if(l(e,n,r,!1),we.push(u(Q,{card_number:e.number,event_type:"check_answered",check_answer:n,check_correct:r,time_on_card_ms:t}).catch(()=>{})),ce=null,e.solution&&e.solution.length>0)return void le();if(Z.currentCard<V.totalCards){const e=Z.currentCard;Z.currentCard+=1,k(Z),$e(e)?Ee():_e(),le()}else W(V,Z),le();return}}if(null!==ae&&["a","b","c","d"].includes(o)){const e=Ie(ae);if(e&&"mcq"===e.type){const n=o.toUpperCase(),r=n===e.answer;return I(Z,e.number,{answer:n,correct:r,submittedAt:(new Date).toISOString()}),C(Z,e.number),k(Z),L(e,n,r,Z),we.push(u(Q,{card_number:e.number,event_type:"mcq_answered",mcq_answer:n,mcq_correct:r}).catch(()=>{})),ae=null,void(r?Z.currentCard<V.totalCards?(Z.currentCard+=1,k(Z),_e()):(W(V,Z),le()):le())}}if(null!==se){if("done"===o){const e=Ie(se);if(e&&"practical"===e.type)return E(Z,e.number),C(Z,e.number),k(Z),x(e),se=null,void le()}if("skip"===o)return C(Z,se),k(Z),console.log(chalk.gray(" Skipped (counts as not completed).")),console.log(),se=null,void le()}if("ok"===o||"next"===o||"continue"===o||"n"===o){if(null!==ae)return console.log(chalk.yellow(" Please answer the MCQ first (A / B / C / D).")),void le();if(null!==se)return console.log(chalk.yellow(" Please type ")+chalk.bold.green("done")+chalk.yellow(" or ")+chalk.bold.yellow("skip")+chalk.yellow(" for the practical.")),void le();if(null!==ce)return console.log(chalk.yellow(" Please answer the check above (")+chalk.bold.green("y")+chalk.yellow(" or ")+chalk.bold.green("n")+chalk.yellow(").")),void le();const e=Z.currentCard;return Z.currentCard+=1,k(Z),Z.currentCard>V.totalCards?W(V,Z):$e(e)?Ee():_e(),void le()}if(null!==ce)return console.log(chalk.yellow(" Please answer the check above (")+chalk.bold.green("y")+chalk.yellow(" or ")+chalk.bold.green("n")+chalk.yellow(").")),void le();if(null!==ae)return console.log(chalk.yellow(" Please answer the MCQ first (A / B / C / D).")),void le();if(null!==se)return console.log(chalk.yellow(" Please type ")+chalk.bold.green("done")+chalk.yellow(" or ")+chalk.bold.yellow("skip")+chalk.yellow(" for the practical.")),void le();console.log(chalk.gray(" Unknown command. Try: ")+chalk.white("ok")+chalk.gray(" / ")+chalk.white("status")+chalk.gray(" / ")+chalk.white("card N")+chalk.gray(" / ")+chalk.white("/ai")+chalk.gray(" / ")+chalk.white("quit")),le()}else if(we.length>0&&await Promise.race([Promise.allSettled(we),new Promise(e=>setTimeout(e,5e3))]),console.log(),console.log(chalk.gray(" Saved. See you next session.")),console.log(chalk.gray(" Streak: ")+chalk.yellow(`🔥 ${Z.streakDays} day(s)`)),console.log(),ee){ne.removeAllListeners("line"),te();for(const e of oe)ne.on("line",e);ne.prompt(),Ae()}else ne.removeAllListeners("line"),ne.close();else if(we.length>0&&await Promise.race([Promise.allSettled(we),new Promise(e=>setTimeout(e,3e3))]),ne.removeAllListeners("line"),te(),ee||!Ce){const{returnToMainMenu:e}=await import("../lib/menu-nav.js");e(ee?ne:void 0),Ae()}else ne.close()}else le()}};ne.on("line",e=>{Le(e).catch(e=>{console.log(),console.log(chalk.red(" ⚠ Something went wrong handling that input.")),console.log(chalk.gray(` ${e instanceof Error?e.message:String(e)}`)),console.log(chalk.gray(" Progress is saved. Type ")+chalk.bold.green("ok")+chalk.gray(" to continue or ")+chalk.bold.green("quit")+chalk.gray(" to exit.")),le()})}),ee||ne.on("close",async()=>{we.length>0&&await Promise.race([Promise.allSettled(we),new Promise(e=>setTimeout(e,5e3))]),Ce?Ae():process.exit(0)});try{_e(),le()}catch{if(U("Could not render this card — the curriculum may be malformed."),ee){ne.removeAllListeners("line"),te();for(const e of oe)ne.on("line",e);ne.prompt()}else ne.close();Ae()}await ve}),J.command("learn-pull [id]").description("Pre-download learn curricula to ~/.icoa/learn-cache/ for offline use (default: all)").action(async e=>{const o=e?[e.trim()]:["LEARNDEMO01","AI4CTFDEMO01","CTF4AIDEMO01","ai4ctf-96","ai4ctf-360","ctf4ai-96","ctf4ai-360","ctf4eai-96","ctf4eai-360","embodied-ai-100","embodied-ai-480"];console.log(),console.log(chalk.bold.cyan(" ICOA learn-pull — fetching curricula for offline use")),console.log(chalk.gray(" Server: https://practice.icoa2026.au")),console.log();let n=0,r=0,t=0;for(const e of o){process.stdout.write(` ${e.padEnd(22)} `);const o=Date.now(),l=await c(e),a=Date.now()-o;if(l){const e=l.cards,o=Array.isArray(e)?e.length:0,r=JSON.stringify(l).length;t+=r,console.log(chalk.green("✓ ")+chalk.gray(`${o.toString().padStart(3)} cards · ${(r/1024).toFixed(1).padStart(6)} KB · ${a.toString().padStart(4)} ms`)),n++}else console.log(chalk.red("✗ failed (server unreachable or unknown id)")),r++}console.log(),console.log(chalk.gray(" Summary: ")+chalk.green.bold(`${n} cached`)+chalk.gray(` · ${r} failed · total ${(t/1024).toFixed(0)} KB`)),console.log(chalk.gray(" Cache: ")+chalk.cyan("~/.icoa/learn-cache/")),console.log()})}
|
|
1
|
+
import chalk from"chalk";import{Interface as o,createInterface as e}from"node:readline";import{spawn as r}from"node:child_process";import{getMainRl as n}from"../lib/main-rl.js";import{handleAi4ioaiMessage as t,startAi4ioaiChat as l}from"./ai4ioai.js";import{existsSync as a}from"node:fs";import{dirname as s,join as i}from"node:path";import{fileURLToPath as c}from"node:url";import{loadCurriculumById as u,validateEAToken as d,syncProgress as g,syncCardFeedback as m}from"../lib/learn-curricula.js";import{normalizeTokenBody as y}from"../lib/token-format.js";import{streamLearnTutor as h,submitPqs as f,viewPqsTop as p,viewHonors as w}from"../lib/learn-ai.js";import{getConfig as b,saveConfig as k}from"../lib/config.js";import{loadLearnState as v,saveLearnState as A,newLearnState as C,updateStreak as I,markCardComplete as $,recordMCQ as E,recordCheck as _,markPracticalComplete as T,addAchievement as M}from"../lib/learn-state.js";import{renderWelcome as P,renderKnowledgeCard as x,renderMCQCard as L,renderMCQFeedback as O,renderPracticalCard as D,renderPracticalSuccess as S,renderSimDemoCard as q,renderMilestone as j,renderArenaTaskCard as N,renderComingSoonCard as R,renderStatus as W,renderPhaseComplete as F,renderCurriculumComplete as B,setLearnAiEnabled as z,setLearnPqsViewCards as U,renderHonorWall as J}from"../lib/learn-render.js";import{printError as Q}from"../lib/ui.js";export function registerLearnCommand(V){V.command("learn [token]").description("Enter learn mode (free demo, or team-issued EA/EI/AC/CA/IO/IA token for full curriculum)").action(async V=>{V&&V.trim()||(console.log(),console.log(chalk.gray(" No token given — starting free 11-card demo (")+chalk.bold.green("LEARNDEMO01")+chalk.gray(").")),console.log(chalk.gray(" Full curriculum: ")+chalk.bold.yellow("learn EA|EI|AC|CA|IO|IA + 8 chars")+chalk.gray(" — token from your country team leader.")),console.log(),V="LEARNDEMO01");let H=V.trim().toUpperCase(),K=null;const X=/^(LEARNDEMO01|AI4CTFDEMO01|CTF4AIDEMO01)$/i.test(H),Z=/^[A-Z]{2}[A-Z0-9]{8}$/i.test(H);let G=!1;if(X)console.log(),console.log(chalk.gray(" Loading demo curriculum...")),K=await u(H,H);else if(Z){console.log(),console.log(chalk.gray(" Validating token..."));let o=await d(H);if(!o.ok){const e=y(H);if(e!==H){const r=await d(e);r.ok&&(console.log(chalk.yellow(` Corrected look-alike characters: ${H} → ${e}`)),o=r,H=e)}}if(!o.ok)return Q(`Token validation failed: ${o.message}`),console.log(),"device_bound"===o.code?(console.log(chalk.gray(" This learn token is bound to the first computer it was used on,")),console.log(chalk.gray(" and this is a different computer.")),console.log(),console.log(chalk.gray(" If you changed computers or reinstalled, ask the coordinator who")),console.log(chalk.gray(" issued your token to move it to this device, then try again."))):(console.log(chalk.gray(" Possible causes:")),console.log(chalk.gray(" · Token expired or revoked")),console.log(chalk.gray(" · Network down (check connection)")),console.log(chalk.gray(" · Typo in token (check 0/O, 1/I/L, V/U)"))),void console.log();if(console.log(chalk.green(` ✓ Token valid · curriculum: ${o.curriculumId} · status: ${o.status}`)+(!0===o.aiEnabled?chalk.bold.magenta(" · ⭐ AI tutor"):"")),G=!0===o.aiEnabled,console.log(chalk.gray(" Loading curriculum… (downloads ~1 MB; up to a minute on slow venue Wi-Fi — please wait)")),K=await u(o.curriculumId||"LEARNDEMO01",H),!K)return Q(`Could not open a learn curriculum for token '${H}'.`),console.log(),console.log(chalk.gray(" If this is an ")+chalk.bold.yellow("EXAM token")+chalk.gray(", start it with ")+chalk.bold.green(`exam ${H}`)+chalk.gray(" instead.")),console.log(chalk.gray(" Otherwise: the full curriculum is ~3 MB; on a slow link the download")),console.log(chalk.gray(" can take a while — stay on a stable connection and run ")+chalk.bold.yellow(`learn ${H}`)+chalk.gray(" again")),console.log(chalk.gray(" (it caches after the first load). If it keeps failing, check ")+chalk.cyan("https://practice.icoa2026.au")+chalk.gray(" is reachable.")),void console.log()}if(!K)return Q(`Unknown learn token: ${H}`),console.log(),console.log(chalk.gray(" Available tokens:")),console.log(chalk.gray(" ")+chalk.bold.green("LEARNDEMO01")+chalk.gray(" free 11-card demo (anyone can use)")),console.log(chalk.gray(" ")+chalk.bold.yellow("EA|EI|AC|CA|IO|IA")+chalk.gray(" + 8 chars full curriculum (issued by team leader)")),console.log(),console.log(chalk.gray(" Tracks: ")+chalk.cyan("EA")+chalk.gray("=ctf4eai · ")+chalk.cyan("AC")+chalk.gray("=ai4ctf · ")+chalk.cyan("CA")+chalk.gray("=ctf4ai · ")+chalk.cyan("IO")+chalk.gray("=ioai · ")+chalk.cyan("IA")+chalk.gray("=iaio")),console.log(),console.log(chalk.gray(" To get a full curriculum, email ")),console.log(chalk.gray(" ")+chalk.cyan("australia@icoa2026.au")+chalk.gray(" or ask your country's team leader.")),void console.log();let Y=v(),oo=!1;try{Y&&Y.token===H?I(Y):(Y=C(H,K.id,K.totalCards),oo=!0),A(Y),P(K,Y,oo)}catch{return Q("Could not open this curriculum — it may be malformed or not a learn curriculum."),console.log(chalk.gray(" If this is an ")+chalk.bold.yellow("EXAM token")+chalk.gray(", start it with ")+chalk.bold.green(`exam ${H}`)+chalk.gray(".")),void console.log()}const eo=n(),ro=null!==eo,no=ro?eo.listeners("line").slice():[];ro&&eo.removeAllListeners("line");const to=ro?eo:e({input:process.stdin,output:process.stdout,terminal:!0}),lo=ro?to.prompt:null;ro&&(to.prompt=o.prototype.prompt);const ao=()=>{ro&&lo&&(to.prompt=lo)},so=()=>{to.setPrompt(ho?chalk.bold.green("icoa ai4ioai> "):chalk.bold.cyan(mo?"icoa learn ai> ":"icoa learn> ")),to.prompt()};let io=null,co=null,uo=null,go=null,mo=!1,yo=!1,ho=!1;const fo=new Map,po=()=>{let o=fo.get(Y.currentCard);return o||(o=[],fo.set(Y.currentCard,o)),o};let wo=!1;const bo=new Set;let ko=!1,vo=0;const Ao=(b().language||"en").toLowerCase().startsWith("zh");z(G),U(bo);const Co=[];let Io=null;const $o=new Promise(o=>{Io=o}),Eo=()=>{const o=Io;Io=null,o?.()},_o="1"===process.env.ICOA_LEARN_EMBEDDED,To=o=>K.cards.find(e=>e.number===o),Mo=o=>{const e=K.modules.find(e=>e.cardRange[1]===o);if(!e)return!1;const r=K.modules.find(o=>o.number===e.number+1);return F(K,Y,e,r),!0},Po=()=>{ko=!0,console.log(chalk.gray(Ao?" 按 Enter 进入下一阶段…":" Press Enter to continue to the next phase…"))},xo=()=>{const o=To(Y.currentCard);if(!o)return console.log(),console.log(chalk.gray(" No more cards in this curriculum.")),console.log(chalk.gray(" Type ")+chalk.bold.green("status")+chalk.gray(" for the dashboard or ")+chalk.bold.green("quit")+chalk.gray(" to exit.")),void console.log();switch(o.type){case"knowledge":x(o,K),o.check?(uo=o.number,vo=Date.now()):($(Y,o.number),A(Y));break;case"mcq":L(o,K),io=o.number;break;case"practical":D(o,K),co=o.number;break;case"sim_demo":q(o,K),$(Y,o.number),A(Y);break;case"milestone":j(o,K),M(Y,o.badge),$(Y,o.number),A(Y);break;case"arena_task":N(o,K),$(Y,o.number),A(Y);break;case"coming_soon":R(o,K),$(Y,o.number),A(Y)}},Lo=()=>{H.endsWith("DEMO01")?console.log(chalk.yellow(Ao?" AI 导师需要开通 AI 的正式课程 token(demo 是共享的,没有 AI 额度)。":" The AI tutor needs an AI-enabled curriculum token — demos are shared and carry no AI budget.")):(console.log(chalk.yellow(Ao?" 此 token 未开通 AI 导师(高级功能)。":" This token does not include the AI tutor (premium).")),console.log(chalk.gray(Ao?" 找你的领队 / 协调人换开通 AI 的 token。":" Ask your team leader / coordinator for an AI-enabled token."))),console.log()},Oo=async(o,e)=>{if(G){yo=!0;try{console.log(),console.log(chalk.gray("deep"===e?Ao?" 🤖 AI 导师(深度思考中,思考过程为灰色小字)…":" 🤖 AI tutor (deepthink — reasoning shown in gray)…":Ao?" 🤖 AI 导师(快速模式)…":" 🤖 AI tutor (quick mode)…")),console.log();let r=!1,n=!1;const t=await h({token:H,cardNumber:Y.currentCard,question:o,mode:e,history:po().slice(-8)},o=>{"reasoning"===o.kind?(r||(process.stdout.write(" "),r=!0),process.stdout.write(chalk.dim(o.text.replace(/\n/g,"\n ")))):(n||(process.stdout.write(r?"\n\n ":" "),n=!0),process.stdout.write(chalk.white(o.text.replace(/\n/g,"\n "))))});(r||n)&&console.log(),t.ok?t.offtopic?(console.log(),console.log(chalk.yellow(Ao?" (被判定与本卡主题无关 — 跑题的问题不会被回答,并会拉低本卡的提问评分)":" (judged unrelated to this card — off-topic questions aren't answered and will lower this card's prompt score)"))):(po().push({role:"user",content:o},{role:"assistant",content:t.answer}),null!=t.spent&&null!=t.cap&&t.spent/t.cap>.8&&console.log(chalk.gray(` (${Ao?"AI 额度":"AI budget"}: ${Math.round(t.spent/1e3)}k / ${Math.round(t.cap/1e3)}k tokens)`))):(console.log(),console.log(chalk.yellow(` ${t.message||(Ao?"AI 导师暂不可用。":"AI tutor unavailable.")}`))),null!==uo&&console.log(chalk.gray(Ao?" (本卡的判断题还等着你作答 — y 或 n)":" (the y/n check on this card is still waiting — answer y or n)")),console.log()}finally{yo=!1}}else Lo()},Do=o=>o.match(/.{1,64}(\s|$)/g)?.map(o=>o.trim())??[o],So=async o=>{const e=o.trim().toLowerCase();if(!yo){if(null!==go){const e=o.trim(),r=go;if(go=null,""===e)console.log(chalk.gray(Ao?" 已取消(未提交)。":" Cancelled (nothing submitted)."));else{const o=vo>0?Date.now()-vo:void 0;Co.push(m(H,{curriculum_id:K.id,card_number:r,feedback_text:e,time_on_card_ms:o}).catch(()=>{})),console.log(chalk.green(Ao?" ✓ 已收到,谢谢帮助完善这张卡。":" ✓ Thanks — your report was recorded."))}return console.log(),void so()}if(wo){wo=!1;const e=o.trim().toLowerCase();return"y"===e||"yes"===e?await(async()=>{yo=!0;try{console.log(),console.log(chalk.gray(Ao?" 📊 提交中 — Panda 正在评审你的提问(通常几十秒,最长一两分钟,请稍候)…":" 📊 Submitting — Panda is reviewing how you asked (usually well under two minutes)…"));const o=await f({token:H,cardNumber:Y.currentCard});if(console.log(),!o.ok)return console.log(chalk.yellow(` ${o.message||(Ao?"评审暂不可用。":"Review unavailable.")}`)),void console.log();(o=>{const e="green"===o.tier?chalk.bold.green:"blue"===o.tier?chalk.bold.cyan:"yellow"===o.tier?chalk.bold.yellow:chalk.bold.red;if(console.log(chalk.gray(" ─────────────────────────────────────────")),console.log(chalk.white(Ao?" 提问评分 ":" Prompt score ")+e(`${o.score??0} / 100`)+chalk.gray(" "+(o=>{switch(o){case"green":return"🟢 "+(Ao?"你把 AI 当作被审视的研究伙伴":"you used the AI as a research partner");case"blue":return"🔵 "+(Ao?"你把 AI 当作实验助手":"you used the AI as a lab assistant");case"yellow":return"🟡 "+(Ao?"你把 AI 当作讲师":"you used the AI as a lecturer");default:return"🔴 "+(Ao?"你把 AI 当作答案机":"you used the AI as an answer machine")}})(o.tier))),o.manipulation&&console.log(chalk.red(Ao?" ⚠ 检测到试图操纵评分,分数已按规则大幅降低":" ⚠ an attempt to manipulate the score was detected — the score was reduced sharply")),null!=o.rank&&null!=o.entrants){const e=!0===o.inTop3,r=Ao?` 本卡排名 #${o.rank} / ${o.entrants}`:` Rank on this card: #${o.rank} of ${o.entrants}`;console.log((e?chalk.bold.green(r):chalk.white(r))+(e?chalk.bold.magenta(Ao?" — 进入 TOP 3!输入 /view 看另两名的提问":" — TOP 3! type /view to read the other top prompts"):""))}if(console.log(chalk.gray(Ao?` 第 ${o.submissionIndex??"?"} / 2 次提交 · 剩余 ${o.submissionsLeft??0} 次(可先追加 /ai 对话再交)`:` Attempt ${o.submissionIndex??"?"} of 2 · ${o.submissionsLeft??0} left (extend the /ai conversation before your next one)`)),o.feedback){console.log();for(const e of Do(o.feedback))console.log(chalk.white(" "+e))}o.improve&&(console.log(),console.log(chalk.cyan(Ao?" 改进提问: ":" Ask better: ")+chalk.gray(o.improve))),console.log(chalk.gray(" ─────────────────────────────────────────")),null!=o.spent&&null!=o.cap&&o.spent/o.cap>.8&&console.log(chalk.gray(` (${Ao?"AI 额度":"AI budget"}: ${Math.round(o.spent/1e3)}k / ${Math.round(o.cap/1e3)}k tokens)`)),console.log()})(o),!0===o.inTop3?bo.add(Y.currentCard):bo.delete(Y.currentCard);const e=await w({token:H,curriculumId:K.id,cardNumber:Y.currentCard});e.ok&&e.totals&&e.chapters&&(J({totals:e.totals,chapters:e.chapters,floor:e.floor}),console.log())}finally{yo=!1}})():(console.log(chalk.gray(Ao?" 已取消(未提交,不消耗次数)。":" Cancelled — nothing submitted, no attempt used.")),console.log()),null!==uo&&(console.log(chalk.gray(Ao?" (本卡的判断题还等着你作答 — y 或 n)":" (the y/n check on this card is still waiting — answer y or n)")),console.log()),void so()}if(ko)return ko=!1,xo(),void so();if(e){if(ho)return"exit"===await t(o.trim())&&(ho=!1),void so();if(mo){if(["q","quit","exit","back","b","menu"].includes(e))return mo=!1,console.log(chalk.gray(Ao?" 已退出 AI 导师,回到卡片。":" Left the AI tutor — back to the card.")),console.log(),void so();const r=o.trim().startsWith("!"),n=r?o.trim().slice(1).trim():o.trim();return n&&await Oo(n,r?"fast":"deep"),void so()}if("ai4ioai"===e||"/ai4ioai"===e)return await l()&&(ho=!0),void so();if("/ai"===e||"/ai!"===e||e.startsWith("/ai ")||e.startsWith("/ai! ")){if(!G)return Lo(),void so();const r=e.startsWith("/ai!"),n=o.trim().slice(r?4:3).trim();if(!n){mo=!0;const o=To(Y.currentCard),e=o?o.title??o.badge??"":"";return console.log(),console.log(chalk.bold.magenta(" 🤖 AI ")+chalk.bold.white(Ao?"AI 导师":"AI tutor")+(e?chalk.gray(Ao?` — 主题「${e}」`:` — topic “${e}”`):"")),console.log(chalk.gray(Ao?" 直接输入问题(默认深度思考) · ! 开头 = 快答 · q = 返回卡片":" Type a question (deepthink by default) · prefix ! = quick answer · q = back to card")),console.log(),void so()}return await Oo(n,r?"fast":"deep"),void so()}if("/submit"===e||e.startsWith("/submit ")||"/research"===e||e.startsWith("/research ")){if(!G)return Lo(),void so();const o=To(Y.currentCard),e=o?o.title??o.badge??"":"";return console.log(),console.log(chalk.bold.magenta(" 📊 ")+chalk.bold.white(Ao?"Panda 提问评审":"Panda's prompt review")+(e?chalk.gray(Ao?` — 主题「${e}」`:` — topic “${e}”`):"")),console.log(chalk.gray(Ao?" 评的是你在本卡上「怎么问 AI」— 你与 /ai 导师的整段对话会被":" It scores HOW you asked the AI on this card — your whole /ai")),console.log(chalk.gray(Ao?" 整体评审,给出分数、名次和改进建议。":" conversation is reviewed for a score, a rank, and advice.")),console.log(chalk.gray(Ao?" 每张卡最多 2 次评分;评出的最好成绩进本卡 Top-3 榜。":" Max 2 graded attempts per card; your best enters the card’s top-3 board.")),console.log(),console.log(chalk.white(Ao?" 确认提交本卡对话评分?":" Submit this card’s conversation for grading?")+chalk.gray(Ao?" y = 提交 · 其他 = 取消":" y = submit · anything else = cancel")),console.log(),wo=!0,void so()}if("/view"===e)return G?(await(async()=>{yo=!0;try{console.log();const o=await p({token:H,cardNumber:Y.currentCard});if(!o.ok)return console.log(chalk.yellow(` ${o.message||(Ao?"暂时无法查看。":"View unavailable right now.")}`)),"not_top3"===o.code&&bo.delete(Y.currentCard),void console.log();null!=o.myRank&&o.myRank<=3&&bo.add(Y.currentCard),console.log(chalk.bold.white(Ao?` 🏆 本卡 Top-3 提问 — 第 ${Y.currentCard} 张 · ${o.entrants??0} 人上榜`:` 🏆 Top-3 prompts — card ${Y.currentCard} · ${o.entrants??0} entrant(s)`));for(const e of o.top3??[]){const o="green"===e.tier?"🟢":"blue"===e.tier?"🔵":"yellow"===e.tier?"🟡":"🔴";console.log(),console.log(chalk.bold.cyan(` ── Rank ${e.rank}`)+(e.me?chalk.bold.magenta(" (me)"):"")+chalk.white(` · ${e.score}`)+` ${o}`),e.questions.forEach((o,e)=>{const r=Do(o);console.log(chalk.gray(` Q${e+1} `)+chalk.white(r[0]??""));for(const o of r.slice(1))console.log(chalk.white(" "+o))}),0===e.questions.length&&console.log(chalk.gray(Ao?" (无可显示的提问)":" (no questions to show)"))}console.log()}finally{yo=!1}})(),void so()):(Lo(),void so());if("/honors"===e||"honors"===e)return X?(console.log(),console.log(chalk.gray(Ao?" 荣誉榜属于正式 learn token — 用团队签发的 token 进来即可查看和参赛。":" The honor board belongs to full learn tokens — join with a team-issued token to view and compete.")),console.log(),void so()):(await(async()=>{yo=!0;try{console.log();const o=await w({token:H,curriculumId:K.id,cardNumber:Y.currentCard});if(!o.ok||!o.totals||!o.chapters)return console.log(chalk.yellow(` ${o.message||(Ao?"荣誉榜暂时不可用。":"Honor board unavailable right now.")}`)),void console.log();if(console.log(chalk.bold.white(Ao?` 🏆 本卡实时 Top-3 — 第 ${Y.currentCard} 张`:` 🏆 Live top-3 — card ${Y.currentCard}`)),o.board&&0!==o.board.length)for(const e of o.board){const o="green"===e.tier?"🟢":"blue"===e.tier?"🔵":"yellow"===e.tier?"🟡":"🔴";console.log(chalk.white(` ${e.icon} `)+chalk.bold.cyan(`#${e.rank}`)+chalk.white(` · ${e.score} `)+o+(e.me?chalk.bold.magenta(" (me)"):""))}else console.log(chalk.gray(Ao?" (本卡还没有人上榜)":" (no one has medaled on this card yet)"));console.log(),J({totals:o.totals,chapters:o.chapters,floor:o.floor}),console.log()}finally{yo=!1}})(),void so());if("menu"!==e&&"menu confirm"!==e)if("quit"!==e&&"exit"!==e&&"q"!==e){if("status"===e)return W(K,Y),void so();if("lang"===e||e.startsWith("lang ")){const o=e.slice(4).trim();if(!o)return console.log(chalk.gray(" 当前语言 / current: ")+chalk.white(b().language||"en")+chalk.gray(" · ")+chalk.bold.green("lang zh")+chalk.gray(" / ")+chalk.bold.green("lang en")),void so();const r=K.cards.some(o=>null!=o._zh),n=o.toLowerCase().startsWith("zh")?"zh":o.toLowerCase(),t="en"===n||"zh"===n&&r,l=t?n:"en";return k({language:l}),t?console.log(chalk.green(" ✓ ")+chalk.gray("语言 / language → ")+chalk.white(l)):console.log(chalk.yellow(" ! ")+chalk.gray(`${o} 本课暂无翻译,已用英文 / not available — using English`)),uo=null,io=null,co=null,go=null,ko=!1,wo=!1,xo(),void so()}if("sim"===e){const o=To(Y.currentCard);return o&&"sim_demo"===o.type?(function(o){const e=function(){const o=s(c(import.meta.url)),e=[i(o,"..","..","panda","mujoco-launcher.py"),i(o,"..","..","..","panda","mujoco-launcher.py")];for(const o of e)if(a(o))return o;return null}();if(!e)return console.log(chalk.yellow(" MuJoCo launcher not found.")),console.log(chalk.gray(" Get it from: https://github.com/newaipanda/ICOA_CLI/blob/main/panda/mujoco-launcher.py")),void console.log(chalk.gray(" Or use the sandbox-vla docker image (Phase 3)."));const n={baseline:"baseline",prompt_injected:"prompt_inj",patch_attacked:"patch",modality_confused:"confused"}[o]||"baseline";console.log(chalk.gray(` Launching MuJoCo viewer (scenario: ${n})...`)),console.log(chalk.gray(" Close the window or press ESC to return to learn mode.")),r("python3",[e,n,"--seconds","5"],{stdio:"inherit"}).on("exit",o=>{0!==o?console.log(chalk.yellow(` MuJoCo exited with code ${o} (install: pip install mujoco)`)):console.log(chalk.gray(" Returned from sim."))})}(o.simAction),void so()):(console.log(chalk.gray(" (sim only available on simulation cards)")),void so())}if("bookmark"===e){const o=Y.currentCard;return Y.bookmarks.includes(o)||Y.bookmarks.push(o),A(Y),console.log(chalk.gray(` ✓ Card ${o} bookmarked.`)),void so()}if("e"===e)return go=Y.currentCard,console.log(chalk.gray(Ao?" 描述问题(回车提交 / 空行取消):":" Describe the issue (Enter to submit / blank to cancel):")),void so();if("back"===e||"b"===e)return Y.currentCard>1&&(Y.currentCard-=1),io=null,co=null,uo=null,A(Y),xo(),void so();if(e.startsWith("card ")){const o=e.slice(5).trim(),r=parseInt(o,10);return!Number.isInteger(r)||r<1||r>K.totalCards?(console.log(chalk.yellow(` Card number must be 1..${K.totalCards}. Try: `)+chalk.bold.green("card 1")),void so()):(Y.currentCard=r,io=null,co=null,uo=null,A(Y),xo(),void so())}if(null!==uo&&["y","yes","n","no"].includes(e)){const o=To(uo);if(o&&"knowledge"===o.type&&o.check){const r=e.startsWith("y")?"y":"n",n=r===o.check.answer,t=Date.now()-vo;$(Y,o.number),_(Y,o.number,{answer:r,correct:n,submittedAt:(new Date).toISOString()}),A(Y);const{renderCheckFeedback:l}=await import("../lib/learn-render.js");if(l(o,r,n,!1),Co.push(g(H,{card_number:o.number,event_type:"check_answered",check_answer:r,check_correct:n,time_on_card_ms:t}).catch(()=>{})),uo=null,o.solution&&o.solution.length>0)return void so();if(Y.currentCard<K.totalCards){const o=Y.currentCard;Y.currentCard+=1,A(Y),Mo(o)?Po():xo(),so()}else B(K,Y),so();return}}if(null!==io&&["a","b","c","d"].includes(e)){const o=To(io);if(o&&"mcq"===o.type){const r=e.toUpperCase(),n=r===o.answer;return E(Y,o.number,{answer:r,correct:n,submittedAt:(new Date).toISOString()}),$(Y,o.number),A(Y),O(o,r,n,Y),Co.push(g(H,{card_number:o.number,event_type:"mcq_answered",mcq_answer:r,mcq_correct:n}).catch(()=>{})),io=null,void(n?Y.currentCard<K.totalCards?(Y.currentCard+=1,A(Y),xo()):(B(K,Y),so()):so())}}if(null!==co){if("done"===e){const o=To(co);if(o&&"practical"===o.type)return T(Y,o.number),$(Y,o.number),A(Y),S(o),co=null,void so()}if("skip"===e)return $(Y,co),A(Y),console.log(chalk.gray(" Skipped (counts as not completed).")),console.log(),co=null,void so()}if("ok"===e||"next"===e||"continue"===e||"n"===e){if(null!==io)return console.log(chalk.yellow(" Please answer the MCQ first (A / B / C / D).")),void so();if(null!==co)return console.log(chalk.yellow(" Please type ")+chalk.bold.green("done")+chalk.yellow(" or ")+chalk.bold.yellow("skip")+chalk.yellow(" for the practical.")),void so();if(null!==uo)return console.log(chalk.yellow(" Please answer the check above (")+chalk.bold.green("y")+chalk.yellow(" or ")+chalk.bold.green("n")+chalk.yellow(").")),void so();const o=Y.currentCard;return Y.currentCard+=1,A(Y),Y.currentCard>K.totalCards?B(K,Y):Mo(o)?Po():xo(),void so()}if(null!==uo)return console.log(chalk.yellow(" Please answer the check above (")+chalk.bold.green("y")+chalk.yellow(" or ")+chalk.bold.green("n")+chalk.yellow(").")),void so();if(null!==io)return console.log(chalk.yellow(" Please answer the MCQ first (A / B / C / D).")),void so();if(null!==co)return console.log(chalk.yellow(" Please type ")+chalk.bold.green("done")+chalk.yellow(" or ")+chalk.bold.yellow("skip")+chalk.yellow(" for the practical.")),void so();console.log(chalk.gray(" Unknown command. Try: ")+chalk.white("ok")+chalk.gray(" / ")+chalk.white("status")+chalk.gray(" / ")+chalk.white("card N")+chalk.gray(" / ")+chalk.white("/ai")+chalk.gray(" / ")+chalk.white("quit")),so()}else if(Co.length>0&&await Promise.race([Promise.allSettled(Co),new Promise(o=>setTimeout(o,5e3))]),console.log(),console.log(chalk.gray(" Saved. See you next session.")),console.log(chalk.gray(" Streak: ")+chalk.yellow(`🔥 ${Y.streakDays} day(s)`)),console.log(),ro){to.removeAllListeners("line"),ao();for(const o of no)to.on("line",o);to.prompt(),Eo()}else to.removeAllListeners("line"),to.close();else if(Co.length>0&&await Promise.race([Promise.allSettled(Co),new Promise(o=>setTimeout(o,3e3))]),to.removeAllListeners("line"),ao(),ro||!_o){const{returnToMainMenu:o}=await import("../lib/menu-nav.js");o(ro?to:void 0),Eo()}else to.close()}else so()}};to.on("line",o=>{So(o).catch(o=>{console.log(),console.log(chalk.red(" ⚠ Something went wrong handling that input.")),console.log(chalk.gray(` ${o instanceof Error?o.message:String(o)}`)),console.log(chalk.gray(" Progress is saved. Type ")+chalk.bold.green("ok")+chalk.gray(" to continue or ")+chalk.bold.green("quit")+chalk.gray(" to exit.")),so()})}),ro||to.on("close",async()=>{Co.length>0&&await Promise.race([Promise.allSettled(Co),new Promise(o=>setTimeout(o,5e3))]),_o?Eo():process.exit(0)});try{xo(),so()}catch{if(Q("Could not render this card — the curriculum may be malformed."),ro){to.removeAllListeners("line"),ao();for(const o of no)to.on("line",o);to.prompt()}else to.close();Eo()}await $o}),V.command("learn-pull [id]").description("Pre-download learn curricula to ~/.icoa/learn-cache/ for offline use (default: all)").action(async o=>{const e=o?[o.trim()]:["LEARNDEMO01","AI4CTFDEMO01","CTF4AIDEMO01","ai4ctf-96","ai4ctf-360","ctf4ai-96","ctf4ai-360","ctf4eai-96","ctf4eai-360","embodied-ai-100","embodied-ai-480"];console.log(),console.log(chalk.bold.cyan(" ICOA learn-pull — fetching curricula for offline use")),console.log(chalk.gray(" Server: https://practice.icoa2026.au")),console.log();let r=0,n=0,t=0;for(const o of e){process.stdout.write(` ${o.padEnd(22)} `);const e=Date.now(),l=await u(o),a=Date.now()-e;if(l){const o=l.cards,e=Array.isArray(o)?o.length:0,n=JSON.stringify(l).length;t+=n,console.log(chalk.green("✓ ")+chalk.gray(`${e.toString().padStart(3)} cards · ${(n/1024).toFixed(1).padStart(6)} KB · ${a.toString().padStart(4)} ms`)),r++}else console.log(chalk.red("✗ failed (server unreachable or unknown id)")),n++}console.log(),console.log(chalk.gray(" Summary: ")+chalk.green.bold(`${r} cached`)+chalk.gray(` · ${n} failed · total ${(t/1024).toFixed(0)} KB`)),console.log(chalk.gray(" Cache: ")+chalk.cyan("~/.icoa/learn-cache/")),console.log()})}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as o}from"commander";import chalk from"chalk";import{registerCtfCommands as e}from"./commands/ctf.js";import{registerRefCommand as n}from"./commands/ref.js";import{registerShellCommand as s}from"./commands/shell.js";import{registerFilesCommand as r}from"./commands/files.js";import{registerViewCommand as t}from"./commands/view.js";import{registerConnectCommand as i}from"./commands/connect.js";import{registerChallengeCommand as a}from"./commands/challenge.js";import{registerDoctorCommand as l}from"./commands/doctor.js";import{registerNoteCommand as m}from"./commands/note.js";import{registerLogCommand as c}from"./commands/log.js";import{registerLangCommand as
|
|
2
|
+
import{Command as o}from"commander";import chalk from"chalk";import{registerCtfCommands as e}from"./commands/ctf.js";import{registerRefCommand as n}from"./commands/ref.js";import{registerShellCommand as s}from"./commands/shell.js";import{registerFilesCommand as r}from"./commands/files.js";import{registerViewCommand as t}from"./commands/view.js";import{registerConnectCommand as i}from"./commands/connect.js";import{registerChallengeCommand as a}from"./commands/challenge.js";import{registerDoctorCommand as l}from"./commands/doctor.js";import{registerNoteCommand as m}from"./commands/note.js";import{registerLogCommand as c}from"./commands/log.js";import{registerLangCommand as d}from"./commands/lang.js";import{registerSetupCommand as g}from"./commands/setup.js";import{registerEnvCommand as p}from"./commands/env.js";import{registerAienvCommand as y}from"./commands/aienv.js";import{registerIpynbCommand as f}from"./commands/ipynb.js";import{registerAi4ctfCommand as h}from"./commands/ai4ctf.js";import{registerExamCommand as u}from"./commands/exam.js";import{registerCtf4aiDemoCommand as w}from"./commands/ctf4ai-demo.js";import{registerThemeCommand as T}from"./commands/theme.js";import{registerLearnCommand as b}from"./commands/learn.js";import{registerCtf4VlaCommand as v}from"./commands/ctf4vla.js";import{registerDemo2Command as j}from"./commands/demo2.js";import{registerSimCommand as A}from"./commands/sim.js";import{registerArenaCommand as I}from"./commands/arena.js";import{registerBoardCommand as $}from"./commands/board.js";import{getConfig as C,saveConfig as E}from"./lib/config.js";import{startRepl as _}from"./repl.js";import{RELAUNCH_CODE as S}from"./lib/menu-nav.js";import{setTerminalTheme as x}from"./lib/theme.js";import{checkForUpdates as O}from"./lib/update-check.js";import{detectIcoaInstalls as L}from"./lib/platform.js";import{ICOA_BIG as N}from"./lib/banner.js";import{readFileSync as R}from"node:fs";import{fileURLToPath as k}from"node:url";import{dirname as F,join as U}from"node:path";const P=F(k(import.meta.url)),M=JSON.parse(R(U(P,"..","package.json"),"utf-8")).version,G=chalk.cyan(" ─────────────────────────────────────────────────────"),D=`\n${G}\n\n${N.map(o=>` ${chalk.bold.white(o)}`).join("\n")}\n\n ${chalk.yellow("International Cyber Olympiad in AI 2026")}\n ${chalk.bold.magenta("The World's First AI-Native CLI Operating System")}\n ${chalk.bold.magenta("for Cybersecurity & AI Security Competition")}\n ${chalk.bold.magenta("and Olympiad for K-12")}\n\n ${chalk.green.bold("AI4CTF")} ${chalk.gray("[Day 1]")} ${chalk.white("AI as your teammate")}\n ${chalk.red.bold("CTF4AI")} ${chalk.gray("[Day 2]")} ${chalk.white("Challenge & evaluate AI systems")}\n ${chalk.cyan.bold("CTF4EAI")} ${chalk.gray("[Pioneer]")} ${chalk.white("Embodied AI security")}\n ${chalk.bold.yellow("AI is your teammate. AI is your target. AI is embodied.")}\n\n ${chalk.white("Sydney, Australia")} ${chalk.gray("Jun 27 - Jul 2, 2026")}\n ${chalk.cyan.underline("https://icoa2026.au")}\n\n ${chalk.gray(`CLI-Native Competition Terminal v${M}`)}\n\n${G}\n`;process.on("uncaughtException",o=>{"__REPL_NO_EXIT__"!==o.message&&(console.error(chalk.red("Error:"),o.message),process.exit(1))}),process.on("unhandledRejection",o=>{const e=o instanceof Error?o.message:String(o);"__REPL_NO_EXIT__"!==e&&(console.error(chalk.red("Error:"),e),process.exit(1))});{const o=process.argv.slice(2).some(o=>["-V","--version","-h","--help"].includes(o));if(!0===process.stdin.isTTY&&"1"!==process.env.ICOA_SUPERVISED&&!o){const{spawnSync:o}=await import("node:child_process");process.on("SIGINT",()=>{}),process.on("SIGTERM",()=>{});let e=0,n=!1;do{const s=o(process.execPath,process.argv.slice(1),{stdio:"inherit",env:{...process.env,ICOA_SUPERVISED:"1",...n?{ICOA_RELAUNCH:"1"}:{}}});if(s.signal){e=0;break}e=s.status,n=!0}while(e===S);process.exit(e??0)}}const H=new o;if(H.name("icoa").version(M).description("ICOA CLI — CLI-Native CTF Competition Terminal").option("--resume","Resume previous session").option("--continue","Continue where you left off — jump straight to your last learn card / exam").action(async o=>{const e=C();x("high-contrast"===e.themeVariant?"high-contrast":"dark"),O(),function(){const o=L();if(o.length<=1)return;const e=o[0];if([...o.map(o=>o.version||"0.0.0")].sort((o,e)=>function(o,e){const n=o.split(".").map(o=>parseInt(o,10)||0),s=e.split(".").map(o=>parseInt(o,10)||0);for(let o=0;o<3;o++)if((n[o]||0)!==(s[o]||0))return(n[o]||0)-(s[o]||0);return 0}(e,o))[0]!==e.version){console.log(),console.log(chalk.yellow.bold(" ⚠ Multiple icoa installations detected on PATH:"));for(let e=0;e<o.length;e++){const n=o[e],s=n.version?`v${n.version}`:"(version unreadable)",r=0===e?chalk.yellow("→"):" ",t=0===e?chalk.gray(" ← currently running (older — shadowing newer install)"):chalk.gray(" ← shadowed");console.log(` ${r} ${chalk.cyan(n.path.padEnd(28))} ${chalk.white(s)}${t}`)}console.log(),console.log(chalk.yellow(" The first install on PATH wins. To use the newer one, remove the older:")),console.log(),e.pkgDir?console.log(` ${chalk.bold.cyan(`sudo rm -rf ${e.pkgDir} ${e.path}`)}`):console.log(` ${chalk.bold.cyan(`sudo rm -rf ${e.path} # also delete its node_modules dir`)}`),console.log(),console.log(chalk.gray(" Then re-run icoa to confirm version banner shows the newer release.")),console.log()}}();const n=process.env.LANG||process.env.LC_ALL||process.env.LC_CTYPE||"";if(!/UTF-?8/i.test(n))if("win32"===process.platform){let o="";try{const{execFileSync:e}=await import("node:child_process");o=e("chcp.com",[],{encoding:"utf-8",timeout:1500,stdio:["ignore","pipe","ignore"]}).trim()}catch{}o.includes("65001")||(console.log(chalk.yellow(`⚠ Windows terminal is not using UTF-8 (current: ${o||"unknown"}).`)),console.log(chalk.gray(' Non-English text (Ukrainian, Chinese, Japanese, etc.) may show as "?" or garbled glyphs.')),console.log(chalk.gray(" Fix (run before ")+chalk.cyan("icoa")+chalk.gray("): ")+chalk.cyan("chcp 65001")),console.log(chalk.gray(" Or stay in English inside the CLI: ")+chalk.cyan("lang en")),console.log())}else console.log(chalk.yellow(`⚠ Your terminal locale is not UTF-8 (LANG=${n||"(unset)"}).`)),console.log(chalk.gray(' Non-English text and box characters may display as "?" or garbled glyphs.')),console.log(chalk.gray(" Fix: ")+chalk.cyan("export LANG=en_US.UTF-8")+chalk.gray(" (or your locale, e.g. ")+chalk.cyan("zh_CN.UTF-8")+chalk.gray(", ")+chalk.cyan("uk_UA.UTF-8")+chalk.gray(")")),console.log();console.log(D);const s=!!o.resume||!!o.continue;if(process.argv.length<=2||s)return o.continue&&(process.env.ICOA_AUTO_RESUME="1"),s||"1"===process.env.ICOA_RELAUNCH||await async function(){const o=process.stdin;if(o.isTTY&&"function"==typeof o.setRawMode)return new Promise(e=>{let n=!1;const s=()=>{if(!n){n=!0,o.removeListener("data",r);try{o.setRawMode(!1)}catch{}o.pause(),e()}},r=()=>s();o.setRawMode(!0),o.resume(),o.once("data",r),console.log(chalk.gray(" (press any key to continue...)")),setTimeout(s,3e3)});await new Promise(o=>setTimeout(o,3e3))}(),void _(H,s)}),e(H),n(H),s(H),r(H),t(H),i(H),a(H),l(H),m(H),c(H),d(H),g(H),p(H),y(H),f(H),h(H),u(H),w(H),T(H),b(H),v(H),j(H),A(H),I(H),$(H),H.command("model",{hidden:!0}).argument("[name]","model name to switch to").action(o=>{const e=C().geminiModel||"gemini-2.5-flash-lite";o?(E({geminiModel:o}),console.log(),console.log(chalk.green(" Model switched: ")+chalk.gray(e)+chalk.white(" -> ")+chalk.bold.white(o)),console.log()):(console.log(),console.log(chalk.gray(" Current model: ")+chalk.white(e)),console.log(),console.log(chalk.gray(" Available models:")),console.log(chalk.bold.white(" Gemini 3.x (Latest)")),console.log(chalk.white(" model gemini-3.5-flash ")+chalk.gray("Newest stable, paid Tier 1+")),console.log(chalk.white(" model gemini-3-flash-preview ")+chalk.gray("Preview")),console.log(chalk.bold.white(" Gemini 2.5 (Stable)")),console.log(chalk.white(" model gemini-2.5-flash-lite ")+chalk.gray("Fastest, default, free tier")),console.log(chalk.white(" model gemini-2.5-flash ")+chalk.gray("Heavier, thinking enabled")),console.log(chalk.white(" model gemini-2.5-pro ")+chalk.gray("Strong reasoning, paid")),console.log(chalk.bold.white(" Open Source")),console.log(chalk.white(" model gemma-4-31b-it ")+chalk.gray("Free, open-source")),console.log(chalk.white(" model <any-model-id> ")+chalk.gray("Custom model")),console.log(),console.log(chalk.gray(" Translation uses gemini-3.5-flash for best quality.")),console.log())}),"1"===process.env.ICOA_RESET_STATE)try{const{clearExamState:o}=await import("./lib/exam-state.js");o(),console.log(chalk.yellow("⚠ ICOA_RESET_STATE=1 — local exam state wiped.")),console.log(chalk.gray(" (Token NOT revoked server-side. Re-enter a fresh token with `exam <token>`.)")),console.log()}catch(o){console.log(chalk.red("⚠ ICOA_RESET_STATE: could not clear state — ")+chalk.gray(String(o)))}H.parse();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* board-render — pure formatting for the ICOA Board CLI (no chalk, no I/O;
|
|
3
|
+
* caller applies colors). Mirrors the arena-submit.ts pure-module pattern.
|
|
4
|
+
*/
|
|
5
|
+
export interface BoardRow {
|
|
6
|
+
rank: number;
|
|
7
|
+
label: string;
|
|
8
|
+
value?: number | null;
|
|
9
|
+
display_score?: number | null;
|
|
10
|
+
total_display?: number | null;
|
|
11
|
+
tasks_counted?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function deltaLine(prevRank: number | null | undefined, rank: number | null | undefined, fieldSize: number | null | undefined): string;
|
|
14
|
+
export declare function renderBoardLines(rows: BoardRow[], width: number): string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function deltaLine(e,l,n){return null==l||null==n?"":null==e?`entered the board at #${l} of ${n}`:l<e?`▲ up ${e-l} — now #${l} of ${n}`:l>e?`best unchanged — slipped to #${l} of ${n}`:`best unchanged — still #${l} of ${n}`}export function renderBoardLines(e,l){return e.map(e=>{const n=null!=e.total_display?`${e.total_display.toFixed(1)}${null!=e.tasks_counted?` (${e.tasks_counted} tasks)`:""}`:null!=e.value?`${e.value}${null!=e.display_score?` ${e.display_score.toFixed(1)}/100`:""}`:"",t=`#${e.rank} ${e.label} ${n}`.trimEnd(),o=Array.from(t);return o.length>l?`${o.slice(0,l-1).join("")}…`:t})}
|
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 n,getConfig as l,saveConfig as a}from"./lib/config.js";import{ensureLangCache as s}from"./lib/translations-fetcher.js";import{loadUiPack as r}from"./lib/i18n.js";import{isActivated as i,activateToken as c,isFreeCommand as g,isDeviceMatch as y,recordExit as m,recordResume as d,isFirstRunOrUpgrade as p,markVersionSeen as u}from"./lib/access.js";import{setReplMode as h}from"./lib/ui.js";import{returnToMainMenu as w,printMenuHint as f,menuHintInline as b}from"./lib/menu-nav.js";import{setMainRl as x}from"./lib/main-rl.js";import{shellSplit as v}from"./lib/shell-split.js";import{isChatActive as C,handleChatMessage as A}from"./commands/ai4ctf.js";import{renderToolMan as k}from"./lib/tool-man.js";import{isCtf4aiActive as I,handleCtf4aiMessage as $}from"./commands/ctf4ai-demo.js";import{isCtf4VlaActive as T,handleCtf4VlaMessage as E}from"./commands/ctf4vla.js";import{isDemo2Active as S}from"./commands/demo2.js";import{loadDemo2Progress as O}from"./lib/demo2-progress.js";import{hasAnySimHistory as j,lastArmAt as N,lastBundledAt as L,lastSimAt as D}from"./lib/sim-cooldown.js";import{getExamState as R,getRealExamState as P,getDemoState as q,practicalChatCommand as F}from"./lib/exam-state.js";import{resolveResumePoint as M}from"./lib/resume.js";import{buildStatusLine as U}from"./lib/statusline.js";import{startBgTask as _,startAsyncTask as B,listBgTasks as W,getBgTask as Q,drainCompleted as Y,killAllBgTasks as z,runningCount as X}from"./lib/bgtasks.js";import{computeStart as J,renderStart as V}from"./commands/challenge.js";import{addDraft as G,listDrafts as H,removeDraft as Z,clearDrafts as K}from"./lib/drafts.js";import{resetTerminalTheme as oo}from"./lib/theme.js";import{ensureSandbox as eo,runInSandbox as to,isDockerAvailable as no}from"./lib/sandbox.js";import{checkShellRisk as lo,logShellAudit as ao,getActiveCwd as so}from"./lib/exam-sandbox.js";import{logCommand as ro}from"./lib/logger.js";import{startLogSync as io,stopLogSync as co}from"./lib/log-sync.js";import{installInputDynamics as go,uninstallInputDynamics as yo,stripPasteMarkers as mo}from"./lib/input-dynamics.js";import{startBroadcastPoll as po,stopBroadcastPoll as uo,showBroadcast as ho,isCompetitionJoined as wo,isMelCompetition as fo,isLeaderAccount as bo,isLeaderSession as xo,fetchPublicScoreboard as vo,isCommsActive as Co}from"./lib/comms.js";import{enterMemoScreen as Ao,enterJuryScreen as ko}from"./commands/comms.js";import{existsSync as Io,mkdirSync as $o,readFileSync as To,writeFileSync as Eo}from"node:fs";import{join as So}from"node:path";import{homedir as Oo}from"node:os";const jo="# 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\ntry: from PIL import Image\nexcept ImportError: pass\n",No=Date.now();function Lo(){return fo()?chalk.yellow("icoa 2026> "):P()?chalk.cyan("icoa exam> "):function(){const o=q();return!(!o||"demo-free"!==o.session?.examId)&&new Date(o.session.startedAt||0).getTime()>=No}()?chalk.yellow("icoa demo> "):chalk.green("icoa> ")}const Do=So(Oo(),"icoa-workspace"),Ro=new Set(["sudo","su","doas","pkexec","brew","apt","apt-get","yum","choco","npm","npx","pip","pip3","shutdown","reboot","halt","mkfs","fdisk","dd","iptables","ufw"]),Po="__REPL_NO_EXIT__",qo="2.5.1",Fo=["join","activate","challenges","ch","open","submit","flag","scoreboard","sb","status","time","ref","shell","files","view","connect","challenge","note","notes","logbook","log","man","manual","lang","setup","env","aienv","ipynb","ai4ctf","model","ctf","exam","demo","retry","nations","next","prev","continue","logout","ctf4ai","mark","unmark","review","learn","demo2","ctf4eai","ctf4vla","theme","sim","arena","eai","learn-pull","doctor","bg","tasks","drafts","help","menu","quit","q","exit","back","resume"],Mo={exam:["list","start","q","answer","submit","review","result","next","prev","mark","unmark","help"],ctf:["challenges","open","submit","scoreboard","status","join","logout","time"],challenge:["start","stop","status"],arena:["eai"],learn:["start","continue"]};function Uo(o){const e=o.replace(/^\s+/,"");if(/^\/\S*$/.test(e)){const o=e.slice(1).toLowerCase();return[Fo.filter(e=>e.startsWith(o)).map(o=>`/${o}`),e]}const t=e.split(/\s+/);if(t.length<=1){const o=(t[0]??"").toLowerCase();return[Fo.filter(e=>e.startsWith(o)).slice(),o]}if(2===t.length){const o=Mo[t[0].toLowerCase()];if(o){const e=t[1].toLowerCase();return[o.filter(o=>o.startsWith(e)).slice(),e]}}return[[],o]}async function _o(o){const e=M();if(!e)return null;const t=process.exit;process.exit=()=>{throw new Error(Po)};try{await o.parseAsync(["node","icoa",...e.command])}catch(o){if(!(o instanceof Error&&o.message===Po))throw o}finally{process.exit=t}return e}function Bo(){console.log(),console.log(` ${chalk.cyan.bold("🎯 ICOA 2026 · National Selection")}`),console.log(),console.log(" "+chalk.bold.cyan("learn ")+chalk.white("ACxxxxxxxx")+chalk.gray(" → ai4ctf-12 (AI as your CTF teammate)")),console.log(" "+chalk.bold.cyan("learn ")+chalk.white("CAxxxxxxxx")+chalk.gray(" → ctf4ai-12 (red-team software AI)")),console.log(" "+chalk.bold.cyan("learn ")+chalk.white("EAxxxxxxxx")+chalk.gray(" → ctf4eai-12 (red-team embodied AI)")),console.log(" "+chalk.bold.cyan("exam ")+chalk.white("<CC>xxxxxxxx")+chalk.gray(" → Paper A/B/C/D/E (country exam token)")),console.log(" "+chalk.bold.cyan("demo")+chalk.gray(" → free practice")),console.log(" "+chalk.bold.cyan("lang")+chalk.white(" es")+chalk.gray(" → switch language")),console.log(" "+chalk.gray("help · menu · quit")),console.log()}export async function startRepl(e,q){process.env.ICOA_INSIDE_REPL="1";let No=!1;if(fo()){const o=l().joinedAt,e=o=>new Intl.DateTimeFormat("en-CA",{timeZone:"Australia/Sydney",year:"numeric",month:"2-digit",day:"2-digit"}).format(o);o&&e(new Date(o))===e(new Date)||(a({ctfdUrl:"",token:"",sessionCookie:"",userId:null,userName:"",teamId:null,country:"",joinedAt:""}),No=!0)}const Mo=l(),Yo=n(),zo=process.exit.bind(process),Xo=i();if(Mo.language&&"en"!==Mo.language&&(await s(Mo.language,{silent:!0}),r(Mo.language)),Mo.demoCleanedForVersion!==qo){try{const{existsSync:o,unlinkSync:e}=await import("node:fs"),{join:t}=await import("node:path"),{getIcoaDir:n}=await import("./lib/config.js"),l=t(n(),"demo-state.json");o(l)&&e(l)}catch{}a({demoCleanedForVersion:qo})}const{select:Jo,confirm:Vo}=await import("@inquirer/prompts"),Go=Mo.mode||"",Ho=""===Go?"instant":["instant","learn","selection","olympiad"].includes(Go)?Go:"selection",Zo=[{name:` ${chalk.bold("⚡ Try ICOA in 30 seconds")} ${chalk.gray("—")} ${chalk.gray("watch · no token needed")}`,value:"instant"},{name:` ${chalk.bold("🧠 Learn")} ${chalk.gray("—")} ${chalk.gray("AC / CA / EA token · skill up")}`,value:"learn"},{name:` ${chalk.bold("🎯 National Selection")} ${chalk.gray("—")} ${chalk.gray("country exam token · aim for finals")}`,value:"selection"},{name:` ${chalk.bold("🏆 International Olympiad")} ${chalk.gray("—")} ${chalk.gray("finals stage · Sydney 2026 · full sandbox")}`,value:"olympiad"},{name:` ${chalk.gray("ℹ️ About ICOA")} ${chalk.gray("·")} ${chalk.gray("what this is · how to join")}`,value:"about"}];""===Go&&(console.log(chalk.green(" 👋 First time here? ")+chalk.white("Just press ")+chalk.bold.cyan("Enter")+chalk.white(" — you’ll see ICOA in 30 seconds, no signup needed.")),console.log()),console.log(chalk.gray(" Type ")+chalk.bold.cyan("menu")+chalk.gray(" from anywhere to return here.")),console.log();let Ko="";for(;!Ko;){let o;try{o=await Jo({message:"Mode",choices:Zo,default:Ho||"instant"})}catch(o){throw o instanceof Error&&"ExitPromptError"===o.name&&(console.log(),console.log(chalk.gray(" Goodbye.")),process.exit(0)),o}"about"!==o?Ko=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 (2-day standard + 1-day Pioneer)")),console.log(` ${chalk.green.bold("AI4CTF")}${chalk.gray(" [Day 1] AI as teammate — speed track, dynamic decay + top-3 bonus")}`),console.log(` ${chalk.red.bold("CTF4AI")}${chalk.gray(" [Day 2] AI as target — knowledge track, static value per challenge")}`),console.log(` ${chalk.cyan.bold("CTF4EAI")}${chalk.gray(" [Pioneer / Practical Round] Embodied AI — VLA + world models + sim-to-real")}`),console.log(),console.log(chalk.bold.white(" Scoring (in brief)")),console.log(chalk.gray(" · Standard medals: AI4CTF + CTF4AI combined · 1/12 Gold · 2/12 Silver · 3/12 Bronze")),console.log(chalk.gray(" · HMA: top 50% on either day, total below bronze cutoff")),console.log(chalk.gray(" · CTF4EAI: separate 15-finalist pool, G/S/B + 12 Finalist Awards")),console.log(),console.log(chalk.white(" Sydney, Australia")+chalk.gray(" · Jun 27 - Jul 2, 2026")),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"===Ko&&"olympiad"!==Ho){console.log(),console.log(chalk.yellow(" This mode will download ~500MB of CTF tools and AI models."));let o=!0;try{o=await Vo({message:"Continue?",default:!0})}catch(e){if(!(e instanceof Error&&"ExitPromptError"===e.name))throw e;o=!1}o||(Ko="selection",console.log(chalk.gray(" Switched to National Selection mode.")))}if(Ko!==Ho&&a({mode:Ko}),console.log(),"olympiad"===Ko&&p(qo)){u(qo),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 n of e)try{o(n.cmd,{stdio:"ignore"})}catch{t++}t>0?(console.log(chalk.yellow(` ${t} core competition libraries missing.`)),console.log(chalk.gray(" Run ")+chalk.white("env setup")+chalk.gray(" when you are ready to install them.")),console.log()):(console.log(chalk.green(" All core libraries ready.")),console.log())}if(q){const o=d();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()}}if("selection"===Ko)console.log(chalk.gray(" ─── exam hall · 90 min per paper once started · no pause ───")),console.log(),Bo();else if("embodied"===Ko)!function(){console.log(chalk.cyan.bold(" [🤖 ctf4eai — Embodied AI Security]")),console.log(),console.log(chalk.bold.cyan(" CTF4EAI")+chalk.gray(" — ICOA 2026's ")+chalk.bold.white("Pioneer Round")),console.log(chalk.gray(" an independent parallel track alongside the scored main events: ")+chalk.green("AI4CTF")+chalk.gray(" (Day 1) and ")+chalk.red("CTF4AI")+chalk.gray(" (Day 2)")),console.log(),console.log(chalk.white(" Embodied AI is at its ImageNet moment.")),console.log(chalk.gray(" VLA models, MuJoCo physics, real robot arms.")),console.log(chalk.gray(" Real models, real physics, real attack surfaces.")),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────"));const o=L()>0||D()>0,e=N()>0,t=j(),n=O(),l=null!==n&&"number"==typeof n.completedAt&&n.nextCardIndex>=n.totalCards,a=null!==n&&!l;if(l)console.log(chalk.bold.yellow(" Continue: ")+chalk.bold.cyan("④ learn LEARNDEMO01")+chalk.gray(" (demo2 ✓ · sim")+(e?chalk.gray(" ✓ · sim arm ✓)"):chalk.gray(")")));else if(a){const o=`${n.nextCardIndex} / ${n.totalCards}`;console.log(chalk.bold.yellow(" Continue: ")+chalk.bold.cyan("③ demo2")+chalk.gray(" (last left at card ")+chalk.bold.yellow(o)+chalk.gray(")"))}else o&&e?console.log(chalk.bold.yellow(" Next up: ")+chalk.bold.cyan("③ demo2")+chalk.gray(" → ")+chalk.bold.cyan("④ learn LEARNDEMO01")+chalk.gray(" (sim ✓ · sim arm ✓)")):t?console.log(chalk.bold.yellow(" Next up: ")+(e?chalk.bold.cyan("① sim")+chalk.gray(" → ")+chalk.bold.cyan("③ demo2"):chalk.bold.cyan("② sim arm")+chalk.gray(" → ")+chalk.bold.cyan("③ demo2"))+chalk.gray(" → ")+chalk.bold.cyan("④ learn LEARNDEMO01")):console.log(chalk.bold.yellow(" First time? Try in order: ")+chalk.bold.cyan("① sim")+chalk.gray(" → ")+chalk.bold.cyan("② sim arm")+chalk.gray(" → ")+chalk.bold.cyan("③ demo2")+chalk.gray(" → ")+chalk.bold.cyan("④ learn LEARNDEMO01"));console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.bold.green(" sim")+chalk.gray(" ")+chalk.bold.cyan("①")+chalk.gray(" ")+chalk.bold.yellow("★ INSTANT")+chalk.gray(" — 9-robot group dance, no wait, no setup")),console.log(chalk.bold.green(" sim arm")+chalk.gray(" ")+chalk.bold.cyan("②")+chalk.gray(" interactive 6-DOF arm sandbox — type joint values, watch")),console.log(chalk.bold.green(" demo2")+chalk.gray(" ")+chalk.bold.cyan("③")+chalk.gray(" ~8 min interactive intro + live MuJoCo sim")),console.log(chalk.bold.green(" learn LEARNDEMO01")+chalk.gray(" ")+chalk.bold.cyan("④")+chalk.gray(" free 11-card intro (paste this command as-is)")),console.log(chalk.bold.green(" learn ")+chalk.bold.yellow("EA")+chalk.gray(" + 8 chars full 96 / 360 curriculum (team-issued)")),console.log(chalk.bold.green(" exam ")+chalk.bold.yellow("PDxxxxxxxx")+chalk.gray(" Paper D (CTF4EAI) — national selection contestants")),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(),console.log(chalk.gray(" Three open tiers: ")+chalk.green("free 11")+chalk.gray(" · ")+chalk.yellow("registered 100")+chalk.gray(" · ")+chalk.bold.magenta("national team 480")+chalk.gray(" (PhD-ready)")),console.log(chalk.gray(" Country team contact: ")+chalk.cyan("australia@icoa2026.au")),console.log(),f(),console.log()}();else if("instant"===Ko)console.log(chalk.cyan.bold(" [⚡ Try ICOA in 30 seconds — no token needed]")),console.log(),console.log(chalk.gray(" Two free tastes — just type the word and press Enter:")),console.log(),console.log(chalk.bold.green(" demo ")+chalk.gray(" Paper A/B/C taste — 10 MCQs, no timer ")+chalk.bold.cyan("🎯 selection-style")),console.log(chalk.bold.green(" demo2")+chalk.gray(" Embodied-AI mini-demo — robots & MuJoCo ")+chalk.bold.yellow("🤖 hands-on")),console.log(),console.log(chalk.gray(" Type ")+chalk.bold.cyan("demo")+chalk.gray(" or ")+chalk.bold.cyan("demo2")+chalk.gray(" to start · ")+chalk.bold.cyan("menu")+chalk.gray(" to go back · ")+chalk.bold.cyan("quit")+chalk.gray(" to exit")),console.log(),a({mode:"selection"}),Ko="selection";else{if("learn"===Ko){console.log(chalk.cyan.bold(" [🧠 Learn]")),console.log(),console.log(chalk.gray(" Three tracks · same difficulty curve.")),console.log(),console.log(chalk.white(" Free 12-card demos — paste the full command:")),console.log(),console.log(chalk.gray(" 🤝 ai4ctf AI as your teammate ")+chalk.bold.yellow("learn AI4CTFDEMO01")),console.log(chalk.gray(" 🔓 ctf4ai AI as your target ")+chalk.bold.yellow("learn CTF4AIDEMO01")),console.log(chalk.gray(" 🤖 ctf4eai Embodied AI Security ")+chalk.bold.yellow("learn LEARNDEMO01")),console.log(),console.log(chalk.gray(" Got a cohort token? Type ")+chalk.bold.yellow("learn <TOKEN>")+chalk.gray(" (e.g. ")+chalk.bold.yellow("learn EAABCD1234")+chalk.gray(").")),console.log();try{const{input:o}=await import("@inquirer/prompts"),{resolveLearnInput:t}=await import("./lib/learn-input.js");console.log(chalk.gray(" Enter a token (or just press Enter to go back).")),console.log();const n=t(await o({message:chalk.bold.cyan("icoa learn>"),theme:{prefix:"",style:{message:o=>o}}}));if("token"===n.kind){console.log(),console.log(chalk.gray(" Opening ")+chalk.bold.cyan("learn")+chalk.gray(`: ${n.token}`)),console.log(),a({mode:"selection"}),process.env.ICOA_LEARN_EMBEDDED="1";try{await e.parseAsync(["node","icoa","learn",n.token])}finally{delete process.env.ICOA_LEARN_EMBEDDED}}else"ambiguous"!==n.kind&&"unknown"!==n.kind||(console.log(),console.log(chalk.yellow(" "+n.hint)),console.log(chalk.gray(" Returning to main menu — pick 🧠 Learn again to retry.")),console.log())}catch{}return a({mode:"selection"}),void w()}"organizer"===Ko?(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(),Yo?(console.log(chalk.green(` Logged in as ${Mo.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(),f(),console.log()):(console.log(` ${chalk.cyan.bold("🏆 ICOA 2026 · International Olympiad")}`),console.log(),Xo&&!y()?(console.log(chalk.red(" Token was activated on a different device.")),console.log(chalk.gray(" Contact organizer for assistance.")),console.log()):Yo?(console.log(chalk.green.bold(` Welcome back, ${Mo.userName}!`)),console.log(chalk.gray(` Connected to ${Mo.ctfdUrl}`)),console.log(chalk.gray(" Not you? Type ")+chalk.cyan("logout")+chalk.gray(" to switch accounts")),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 & timer")),console.log(chalk.white(" scoreboard")+chalk.gray(" Live rankings")),console.log(chalk.white(" memo")+chalk.gray(" Report an issue to organizers (one-way)")),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.bold.yellow(" ★ ")+chalk.bold.cyan("man <tool>")+chalk.gray(" How to USE a tool (steghide, nmap…) · ")+chalk.cyan("man tools")+chalk.gray(" = list")),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.gray(" Tip: ")+chalk.cyan("help")+chalk.gray(" · ")+b()+chalk.gray(" · ")+chalk.cyan("Ctrl+C")+chalk.gray(" pauses · ")+chalk.cyan("quit")+chalk.gray(" closes")),console.log()):Xo?(Io(Do)||$o(Do,{recursive:!0}),console.log(chalk.green.bold(" Welcome, competitor!")),console.log(chalk.gray(` Workspace: ${Do}`)),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(chalk.bold.yellow(" ★ ")+chalk.bold.cyan("man <tool>")+chalk.gray(" How to USE a tool (steghide, nmap…) · ")+chalk.cyan("man tools")+chalk.gray(" = list")),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 · ")+b()+chalk.gray(" · ")+chalk.cyan("quit")+chalk.gray(" closes CLI")),console.log()):(console.log(chalk.bold.yellow(" 🏆 You reached the finals tier.")),console.log(chalk.gray(" Full sandbox · 110 CTF tools · Sydney 2026 · this is where finalists compete.")),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 · ")+b()+chalk.gray(" · ")+chalk.cyan("quit")+chalk.gray(" closes CLI")),console.log()))}e.exitOverride(),e.configureOutput({writeErr:()=>{},writeOut:o=>{console.log(o)}});const oe=o({input:process.stdin,output:process.stdout,prompt:Lo(),terminal:!0,completer:Uo});x(oe),go(!1!==l().bracketedPaste);let ee=!1;if(h(!0),io(),No&&(console.log(),console.log(chalk.yellow(" ⏱ A new competition day has started (Sydney time).")),console.log(chalk.gray(" For fairness, yesterday’s sign-in has been cleared. Type ")+chalk.cyan("join <url>")+chalk.gray(" to sign in with your account.")),console.log()),fo()){const o=l().userName||"this account";console.log(),console.log(chalk.yellow(" ⚠ This machine is signed in as ")+chalk.bold.white(o)+chalk.yellow(".")),console.log(chalk.gray(" Press ")+chalk.cyan("Enter")+chalk.gray(` to continue as ${o}, or type `)+chalk.cyan("logout")+chalk.gray(" to switch accounts.")),"logout"===(await new Promise(o=>{oe.question(chalk.white(" > "),e=>o(e.trim()))})).toLowerCase()&&(a({ctfdUrl:"",token:"",sessionCookie:"",userId:null,userName:"",teamId:null,country:""}),console.log(),console.log(chalk.green(" ✓ Logged out. ")+chalk.gray("Run ")+chalk.cyan("join <url>")+chalk.gray(" to sign in with your own account."))),console.log()}wo()&&po(()=>oe.prompt());let te=null;const ne=oe.prompt.bind(oe);oe.prompt=o=>{if(!(C()||I()||T()||S()||Co()||(oe.setPrompt(Lo()),o)))try{for(const o of Y()){const e="done"===o.status,t=o.cmd.length>40?`${o.cmd.slice(0,37)}…`:o.cmd,n=o.render?"":chalk.gray(" (")+chalk.white(t)+chalk.gray(") — ")+chalk.cyan(`tasks ${o.id}`);if(console.log((e?chalk.green(" ✓"):chalk.red(" ✗"))+chalk.white(` background task #${o.id} ${o.status}`)+n),o.render)try{o.render()}catch{}}const o=U();o&&o!==te?(console.log(o),te=o):o||(te=null)}catch{}ne(o)};const le=M();le&&(console.log(chalk.cyan(" ↻ Continue where you left off: ")+chalk.white(le.label)+(le.urgent?chalk.yellow(" (exam timer running)"):"")),console.log(chalk.gray(" type ")+chalk.cyan("resume")+chalk.gray(" to jump back in.")),console.log()),oe.prompt(),oe.on("line",async o=>{if(ee)return;let a=mo(o.trim());if(!a)return oe.setPrompt(C()?chalk.magenta("icoa ai4ctf> "):Lo()),void oe.prompt();if(a.startsWith("/")&&!a.startsWith("//")){const o=a.slice(1).split(/\s+/)[0].toLowerCase(),t=a.slice(1+o.length).trim();if("ai"===o)return ee=!0,await async function(o,e){if(C())return void(o?await A(o):console.log(chalk.gray(" You are already in the AI chat — just type your message.")));if(I())return void(o?await $(o):console.log(chalk.gray(" You are already in the CTF4AI target chat — just type your message.")));if(T())return void(o?await E(o):console.log(chalk.gray(" You are already in the Embodied-AI chat — just type your message.")));ro(o?`/ai ${o}`:"/ai");const t=process.exit;process.exit=()=>{throw new Error(Po)};try{await e.parseAsync(["node","icoa","ai4ctf"])}catch(o){if(!(o instanceof Error&&o.message===Po))throw o}finally{process.exit=t}o&&C()&&await A(o)}(t,e),ee=!1,oe.setPrompt(C()?chalk.magenta("icoa ai4ctf> "):Lo()),void oe.prompt();!(C()||I()||T()||S()||Co())&&Fo.includes(o)&&(a=a.slice(1))}if("resume"===a.toLowerCase()){const o=M();return o?(ro(`resume → ${o.command.join(" ")}`),ee=!0,await _o(e),ee=!1,oe.setPrompt(C()?chalk.magenta("icoa ai4ctf> "):Lo()),void oe.prompt()):(console.log(),console.log(chalk.gray(" Nothing to resume yet — start with ")+chalk.cyan("learn")+chalk.gray(" (curriculum) or ")+chalk.cyan("demo")+chalk.gray(" (taste).")),console.log(),void oe.prompt())}const s=C()||I()||T()||S();if(!s&&("bg"===a.toLowerCase()||/^bg\s/i.test(a))){const o=a.replace(/^bg\s*/i,"").trim();if(!o)return console.log(),console.log(chalk.gray(" Usage: ")+chalk.cyan("bg <command>")+chalk.gray(" — run a non-interactive shell command in the background.")),console.log(chalk.gray(" e.g. ")+chalk.cyan("bg nmap -p- 10.0.0.5")+chalk.gray(" · view: ")+chalk.cyan("tasks")),console.log(),void oe.prompt();const e=so(),t=lo(o);ao({cwd:e,input:`bg ${o}`,riskFlags:t}),t.length>0&&P()&&console.log(chalk.yellow(` ⚠ Action flagged: ${t.join(", ")} — logged.`));const n=_(o,e);return console.log(),console.log(chalk.cyan(` ▸ background task #${n.id} started`)+chalk.gray(` (${X()} running)`)),console.log(chalk.gray(" ")+chalk.white(o)),console.log(chalk.gray(" view: ")+chalk.cyan("tasks")+chalk.gray(" · output: ")+chalk.cyan(`tasks ${n.id}`)),console.log(),void oe.prompt()}if(!s&&("tasks"===a.toLowerCase()||/^tasks\s/i.test(a))){const o=a.replace(/^tasks\s*/i,"").trim(),e=W();if(console.log(),o){const e=Q(Number(o));if(!e||Number.isNaN(Number(o)))console.log(chalk.yellow(` No background task #${o}.`)+chalk.gray(" Type ")+chalk.cyan("tasks")+chalk.gray(" to list them."));else{const o="running"===e.status?chalk.yellow:"done"===e.status?chalk.green:chalk.red;console.log(o(` #${e.id} [${e.status}]`)+chalk.white(` ${e.cmd}`)+(null!==e.exitCode?chalk.gray(` (exit ${e.exitCode})`):"")),console.log(chalk.gray(" ─────────────────────────────────────────────"));const t=e.output.trim();if(t)for(const o of t.split("\n"))console.log(chalk.gray(" ")+chalk.white(o));else console.log(chalk.gray(" (no output yet)"))}}else if(0===e.length)console.log(chalk.gray(" No background tasks. Start one with ")+chalk.cyan("bg <command>")+chalk.gray("."));else{console.log(chalk.bold.white(" Background tasks"));for(const o of e){const e="running"===o.status?chalk.yellow("●"):"done"===o.status?chalk.green("✓"):chalk.red("✗"),t=Math.round(((o.endedAt??Date.now())-o.startedAt)/1e3),n=(o.endedAt?`${t}s`:`${t}s…`).padStart(5),l=o.cmd.length>48?`${o.cmd.slice(0,45)}…`:o.cmd;console.log(` ${e} `+chalk.white(`#${o.id}`)+chalk.gray(` ${n} `)+chalk.white(l))}console.log(chalk.gray(" output: ")+chalk.cyan("tasks <id>"))}return console.log(),void oe.prompt()}if(!s&&/&\s*$/.test(a)&&/^challenge\s+start\s+/i.test(a)){const o=a.replace(/\s*&\s*$/,"").trim().replace(/^challenge\s+start\s+/i,"").trim().split(/\s+/),e=o.some(o=>"-n"===o||"--new"===o),t=o.find(o=>"-n"!==o&&"--new"!==o);if(!t)return console.log(),console.log(chalk.gray(" Usage: ")+chalk.cyan("challenge start <id> &")),console.log(),void oe.prompt();if(!n())return console.log(),console.log(chalk.red(" Not connected. ")+chalk.gray("Run ")+chalk.cyan("join <url>")+chalk.gray(" first.")),console.log(),void oe.prompt();ro(`challenge start ${t}${e?" --new":""} & (background)`);const l=B(`challenge start ${t}`,()=>J(t,e),o=>V(t,o));return console.log(),console.log(chalk.cyan(` ▸ background task #${l.id}: `)+chalk.white(`starting ${t}`)+chalk.gray(" — you can keep working; the connection info appears here when it is ready.")),console.log(),void oe.prompt()}if(!s&&a.startsWith("#")){const o=a.slice(1).trim();if(!o)return console.log(),console.log(chalk.gray(" Usage: ")+chalk.cyan("#<candidate answer>")+chalk.gray(" — stage a flag/answer WITHOUT submitting it. Review with ")+chalk.cyan("drafts")+chalk.gray(".")),console.log(),void oe.prompt();const e=R(),t=e?.session?.examId??null,n=e?e._lastQ||1:null,l=G(o,t,n);return ro(`draft #${l.id} (not submitted)`),console.log(),console.log(chalk.green(` ✎ draft #${l.id} saved`)+(n?chalk.gray(` (Q${n})`):"")+chalk.yellow(" — NOT submitted.")),console.log(chalk.gray(" review with ")+chalk.cyan("drafts")+chalk.gray("; when sure, submit it the normal way.")),console.log(),void oe.prompt()}if(!s&&("drafts"===a.toLowerCase()||/^drafts\s/i.test(a))){const o=a.replace(/^drafts\s*/i,"").trim().toLowerCase();if("clear"===o){const o=K();return console.log(),console.log(chalk.gray(` Cleared ${o} draft${1===o?"":"s"}.`)),console.log(),void oe.prompt()}const e=o.match(/^(?:rm|remove|del|delete)\s+(\d+)$/);if(e){const o=Z(Number(e[1]));return console.log(),console.log(o?chalk.gray(` Removed draft #${e[1]}.`):chalk.yellow(` No draft #${e[1]}.`)),console.log(),void oe.prompt()}const t=R(),n=H(t?.session?.examId??null);if(console.log(),0===n.length)console.log(chalk.gray(" No drafts yet. Stage a candidate with ")+chalk.cyan("#ICOA{maybe_this}")+chalk.gray(" — it won’t submit."));else{console.log(chalk.bold.white(" Draft answers")+chalk.gray(" (staged — not submitted)"));for(const o of n){const e=o.q?chalk.cyan(`Q${o.q}`.padStart(4)):chalk.gray(" · ");console.log(chalk.gray(` #${o.id}`)+` ${e} `+chalk.white(o.text.length>56?`${o.text.slice(0,53)}…`:o.text))}console.log(chalk.gray(" remove: ")+chalk.cyan("drafts rm <id>")+chalk.gray(" · clear all: ")+chalk.cyan("drafts clear"))}return console.log(),void oe.prompt()}if("menu"===a||"menu confirm"===a)return P()&&"menu confirm"!==a?(console.log(),console.log(chalk.yellow(" Real exam active. Progress is auto-saved.")),console.log(chalk.gray(" To return to main menu anyway, type ")+chalk.white("menu confirm")),console.log(),void oe.prompt()):void w(oe);if(C()){ee=!0;const o=await A(a);return ee=!1,"exit"===o&&oe.setPrompt(Lo()),void oe.prompt()}if(I()){ee=!0;const o=await $(a);return ee=!1,"exit"!==o&&"solved"!==o||oe.setPrompt(Lo()),void oe.prompt()}if(T()){ee=!0;const o=await E(a);return ee=!1,"exit"===o&&oe.setPrompt(Lo()),oe.setPrompt(T()?chalk.bold.cyan("icoa ctf4eai> "):Lo()),void oe.prompt()}if(ro(a),"exit"===a)return R()?(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 oe.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"===Ko&&Bo(),void oe.prompt());if("quit"===a||"q"===a||"quit confirm"===a){const o=R();return o&&"demo-free"!==o.session.examId&&"quit confirm"!==a?(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 oe.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)."))),co(),yo(),uo(),z(),m(),console.log(chalk.gray(" Session saved. Use ")+chalk.white("icoa --resume")+chalk.gray(" to continue.")),oo(),void zo(0))}if("back"===a){const o=R(),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=l();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"===Ko?Bo():console.log(chalk.gray(" Already at main menu."))}return void oe.prompt()}if("help"===a||"?"===a){if(R()){ee=!0;try{await e.parseAsync(["node","icoa","exam","help"])}catch{}return ee=!1,void oe.prompt()}return Qo(i(),Ko),void oe.prompt()}if("man"===a||"manual"===a)return Qo(i(),Ko),console.log(),console.log(chalk.bold.yellow(" ★ Stuck on a tool? ")+chalk.bold.cyan("man <tool>")+chalk.gray(" e.g. ")+chalk.cyan("man steghide")+chalk.gray(" · ")+chalk.cyan("man tools")+chalk.gray(" = full list")),void oe.prompt();if(/^(?:man|manual)\s+\S/i.test(a))return k(a.replace(/^(?:man|manual)\s+/i,"")),void oe.prompt();if("more help"===a.toLowerCase()&&R()){ee=!0;try{await e.parseAsync(["node","icoa","exam","more-help"])}catch{}return ee=!1,void oe.prompt()}if("continue"===a.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 this demo, your AI teammate gives help at 3 levels:")),console.log(chalk.yellow(" hint a")+chalk.gray(" General guidance")),console.log(chalk.yellow(" hint b")+chalk.gray(" Deep analysis")),console.log(chalk.yellow(" hint c")+chalk.gray(" Critical assist")),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 oe.prompt();if(/^ICOA-[A-Z]{2,3}-\d{1,6}$/i.test(a.trim())){ee=!0;try{await e.parseAsync(["node","icoa","exam","token",a.trim()])}catch{}return ee=!1,void oe.prompt()}if(/^[A-Z]{2}[0-9A-HJKMNP-TV-Z]{8}$/i.test(a.trim())){ee=!0;try{await e.parseAsync(["node","icoa","exam","token",a.trim().toUpperCase()])}catch{}return ee=!1,void oe.prompt()}const r=a.match(/^exam\s+([A-Z]{2}[0-9A-HJKMNP-TV-Z]{8})$/i);if(r){ee=!0;try{await e.parseAsync(["node","icoa","exam","token",r[1].toUpperCase()])}catch{}return ee=!1,void oe.prompt()}const d=a.match(/^exam\s+([A-Z]{2,3})$/i);if(d){ee=!0;try{await e.parseAsync(["node","icoa","exam","list",d[1]])}catch{}return ee=!1,void oe.prompt()}if("clear"===a||"cls"===a)return console.clear(),void oe.prompt();if("news"===a)return wo()?ho():console.log(chalk.gray(" No competition joined. Use ")+chalk.white("join <url>")+chalk.gray(".")),void oe.prompt();if("memo"===a){if(!wo())return console.log(chalk.gray(" Join a competition first: ")+chalk.white("join <url>")),void oe.prompt();ee=!0;try{await Ao()}catch{}return void(ee=!1)}const p=xo()||bo(l().userName||"");if("jury"===a&&p){ee=!0;try{await ko()}catch{}return void(ee=!1)}if(p&&xo()&&"scoreboard"===a.split(/\s+/)[0]){ee=!0;try{const o=await vo();if(console.log(),0===o.length)console.log(chalk.gray(" Scoreboard is empty."));else{console.log(chalk.bold.white(" Scoreboard"));for(const e of o.slice(0,50))console.log(chalk.gray(` ${String(e.pos).padStart(3)}. `)+chalk.white(e.name.padEnd(28).slice(0,28))+chalk.cyan(String(e.score)))}console.log()}catch{}return ee=!1,void oe.prompt()}if(p&&fo()){const o=a.split(/\s+/)[0];if(["challenges","open","submit","hint"].includes(o))return console.log(),console.log(chalk.yellow(" Team-leader account — the challenge bracket is not available here.")),console.log(chalk.gray(" Available: ")+chalk.white("scoreboard")+chalk.gray(" · ")+chalk.white("news")+chalk.gray(" · ")+chalk.white("jury")+chalk.gray(" · ")+chalk.white("time")+chalk.gray(" · ")+chalk.white("status")+chalk.gray(" · ")+chalk.white("logout")),console.log(),void oe.prompt()}if(a.startsWith("activate ")){const o=a.slice(9).trim(),e=c(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 — access tokens are case-insensitive and look like ")+chalk.cyan("ICOA-XXXX-XXXX-XXXX")+chalk.gray(" (4-char hex groups, e.g. ICOA-385E-27F2-B21B)")),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 oe.prompt()}if("activate"===a)return console.log(chalk.gray(" Usage: ")+chalk.white("activate <token>")),console.log(),void oe.prompt();const u=R();if(u){const o=a.toUpperCase().trim(),t=o=>{const e=u.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=u.questions.find(e=>e.number===o);return"short_answer"===e?.type},l=o=>{const e="demo-free"!==u.session.examId,t=u.questions.find(e=>e.number===o),n=F(t?.type,e);console.log(),console.log(chalk.yellow(` Q${o} is a practical question — letters (A/B/C/D) don't apply here.`)),n?(console.log(chalk.white(" Enter the AI chat for this question: ")+chalk.bold.cyan(n)),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(/^[ABCDE]$/.test(o)){const a=u._lastQ||1;if(t(a))return l(a),void oe.prompt();if(n(a))return console.log(),console.log(chalk.yellow(` Q${a} is a short-answer question — type a number, not a letter.`)),console.log(chalk.gray(" Example: ")+chalk.green("42")+chalk.gray(" (or ")+chalk.green(`exam answer ${a} 42`)+chalk.gray(")")),console.log(),void oe.prompt();ee=!0;try{await e.parseAsync(["node","icoa","exam","answer",String(a),o])}catch{}return ee=!1,void oe.prompt()}if(/^-?\d+$/.test(o)){const t=u._lastQ||1;if(n(t)){ee=!0;try{await e.parseAsync(["node","icoa","exam","answer",String(t),o])}catch{}return ee=!1,void oe.prompt()}}const s=o.match(/^(\d+)\s+([ABCDE]|-?\d+)$/);if(s){const o=parseInt(s[1],10);if(t(o))return l(o),void oe.prompt();ee=!0;try{await e.parseAsync(["node","icoa","exam","answer",s[1],s[2]])}catch{}return ee=!1,void oe.prompt()}}const h=a.split(/\s+/)[0].toLowerCase(),f=/^python3?(\.\d+)?$/.test(h),b="ls"===h||"pwd"===h,x=a.startsWith("!")||h.startsWith("!")||f||b,O=new Set(["nano","vi","vim","emacs","pico","ed","code","cat","less","more","head","tail","ls","pwd","cd","base64","xxd","hexdump","od","openssl","md5sum","sha256sum","grep","find","awk","sed","strings","file","curl","wget","nc","ping","nmap","echo","bash","sh","zsh","pip","pip3","node","npm"]),j=()=>{console.log(chalk.gray(" Shell commands need ")+chalk.bold.cyan("!")+chalk.gray(" prefix. Try: ")+chalk.bold.cyan(`!${a}`)),console.log()};if("embodied"===Ko&&!x&&!["doctor","demo2","learn","exam","ctf4eai","ctf4vla","eai","arena","lang","ref","setup","env","aienv","ipynb","sim"].includes(h))return O.has(h)?(j(),void oe.prompt()):(console.log(chalk.gray(" Not available in Embodied AI Security mode.")),console.log(chalk.white(" Try ")+chalk.bold.cyan("demo2")+chalk.gray(" (intro) · ")+chalk.bold.cyan("learn")+chalk.gray(" (curriculum) · ")+chalk.bold.cyan("exam <PD-token>")+chalk.gray(" (Paper D)")),console.log(),void oe.prompt());if("selection"===Ko&&!x&&!["doctor","exam","demo","demo2","retry","nations","next","prev","continue","setup","lang","ref","ai4ctf","ctf4ai","mark","unmark","review","submit","env","aienv","ipynb","sim"].includes(h)){if(O.has(h))return j(),void oe.prompt();if(console.log(chalk.gray(" Not available in Selection mode.")),u){const o=u._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 oe.prompt()}if("organizer"===Ko&&!["doctor","join","exam","demo","retry","next","prev","logout","setup","lang","ref","ctf","mark","unmark","review","submit"].includes(h))return console.log(chalk.gray(" Not available in Organizer mode. Switch via: setup")),console.log(),void oe.prompt();if(!("olympiad"!==Ko||i()&&y()||g(h)))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 oe.prompt();if(!["join","activate","challenges","ch","open","submit","flag","scoreboard","sb","status","time","ref","shell","files","view","connect","challenge","note","notes","logbook","log","man","manual","lang","setup","env","aienv","ipynb","ai4ctf","model","ctf","exam","demo","retry","nations","next","prev","continue","logout","ctf4ai","mark","unmark","review","submit","learn","demo2","ctf4eai","ctf4vla","theme","sim","arena","eai","learn-pull","doctor"].includes(h)){if(Ro.has(h))return console.log(chalk.yellow(` "${h}" is a shell command — the ICOA prompt isn't a shell.`)),console.log(chalk.gray(" In real-exam mode, prefix with ! to run inside the sandbox: ")+chalk.cyan(`!${a}`)),console.log(),void oe.prompt();if(/(?:^|\s)(?:\/(?!home\/|Users\/|tmp\/)|\.\.\/|~\/)/.test(a)&&!a.startsWith("cd ")){const o=/(?:^|\s)\/(?!home\/\w+\/icoa-workspace|Users\/\w+\/icoa-workspace|tmp\/)/.test(a),e=/\.\./.test(a);if(o||e)return console.log(chalk.red(" Blocked: access outside workspace is not allowed.")),console.log(chalk.gray(` Workspace: ${Do}`)),console.log(),void oe.prompt()}let o=a.startsWith("!")?a.slice(1).trim():a;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=so(),n=lo(a);ao({cwd:e,input:a,riskFlags:n}),n.length>0&&P()&&(console.log(chalk.yellow(` ⚠ Action flagged: ${n.join(", ")} — logged.`)),console.log()),/^(\S*python3?(\.\d+)?)\s*$/.test(o)&&(o=`PYTHONSTARTUP="${function(){const o=So(Oo(),".icoa");Io(o)||$o(o,{recursive:!0});const e=So(o,"python-startup.py");let t="";if(Io(e))try{t=To(e,"utf-8")}catch{}return t!==jo&&Eo(e,jo),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, PIL.Image, 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()),ee=!0;try{await no()&&await eo()?await to(o,oe):await Wo(o,oe,e)}catch{console.log(chalk.yellow(` Command failed: ${h}`))}return ee=!1,console.log(),void oe.prompt()}ee=!0;const N=a.trim(),L=N.toLowerCase();let D,q=null,U="";if(u)if("submit"===L)q="final";else if(L.startsWith("submit ")){let o=N.slice(7).trim();/^submit\s+/i.test(o)&&(o=o.replace(/^submit\s+/i,"").trim()),o=o.replace(/^["'`]+|["'`]+$/g,"").trim(),o&&/^ICOA\{[^}]*\}?$/i.test(o)?(q="flag",U=o):o&&/^[ABCDE]$/i.test(o)&&(q="flag",U=o.toUpperCase())}else/^ICOA\{[^}]+\}$/i.test(N)&&(q="flag",U=N);D="final"===q?["exam","submit"]:"flag"===q?["exam","answer",String(u?._lastQ||1),U]:function(o){const e=o.split(/\s+/),t=e[0].toLowerCase(),n=e.slice(1);if("aienv"===t&&["run","python","py"].includes((n[0]??"").toLowerCase()))return v(o);if(("submit"===t||"flag"===t)&&n.length>=2)return["ctf","submit",n[0],n.slice(1).join(" ").trim().replace(/^["'`]+|["'`]+$/g,"")];if("connect"===t&&n.length>=2){const e=v(o);return["connect",e[1]??n[0],e.slice(2).join(" ")]}const l={demo:["exam","demo"],retry:["exam","demo-retry"],nations:["exam","nations"],next:["exam","next"],prev:["exam","prev"],mark:["exam","mark",...n],unmark:["exam","unmark",...n],review:["exam","review"],logout:["ctf","logout"],join:["ctf","join",...n],activate:["ctf","activate",...n],challenges:["ctf","challenges"],ch:["ctf","challenges"],open:["ctf","open",...n],submit:["ctf","submit",...n],flag:["ctf","submit",...n],scoreboard:["ctf","scoreboard",...n],sb:["ctf","scoreboard",...n],status:["ctf","status"],time:["ctf","time"]};return l[t]?l[t]:["ref","shell","files","view","connect","challenge","note","notes","logbook","log","man","manual","lang","setup","env","aienv","ipynb","ai4ctf","model","ctf","exam","ctf4ai","doctor"].includes(t)?[t,...n]:e}(a);const Y="ctf"===D[0]&&"join"===D[1];Y&&oe.pause(),process.exit=()=>{throw new Error(Po)};try{await e.parseAsync(["node","icoa",...D])}catch(o){const e=o instanceof Error?o.message:String(o);if(e===Po);else if(e.includes("commander.unknownCommand")){const{distance:o}=await import("fastest-levenshtein"),e=["ctf","ref","shell","files","view","connect","challenge","note","notes","logbook","log","manual","lang","setup","env","aienv","ipynb","ai4ctf","exam","ctf4ai","theme","eai","clear","cls","quit","exit","back","menu","help","continue","activate","demo","challenges","status","scoreboard","join","logout"],t=h.split(/\s+/)[0]||h;let n={word:"",dist:1/0};for(const l of e){const e=o(t.toLowerCase(),l);e<n.dist&&(n={word:l,dist:e})}console.log(chalk.yellow(` Unknown command: ${h}.`)),n.dist>0&&n.dist<=2&&console.log(chalk.gray(" Did you mean: ")+chalk.bold.cyan(n.word)+chalk.gray("?")),console.log(chalk.gray(" Type ")+chalk.cyan("help")+chalk.gray(" for the full command list."))}else e.includes("commander.missingArgument")||"commander.missingArgument"===o?.code?"submit"===h||"flag"===h?(console.log(chalk.yellow(" Incomplete — usage: ")+chalk.cyan("submit <challenge#> <flag>")),console.log(chalk.gray(" Put the number AND the flag on one line, e.g. ")+chalk.cyan("submit 44 pecan{...}"))):(console.log(chalk.yellow(` Incomplete command: ${h}`)),console.log(chalk.gray(" Type ")+chalk.cyan("help")+chalk.gray(" for usage."))):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=zo,ee=!1,Y&&oe.resume()}C()?oe.setPrompt(chalk.magenta("icoa ai4ctf> ")):I()?oe.setPrompt(chalk.red("icoa ctf4ai> ")):T()&&oe.setPrompt(chalk.bold.cyan("icoa ctf4eai> ")),console.log(),oe.prompt()}),oe.on("SIGINT",()=>{if(console.log(),C()||I())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(R()){const o="demo-free"!==R().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(),oe.prompt()}),oe.on("close",()=>{co(),yo(),uo(),z(),m(),oo(),zo(0)}),"1"===process.env.ICOA_AUTO_RESUME&&(delete process.env.ICOA_AUTO_RESUME,M()&&(ee=!0,await _o(e),ee=!1,oe.setPrompt(C()?chalk.magenta("icoa ai4ctf> "):Lo()),oe.prompt()))}function Wo(o,t,n){return new Promise(l=>{const a=process.stdin,s=!!a.isTTY&&!!a.isRaw;if(t.pause(),a.isTTY&&"function"==typeof a.setRawMode)try{a.setRawMode(!1)}catch{}const r=e(o,{shell:!0,stdio:"inherit",cwd:n||process.cwd()}),i=()=>{if(a.isTTY&&"function"==typeof a.setRawMode&&s)try{a.setRawMode(!0)}catch{}t.resume(),l()};r.on("close",i),r.on("error",i)})}function Qo(o,e="olympiad"){return 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")),void 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> <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 & timer")),console.log(chalk.white(" time ")+chalk.gray("Countdown timer")),console.log(),console.log(chalk.bold.white(" AI Teammate")),console.log(chalk.white(" ai4ctf ")+chalk.gray("Free-chat with your AI teammate")),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("Interactive CTF shell (sandbox if present, else host)")),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("Add a logbook entry (in-exam: tags current Q)")),console.log(chalk.white(" logbook [n] ")+chalk.gray("View your logbook (in-exam: filter by Q)")),console.log(chalk.white(" log ")+chalk.gray("Session history")),console.log(chalk.white(" man <tool> ")+chalk.bold.yellow("★ ")+chalk.gray("How to use a tool (steghide, nmap…); ")+chalk.cyan("man tools")+chalk.gray(" = list. Bare ")+chalk.cyan("man")+chalk.gray(" = this list.")),console.log(),console.log(chalk.bold.white(" System")),console.log(chalk.white(" resume ")+chalk.gray("Continue your last learn card / exam")),console.log(chalk.white(" #<candidate> ")+chalk.gray("Stage a draft answer (never auto-submits)")),console.log(chalk.white(" drafts ")+chalk.gray("Review staged draft answers")),console.log(chalk.white(" bg <command> ")+chalk.gray("Run a shell command in the background")),console.log(chalk.white(" tasks [id] ")+chalk.gray("Background task panel / view output")),console.log(chalk.white(" doctor ")+chalk.gray("Self-check: version, Python, server, tools")),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)")),void 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")),void console.log())}
|
|
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 n,getConfig as l,saveConfig as a}from"./lib/config.js";import{ensureLangCache as s}from"./lib/translations-fetcher.js";import{loadUiPack as r}from"./lib/i18n.js";import{isActivated as i,activateToken as c,isFreeCommand as g,isDeviceMatch as y,recordExit as m,recordResume as d,isFirstRunOrUpgrade as p,markVersionSeen as u}from"./lib/access.js";import{setReplMode as h}from"./lib/ui.js";import{returnToMainMenu as w,printMenuHint as f,menuHintInline as b}from"./lib/menu-nav.js";import{setMainRl as x}from"./lib/main-rl.js";import{shellSplit as v}from"./lib/shell-split.js";import{isChatActive as C,handleChatMessage as A}from"./commands/ai4ctf.js";import{renderToolMan as k}from"./lib/tool-man.js";import{isCtf4aiActive as I,handleCtf4aiMessage as $}from"./commands/ctf4ai-demo.js";import{isCtf4VlaActive as T,handleCtf4VlaMessage as E}from"./commands/ctf4vla.js";import{isDemo2Active as S}from"./commands/demo2.js";import{loadDemo2Progress as O}from"./lib/demo2-progress.js";import{hasAnySimHistory as j,lastArmAt as N,lastBundledAt as L,lastSimAt as D}from"./lib/sim-cooldown.js";import{getExamState as R,getRealExamState as P,getDemoState as q,practicalChatCommand as F}from"./lib/exam-state.js";import{resolveResumePoint as M}from"./lib/resume.js";import{buildStatusLine as U}from"./lib/statusline.js";import{startBgTask as _,startAsyncTask as B,listBgTasks as W,getBgTask as Q,drainCompleted as Y,killAllBgTasks as z,runningCount as X}from"./lib/bgtasks.js";import{computeStart as J,renderStart as V}from"./commands/challenge.js";import{addDraft as G,listDrafts as H,removeDraft as Z,clearDrafts as K}from"./lib/drafts.js";import{resetTerminalTheme as oo}from"./lib/theme.js";import{ensureSandbox as eo,runInSandbox as to,isDockerAvailable as no}from"./lib/sandbox.js";import{checkShellRisk as lo,logShellAudit as ao,getActiveCwd as so}from"./lib/exam-sandbox.js";import{logCommand as ro}from"./lib/logger.js";import{startLogSync as io,stopLogSync as co}from"./lib/log-sync.js";import{installInputDynamics as go,uninstallInputDynamics as yo,stripPasteMarkers as mo}from"./lib/input-dynamics.js";import{startBroadcastPoll as po,stopBroadcastPoll as uo,showBroadcast as ho,isCompetitionJoined as wo,isMelCompetition as fo,isLeaderAccount as bo,isLeaderSession as xo,fetchPublicScoreboard as vo,isCommsActive as Co}from"./lib/comms.js";import{enterMemoScreen as Ao,enterJuryScreen as ko}from"./commands/comms.js";import{existsSync as Io,mkdirSync as $o,readFileSync as To,writeFileSync as Eo}from"node:fs";import{join as So}from"node:path";import{homedir as Oo}from"node:os";const jo="# 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\ntry: from PIL import Image\nexcept ImportError: pass\n",No=Date.now();function Lo(){return fo()?chalk.yellow("icoa 2026> "):P()?chalk.cyan("icoa exam> "):function(){const o=q();return!(!o||"demo-free"!==o.session?.examId)&&new Date(o.session.startedAt||0).getTime()>=No}()?chalk.yellow("icoa demo> "):chalk.green("icoa> ")}const Do=So(Oo(),"icoa-workspace"),Ro=new Set(["sudo","su","doas","pkexec","brew","apt","apt-get","yum","choco","npm","npx","pip","pip3","shutdown","reboot","halt","mkfs","fdisk","dd","iptables","ufw"]),Po="__REPL_NO_EXIT__",qo="2.5.1",Fo=["join","activate","challenges","ch","open","submit","flag","scoreboard","sb","status","time","ref","shell","files","view","connect","challenge","note","notes","logbook","log","man","manual","lang","setup","env","aienv","ipynb","ai4ctf","model","ctf","exam","demo","retry","nations","next","prev","continue","logout","ctf4ai","mark","unmark","review","learn","demo2","ctf4eai","ctf4vla","theme","sim","arena","eai","board","learn-pull","doctor","bg","tasks","drafts","help","menu","quit","q","exit","back","resume"],Mo={exam:["list","start","q","answer","submit","review","result","next","prev","mark","unmark","help"],ctf:["challenges","open","submit","scoreboard","status","join","logout","time"],challenge:["start","stop","status"],arena:["eai"],learn:["start","continue"]};function Uo(o){const e=o.replace(/^\s+/,"");if(/^\/\S*$/.test(e)){const o=e.slice(1).toLowerCase();return[Fo.filter(e=>e.startsWith(o)).map(o=>`/${o}`),e]}const t=e.split(/\s+/);if(t.length<=1){const o=(t[0]??"").toLowerCase();return[Fo.filter(e=>e.startsWith(o)).slice(),o]}if(2===t.length){const o=Mo[t[0].toLowerCase()];if(o){const e=t[1].toLowerCase();return[o.filter(o=>o.startsWith(e)).slice(),e]}}return[[],o]}async function _o(o){const e=M();if(!e)return null;const t=process.exit;process.exit=()=>{throw new Error(Po)};try{await o.parseAsync(["node","icoa",...e.command])}catch(o){if(!(o instanceof Error&&o.message===Po))throw o}finally{process.exit=t}return e}function Bo(){console.log(),console.log(` ${chalk.cyan.bold("🎯 ICOA 2026 · National Selection")}`),console.log(),console.log(" "+chalk.bold.cyan("learn ")+chalk.white("ACxxxxxxxx")+chalk.gray(" → ai4ctf-12 (AI as your CTF teammate)")),console.log(" "+chalk.bold.cyan("learn ")+chalk.white("CAxxxxxxxx")+chalk.gray(" → ctf4ai-12 (red-team software AI)")),console.log(" "+chalk.bold.cyan("learn ")+chalk.white("EAxxxxxxxx")+chalk.gray(" → ctf4eai-12 (red-team embodied AI)")),console.log(" "+chalk.bold.cyan("exam ")+chalk.white("<CC>xxxxxxxx")+chalk.gray(" → Paper A/B/C/D/E (country exam token)")),console.log(" "+chalk.bold.cyan("demo")+chalk.gray(" → free practice")),console.log(" "+chalk.bold.cyan("lang")+chalk.white(" es")+chalk.gray(" → switch language")),console.log(" "+chalk.gray("help · menu · quit")),console.log()}export async function startRepl(e,q){process.env.ICOA_INSIDE_REPL="1";let No=!1;if(fo()){const o=l().joinedAt,e=o=>new Intl.DateTimeFormat("en-CA",{timeZone:"Australia/Sydney",year:"numeric",month:"2-digit",day:"2-digit"}).format(o);o&&e(new Date(o))===e(new Date)||(a({ctfdUrl:"",token:"",sessionCookie:"",userId:null,userName:"",teamId:null,country:"",joinedAt:""}),No=!0)}const Mo=l(),Yo=n(),zo=process.exit.bind(process),Xo=i();if(Mo.language&&"en"!==Mo.language&&(await s(Mo.language,{silent:!0}),r(Mo.language)),Mo.demoCleanedForVersion!==qo){try{const{existsSync:o,unlinkSync:e}=await import("node:fs"),{join:t}=await import("node:path"),{getIcoaDir:n}=await import("./lib/config.js"),l=t(n(),"demo-state.json");o(l)&&e(l)}catch{}a({demoCleanedForVersion:qo})}const{select:Jo,confirm:Vo}=await import("@inquirer/prompts"),Go=Mo.mode||"",Ho=""===Go?"instant":["instant","learn","selection","olympiad"].includes(Go)?Go:"selection",Zo=[{name:` ${chalk.bold("⚡ Try ICOA in 30 seconds")} ${chalk.gray("—")} ${chalk.gray("watch · no token needed")}`,value:"instant"},{name:` ${chalk.bold("🧠 Learn")} ${chalk.gray("—")} ${chalk.gray("AC / CA / EA token · skill up")}`,value:"learn"},{name:` ${chalk.bold("🎯 National Selection")} ${chalk.gray("—")} ${chalk.gray("country exam token · aim for finals")}`,value:"selection"},{name:` ${chalk.bold("🏆 International Olympiad")} ${chalk.gray("—")} ${chalk.gray("finals stage · Sydney 2026 · full sandbox")}`,value:"olympiad"},{name:` ${chalk.gray("ℹ️ About ICOA")} ${chalk.gray("·")} ${chalk.gray("what this is · how to join")}`,value:"about"}];""===Go&&(console.log(chalk.green(" 👋 First time here? ")+chalk.white("Just press ")+chalk.bold.cyan("Enter")+chalk.white(" — you’ll see ICOA in 30 seconds, no signup needed.")),console.log()),console.log(chalk.gray(" Type ")+chalk.bold.cyan("menu")+chalk.gray(" from anywhere to return here.")),console.log();let Ko="";for(;!Ko;){let o;try{o=await Jo({message:"Mode",choices:Zo,default:Ho||"instant"})}catch(o){throw o instanceof Error&&"ExitPromptError"===o.name&&(console.log(),console.log(chalk.gray(" Goodbye.")),process.exit(0)),o}"about"!==o?Ko=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 (2-day standard + 1-day Pioneer)")),console.log(` ${chalk.green.bold("AI4CTF")}${chalk.gray(" [Day 1] AI as teammate — speed track, dynamic decay + top-3 bonus")}`),console.log(` ${chalk.red.bold("CTF4AI")}${chalk.gray(" [Day 2] AI as target — knowledge track, static value per challenge")}`),console.log(` ${chalk.cyan.bold("CTF4EAI")}${chalk.gray(" [Pioneer / Practical Round] Embodied AI — VLA + world models + sim-to-real")}`),console.log(),console.log(chalk.bold.white(" Scoring (in brief)")),console.log(chalk.gray(" · Standard medals: AI4CTF + CTF4AI combined · 1/12 Gold · 2/12 Silver · 3/12 Bronze")),console.log(chalk.gray(" · HMA: top 50% on either day, total below bronze cutoff")),console.log(chalk.gray(" · CTF4EAI: separate 15-finalist pool, G/S/B + 12 Finalist Awards")),console.log(),console.log(chalk.white(" Sydney, Australia")+chalk.gray(" · Jun 27 - Jul 2, 2026")),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"===Ko&&"olympiad"!==Ho){console.log(),console.log(chalk.yellow(" This mode will download ~500MB of CTF tools and AI models."));let o=!0;try{o=await Vo({message:"Continue?",default:!0})}catch(e){if(!(e instanceof Error&&"ExitPromptError"===e.name))throw e;o=!1}o||(Ko="selection",console.log(chalk.gray(" Switched to National Selection mode.")))}if(Ko!==Ho&&a({mode:Ko}),console.log(),"olympiad"===Ko&&p(qo)){u(qo),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 n of e)try{o(n.cmd,{stdio:"ignore"})}catch{t++}t>0?(console.log(chalk.yellow(` ${t} core competition libraries missing.`)),console.log(chalk.gray(" Run ")+chalk.white("env setup")+chalk.gray(" when you are ready to install them.")),console.log()):(console.log(chalk.green(" All core libraries ready.")),console.log())}if(q){const o=d();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()}}if("selection"===Ko)console.log(chalk.gray(" ─── exam hall · 90 min per paper once started · no pause ───")),console.log(),Bo();else if("embodied"===Ko)!function(){console.log(chalk.cyan.bold(" [🤖 ctf4eai — Embodied AI Security]")),console.log(),console.log(chalk.bold.cyan(" CTF4EAI")+chalk.gray(" — ICOA 2026's ")+chalk.bold.white("Pioneer Round")),console.log(chalk.gray(" an independent parallel track alongside the scored main events: ")+chalk.green("AI4CTF")+chalk.gray(" (Day 1) and ")+chalk.red("CTF4AI")+chalk.gray(" (Day 2)")),console.log(),console.log(chalk.white(" Embodied AI is at its ImageNet moment.")),console.log(chalk.gray(" VLA models, MuJoCo physics, real robot arms.")),console.log(chalk.gray(" Real models, real physics, real attack surfaces.")),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────"));const o=L()>0||D()>0,e=N()>0,t=j(),n=O(),l=null!==n&&"number"==typeof n.completedAt&&n.nextCardIndex>=n.totalCards,a=null!==n&&!l;if(l)console.log(chalk.bold.yellow(" Continue: ")+chalk.bold.cyan("④ learn LEARNDEMO01")+chalk.gray(" (demo2 ✓ · sim")+(e?chalk.gray(" ✓ · sim arm ✓)"):chalk.gray(")")));else if(a){const o=`${n.nextCardIndex} / ${n.totalCards}`;console.log(chalk.bold.yellow(" Continue: ")+chalk.bold.cyan("③ demo2")+chalk.gray(" (last left at card ")+chalk.bold.yellow(o)+chalk.gray(")"))}else o&&e?console.log(chalk.bold.yellow(" Next up: ")+chalk.bold.cyan("③ demo2")+chalk.gray(" → ")+chalk.bold.cyan("④ learn LEARNDEMO01")+chalk.gray(" (sim ✓ · sim arm ✓)")):t?console.log(chalk.bold.yellow(" Next up: ")+(e?chalk.bold.cyan("① sim")+chalk.gray(" → ")+chalk.bold.cyan("③ demo2"):chalk.bold.cyan("② sim arm")+chalk.gray(" → ")+chalk.bold.cyan("③ demo2"))+chalk.gray(" → ")+chalk.bold.cyan("④ learn LEARNDEMO01")):console.log(chalk.bold.yellow(" First time? Try in order: ")+chalk.bold.cyan("① sim")+chalk.gray(" → ")+chalk.bold.cyan("② sim arm")+chalk.gray(" → ")+chalk.bold.cyan("③ demo2")+chalk.gray(" → ")+chalk.bold.cyan("④ learn LEARNDEMO01"));console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.bold.green(" sim")+chalk.gray(" ")+chalk.bold.cyan("①")+chalk.gray(" ")+chalk.bold.yellow("★ INSTANT")+chalk.gray(" — 9-robot group dance, no wait, no setup")),console.log(chalk.bold.green(" sim arm")+chalk.gray(" ")+chalk.bold.cyan("②")+chalk.gray(" interactive 6-DOF arm sandbox — type joint values, watch")),console.log(chalk.bold.green(" demo2")+chalk.gray(" ")+chalk.bold.cyan("③")+chalk.gray(" ~8 min interactive intro + live MuJoCo sim")),console.log(chalk.bold.green(" learn LEARNDEMO01")+chalk.gray(" ")+chalk.bold.cyan("④")+chalk.gray(" free 11-card intro (paste this command as-is)")),console.log(chalk.bold.green(" learn ")+chalk.bold.yellow("EA")+chalk.gray(" + 8 chars full 96 / 360 curriculum (team-issued)")),console.log(chalk.bold.green(" exam ")+chalk.bold.yellow("PDxxxxxxxx")+chalk.gray(" Paper D (CTF4EAI) — national selection contestants")),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(),console.log(chalk.gray(" Three open tiers: ")+chalk.green("free 11")+chalk.gray(" · ")+chalk.yellow("registered 100")+chalk.gray(" · ")+chalk.bold.magenta("national team 480")+chalk.gray(" (PhD-ready)")),console.log(chalk.gray(" Country team contact: ")+chalk.cyan("australia@icoa2026.au")),console.log(),f(),console.log()}();else if("instant"===Ko)console.log(chalk.cyan.bold(" [⚡ Try ICOA in 30 seconds — no token needed]")),console.log(),console.log(chalk.gray(" Two free tastes — just type the word and press Enter:")),console.log(),console.log(chalk.bold.green(" demo ")+chalk.gray(" Paper A/B/C taste — 10 MCQs, no timer ")+chalk.bold.cyan("🎯 selection-style")),console.log(chalk.bold.green(" demo2")+chalk.gray(" Embodied-AI mini-demo — robots & MuJoCo ")+chalk.bold.yellow("🤖 hands-on")),console.log(),console.log(chalk.gray(" Type ")+chalk.bold.cyan("demo")+chalk.gray(" or ")+chalk.bold.cyan("demo2")+chalk.gray(" to start · ")+chalk.bold.cyan("menu")+chalk.gray(" to go back · ")+chalk.bold.cyan("quit")+chalk.gray(" to exit")),console.log(),a({mode:"selection"}),Ko="selection";else{if("learn"===Ko){console.log(chalk.cyan.bold(" [🧠 Learn]")),console.log(),console.log(chalk.gray(" Three tracks · same difficulty curve.")),console.log(),console.log(chalk.white(" Free 12-card demos — paste the full command:")),console.log(),console.log(chalk.gray(" 🤝 ai4ctf AI as your teammate ")+chalk.bold.yellow("learn AI4CTFDEMO01")),console.log(chalk.gray(" 🔓 ctf4ai AI as your target ")+chalk.bold.yellow("learn CTF4AIDEMO01")),console.log(chalk.gray(" 🤖 ctf4eai Embodied AI Security ")+chalk.bold.yellow("learn LEARNDEMO01")),console.log(),console.log(chalk.gray(" Got a cohort token? Type ")+chalk.bold.yellow("learn <TOKEN>")+chalk.gray(" (e.g. ")+chalk.bold.yellow("learn EAABCD1234")+chalk.gray(").")),console.log();try{const{input:o}=await import("@inquirer/prompts"),{resolveLearnInput:t}=await import("./lib/learn-input.js");console.log(chalk.gray(" Enter a token (or just press Enter to go back).")),console.log();const n=t(await o({message:chalk.bold.cyan("icoa learn>"),theme:{prefix:"",style:{message:o=>o}}}));if("token"===n.kind){console.log(),console.log(chalk.gray(" Opening ")+chalk.bold.cyan("learn")+chalk.gray(`: ${n.token}`)),console.log(),a({mode:"selection"}),process.env.ICOA_LEARN_EMBEDDED="1";try{await e.parseAsync(["node","icoa","learn",n.token])}finally{delete process.env.ICOA_LEARN_EMBEDDED}}else"ambiguous"!==n.kind&&"unknown"!==n.kind||(console.log(),console.log(chalk.yellow(" "+n.hint)),console.log(chalk.gray(" Returning to main menu — pick 🧠 Learn again to retry.")),console.log())}catch{}return a({mode:"selection"}),void w()}"organizer"===Ko?(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(),Yo?(console.log(chalk.green(` Logged in as ${Mo.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(),f(),console.log()):(console.log(` ${chalk.cyan.bold("🏆 ICOA 2026 · International Olympiad")}`),console.log(),Xo&&!y()?(console.log(chalk.red(" Token was activated on a different device.")),console.log(chalk.gray(" Contact organizer for assistance.")),console.log()):Yo?(console.log(chalk.green.bold(` Welcome back, ${Mo.userName}!`)),console.log(chalk.gray(` Connected to ${Mo.ctfdUrl}`)),console.log(chalk.gray(" Not you? Type ")+chalk.cyan("logout")+chalk.gray(" to switch accounts")),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 & timer")),console.log(chalk.white(" scoreboard")+chalk.gray(" Live rankings")),console.log(chalk.white(" memo")+chalk.gray(" Report an issue to organizers (one-way)")),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.bold.yellow(" ★ ")+chalk.bold.cyan("man <tool>")+chalk.gray(" How to USE a tool (steghide, nmap…) · ")+chalk.cyan("man tools")+chalk.gray(" = list")),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log(chalk.gray(" Tip: ")+chalk.cyan("help")+chalk.gray(" · ")+b()+chalk.gray(" · ")+chalk.cyan("Ctrl+C")+chalk.gray(" pauses · ")+chalk.cyan("quit")+chalk.gray(" closes")),console.log()):Xo?(Io(Do)||$o(Do,{recursive:!0}),console.log(chalk.green.bold(" Welcome, competitor!")),console.log(chalk.gray(` Workspace: ${Do}`)),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(chalk.bold.yellow(" ★ ")+chalk.bold.cyan("man <tool>")+chalk.gray(" How to USE a tool (steghide, nmap…) · ")+chalk.cyan("man tools")+chalk.gray(" = list")),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 · ")+b()+chalk.gray(" · ")+chalk.cyan("quit")+chalk.gray(" closes CLI")),console.log()):(console.log(chalk.bold.yellow(" 🏆 You reached the finals tier.")),console.log(chalk.gray(" Full sandbox · 110 CTF tools · Sydney 2026 · this is where finalists compete.")),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 · ")+b()+chalk.gray(" · ")+chalk.cyan("quit")+chalk.gray(" closes CLI")),console.log()))}e.exitOverride(),e.configureOutput({writeErr:()=>{},writeOut:o=>{console.log(o)}});const oe=o({input:process.stdin,output:process.stdout,prompt:Lo(),terminal:!0,completer:Uo});x(oe),go(!1!==l().bracketedPaste);let ee=!1;if(h(!0),io(),No&&(console.log(),console.log(chalk.yellow(" ⏱ A new competition day has started (Sydney time).")),console.log(chalk.gray(" For fairness, yesterday’s sign-in has been cleared. Type ")+chalk.cyan("join <url>")+chalk.gray(" to sign in with your account.")),console.log()),fo()){const o=l().userName||"this account";console.log(),console.log(chalk.yellow(" ⚠ This machine is signed in as ")+chalk.bold.white(o)+chalk.yellow(".")),console.log(chalk.gray(" Press ")+chalk.cyan("Enter")+chalk.gray(` to continue as ${o}, or type `)+chalk.cyan("logout")+chalk.gray(" to switch accounts.")),"logout"===(await new Promise(o=>{oe.question(chalk.white(" > "),e=>o(e.trim()))})).toLowerCase()&&(a({ctfdUrl:"",token:"",sessionCookie:"",userId:null,userName:"",teamId:null,country:""}),console.log(),console.log(chalk.green(" ✓ Logged out. ")+chalk.gray("Run ")+chalk.cyan("join <url>")+chalk.gray(" to sign in with your own account."))),console.log()}wo()&&po(()=>oe.prompt());let te=null;const ne=oe.prompt.bind(oe);oe.prompt=o=>{if(!(C()||I()||T()||S()||Co()||(oe.setPrompt(Lo()),o)))try{for(const o of Y()){const e="done"===o.status,t=o.cmd.length>40?`${o.cmd.slice(0,37)}…`:o.cmd,n=o.render?"":chalk.gray(" (")+chalk.white(t)+chalk.gray(") — ")+chalk.cyan(`tasks ${o.id}`);if(console.log((e?chalk.green(" ✓"):chalk.red(" ✗"))+chalk.white(` background task #${o.id} ${o.status}`)+n),o.render)try{o.render()}catch{}}const o=U();o&&o!==te?(console.log(o),te=o):o||(te=null)}catch{}ne(o)};const le=M();le&&(console.log(chalk.cyan(" ↻ Continue where you left off: ")+chalk.white(le.label)+(le.urgent?chalk.yellow(" (exam timer running)"):"")),console.log(chalk.gray(" type ")+chalk.cyan("resume")+chalk.gray(" to jump back in.")),console.log()),oe.prompt(),oe.on("line",async o=>{if(ee)return;let a=mo(o.trim());if(!a)return oe.setPrompt(C()?chalk.magenta("icoa ai4ctf> "):Lo()),void oe.prompt();if(a.startsWith("/")&&!a.startsWith("//")){const o=a.slice(1).split(/\s+/)[0].toLowerCase(),t=a.slice(1+o.length).trim();if("ai"===o)return ee=!0,await async function(o,e){if(C())return void(o?await A(o):console.log(chalk.gray(" You are already in the AI chat — just type your message.")));if(I())return void(o?await $(o):console.log(chalk.gray(" You are already in the CTF4AI target chat — just type your message.")));if(T())return void(o?await E(o):console.log(chalk.gray(" You are already in the Embodied-AI chat — just type your message.")));ro(o?`/ai ${o}`:"/ai");const t=process.exit;process.exit=()=>{throw new Error(Po)};try{await e.parseAsync(["node","icoa","ai4ctf"])}catch(o){if(!(o instanceof Error&&o.message===Po))throw o}finally{process.exit=t}o&&C()&&await A(o)}(t,e),ee=!1,oe.setPrompt(C()?chalk.magenta("icoa ai4ctf> "):Lo()),void oe.prompt();!(C()||I()||T()||S()||Co())&&Fo.includes(o)&&(a=a.slice(1))}if("resume"===a.toLowerCase()){const o=M();return o?(ro(`resume → ${o.command.join(" ")}`),ee=!0,await _o(e),ee=!1,oe.setPrompt(C()?chalk.magenta("icoa ai4ctf> "):Lo()),void oe.prompt()):(console.log(),console.log(chalk.gray(" Nothing to resume yet — start with ")+chalk.cyan("learn")+chalk.gray(" (curriculum) or ")+chalk.cyan("demo")+chalk.gray(" (taste).")),console.log(),void oe.prompt())}const s=C()||I()||T()||S();if(!s&&("bg"===a.toLowerCase()||/^bg\s/i.test(a))){const o=a.replace(/^bg\s*/i,"").trim();if(!o)return console.log(),console.log(chalk.gray(" Usage: ")+chalk.cyan("bg <command>")+chalk.gray(" — run a non-interactive shell command in the background.")),console.log(chalk.gray(" e.g. ")+chalk.cyan("bg nmap -p- 10.0.0.5")+chalk.gray(" · view: ")+chalk.cyan("tasks")),console.log(),void oe.prompt();const e=so(),t=lo(o);ao({cwd:e,input:`bg ${o}`,riskFlags:t}),t.length>0&&P()&&console.log(chalk.yellow(` ⚠ Action flagged: ${t.join(", ")} — logged.`));const n=_(o,e);return console.log(),console.log(chalk.cyan(` ▸ background task #${n.id} started`)+chalk.gray(` (${X()} running)`)),console.log(chalk.gray(" ")+chalk.white(o)),console.log(chalk.gray(" view: ")+chalk.cyan("tasks")+chalk.gray(" · output: ")+chalk.cyan(`tasks ${n.id}`)),console.log(),void oe.prompt()}if(!s&&("tasks"===a.toLowerCase()||/^tasks\s/i.test(a))){const o=a.replace(/^tasks\s*/i,"").trim(),e=W();if(console.log(),o){const e=Q(Number(o));if(!e||Number.isNaN(Number(o)))console.log(chalk.yellow(` No background task #${o}.`)+chalk.gray(" Type ")+chalk.cyan("tasks")+chalk.gray(" to list them."));else{const o="running"===e.status?chalk.yellow:"done"===e.status?chalk.green:chalk.red;console.log(o(` #${e.id} [${e.status}]`)+chalk.white(` ${e.cmd}`)+(null!==e.exitCode?chalk.gray(` (exit ${e.exitCode})`):"")),console.log(chalk.gray(" ─────────────────────────────────────────────"));const t=e.output.trim();if(t)for(const o of t.split("\n"))console.log(chalk.gray(" ")+chalk.white(o));else console.log(chalk.gray(" (no output yet)"))}}else if(0===e.length)console.log(chalk.gray(" No background tasks. Start one with ")+chalk.cyan("bg <command>")+chalk.gray("."));else{console.log(chalk.bold.white(" Background tasks"));for(const o of e){const e="running"===o.status?chalk.yellow("●"):"done"===o.status?chalk.green("✓"):chalk.red("✗"),t=Math.round(((o.endedAt??Date.now())-o.startedAt)/1e3),n=(o.endedAt?`${t}s`:`${t}s…`).padStart(5),l=o.cmd.length>48?`${o.cmd.slice(0,45)}…`:o.cmd;console.log(` ${e} `+chalk.white(`#${o.id}`)+chalk.gray(` ${n} `)+chalk.white(l))}console.log(chalk.gray(" output: ")+chalk.cyan("tasks <id>"))}return console.log(),void oe.prompt()}if(!s&&/&\s*$/.test(a)&&/^challenge\s+start\s+/i.test(a)){const o=a.replace(/\s*&\s*$/,"").trim().replace(/^challenge\s+start\s+/i,"").trim().split(/\s+/),e=o.some(o=>"-n"===o||"--new"===o),t=o.find(o=>"-n"!==o&&"--new"!==o);if(!t)return console.log(),console.log(chalk.gray(" Usage: ")+chalk.cyan("challenge start <id> &")),console.log(),void oe.prompt();if(!n())return console.log(),console.log(chalk.red(" Not connected. ")+chalk.gray("Run ")+chalk.cyan("join <url>")+chalk.gray(" first.")),console.log(),void oe.prompt();ro(`challenge start ${t}${e?" --new":""} & (background)`);const l=B(`challenge start ${t}`,()=>J(t,e),o=>V(t,o));return console.log(),console.log(chalk.cyan(` ▸ background task #${l.id}: `)+chalk.white(`starting ${t}`)+chalk.gray(" — you can keep working; the connection info appears here when it is ready.")),console.log(),void oe.prompt()}if(!s&&a.startsWith("#")){const o=a.slice(1).trim();if(!o)return console.log(),console.log(chalk.gray(" Usage: ")+chalk.cyan("#<candidate answer>")+chalk.gray(" — stage a flag/answer WITHOUT submitting it. Review with ")+chalk.cyan("drafts")+chalk.gray(".")),console.log(),void oe.prompt();const e=R(),t=e?.session?.examId??null,n=e?e._lastQ||1:null,l=G(o,t,n);return ro(`draft #${l.id} (not submitted)`),console.log(),console.log(chalk.green(` ✎ draft #${l.id} saved`)+(n?chalk.gray(` (Q${n})`):"")+chalk.yellow(" — NOT submitted.")),console.log(chalk.gray(" review with ")+chalk.cyan("drafts")+chalk.gray("; when sure, submit it the normal way.")),console.log(),void oe.prompt()}if(!s&&("drafts"===a.toLowerCase()||/^drafts\s/i.test(a))){const o=a.replace(/^drafts\s*/i,"").trim().toLowerCase();if("clear"===o){const o=K();return console.log(),console.log(chalk.gray(` Cleared ${o} draft${1===o?"":"s"}.`)),console.log(),void oe.prompt()}const e=o.match(/^(?:rm|remove|del|delete)\s+(\d+)$/);if(e){const o=Z(Number(e[1]));return console.log(),console.log(o?chalk.gray(` Removed draft #${e[1]}.`):chalk.yellow(` No draft #${e[1]}.`)),console.log(),void oe.prompt()}const t=R(),n=H(t?.session?.examId??null);if(console.log(),0===n.length)console.log(chalk.gray(" No drafts yet. Stage a candidate with ")+chalk.cyan("#ICOA{maybe_this}")+chalk.gray(" — it won’t submit."));else{console.log(chalk.bold.white(" Draft answers")+chalk.gray(" (staged — not submitted)"));for(const o of n){const e=o.q?chalk.cyan(`Q${o.q}`.padStart(4)):chalk.gray(" · ");console.log(chalk.gray(` #${o.id}`)+` ${e} `+chalk.white(o.text.length>56?`${o.text.slice(0,53)}…`:o.text))}console.log(chalk.gray(" remove: ")+chalk.cyan("drafts rm <id>")+chalk.gray(" · clear all: ")+chalk.cyan("drafts clear"))}return console.log(),void oe.prompt()}if("menu"===a||"menu confirm"===a)return P()&&"menu confirm"!==a?(console.log(),console.log(chalk.yellow(" Real exam active. Progress is auto-saved.")),console.log(chalk.gray(" To return to main menu anyway, type ")+chalk.white("menu confirm")),console.log(),void oe.prompt()):void w(oe);if(C()){ee=!0;const o=await A(a);return ee=!1,"exit"===o&&oe.setPrompt(Lo()),void oe.prompt()}if(I()){ee=!0;const o=await $(a);return ee=!1,"exit"!==o&&"solved"!==o||oe.setPrompt(Lo()),void oe.prompt()}if(T()){ee=!0;const o=await E(a);return ee=!1,"exit"===o&&oe.setPrompt(Lo()),oe.setPrompt(T()?chalk.bold.cyan("icoa ctf4eai> "):Lo()),void oe.prompt()}if(ro(a),"exit"===a)return R()?(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 oe.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"===Ko&&Bo(),void oe.prompt());if("quit"===a||"q"===a||"quit confirm"===a){const o=R();return o&&"demo-free"!==o.session.examId&&"quit confirm"!==a?(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 oe.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)."))),co(),yo(),uo(),z(),m(),console.log(chalk.gray(" Session saved. Use ")+chalk.white("icoa --resume")+chalk.gray(" to continue.")),oo(),void zo(0))}if("back"===a){const o=R(),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=l();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"===Ko?Bo():console.log(chalk.gray(" Already at main menu."))}return void oe.prompt()}if("help"===a||"?"===a){if(R()){ee=!0;try{await e.parseAsync(["node","icoa","exam","help"])}catch{}return ee=!1,void oe.prompt()}return Qo(i(),Ko),void oe.prompt()}if("man"===a||"manual"===a)return Qo(i(),Ko),console.log(),console.log(chalk.bold.yellow(" ★ Stuck on a tool? ")+chalk.bold.cyan("man <tool>")+chalk.gray(" e.g. ")+chalk.cyan("man steghide")+chalk.gray(" · ")+chalk.cyan("man tools")+chalk.gray(" = full list")),void oe.prompt();if(/^(?:man|manual)\s+\S/i.test(a))return k(a.replace(/^(?:man|manual)\s+/i,"")),void oe.prompt();if("more help"===a.toLowerCase()&&R()){ee=!0;try{await e.parseAsync(["node","icoa","exam","more-help"])}catch{}return ee=!1,void oe.prompt()}if("continue"===a.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 this demo, your AI teammate gives help at 3 levels:")),console.log(chalk.yellow(" hint a")+chalk.gray(" General guidance")),console.log(chalk.yellow(" hint b")+chalk.gray(" Deep analysis")),console.log(chalk.yellow(" hint c")+chalk.gray(" Critical assist")),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 oe.prompt();if(/^ICOA-[A-Z]{2,3}-\d{1,6}$/i.test(a.trim())){ee=!0;try{await e.parseAsync(["node","icoa","exam","token",a.trim()])}catch{}return ee=!1,void oe.prompt()}if(/^[A-Z]{2}[0-9A-HJKMNP-TV-Z]{8}$/i.test(a.trim())){ee=!0;try{await e.parseAsync(["node","icoa","exam","token",a.trim().toUpperCase()])}catch{}return ee=!1,void oe.prompt()}const r=a.match(/^exam\s+([A-Z]{2}[0-9A-HJKMNP-TV-Z]{8})$/i);if(r){ee=!0;try{await e.parseAsync(["node","icoa","exam","token",r[1].toUpperCase()])}catch{}return ee=!1,void oe.prompt()}const d=a.match(/^exam\s+([A-Z]{2,3})$/i);if(d){ee=!0;try{await e.parseAsync(["node","icoa","exam","list",d[1]])}catch{}return ee=!1,void oe.prompt()}if("clear"===a||"cls"===a)return console.clear(),void oe.prompt();if("news"===a)return wo()?ho():console.log(chalk.gray(" No competition joined. Use ")+chalk.white("join <url>")+chalk.gray(".")),void oe.prompt();if("memo"===a){if(!wo())return console.log(chalk.gray(" Join a competition first: ")+chalk.white("join <url>")),void oe.prompt();ee=!0;try{await Ao()}catch{}return void(ee=!1)}const p=xo()||bo(l().userName||"");if("jury"===a&&p){ee=!0;try{await ko()}catch{}return void(ee=!1)}if(p&&xo()&&"scoreboard"===a.split(/\s+/)[0]){ee=!0;try{const o=await vo();if(console.log(),0===o.length)console.log(chalk.gray(" Scoreboard is empty."));else{console.log(chalk.bold.white(" Scoreboard"));for(const e of o.slice(0,50))console.log(chalk.gray(` ${String(e.pos).padStart(3)}. `)+chalk.white(e.name.padEnd(28).slice(0,28))+chalk.cyan(String(e.score)))}console.log()}catch{}return ee=!1,void oe.prompt()}if(p&&fo()){const o=a.split(/\s+/)[0];if(["challenges","open","submit","hint"].includes(o))return console.log(),console.log(chalk.yellow(" Team-leader account — the challenge bracket is not available here.")),console.log(chalk.gray(" Available: ")+chalk.white("scoreboard")+chalk.gray(" · ")+chalk.white("news")+chalk.gray(" · ")+chalk.white("jury")+chalk.gray(" · ")+chalk.white("time")+chalk.gray(" · ")+chalk.white("status")+chalk.gray(" · ")+chalk.white("logout")),console.log(),void oe.prompt()}if(a.startsWith("activate ")){const o=a.slice(9).trim(),e=c(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 — access tokens are case-insensitive and look like ")+chalk.cyan("ICOA-XXXX-XXXX-XXXX")+chalk.gray(" (4-char hex groups, e.g. ICOA-385E-27F2-B21B)")),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 oe.prompt()}if("activate"===a)return console.log(chalk.gray(" Usage: ")+chalk.white("activate <token>")),console.log(),void oe.prompt();const u=R();if(u){const o=a.toUpperCase().trim(),t=o=>{const e=u.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=u.questions.find(e=>e.number===o);return"short_answer"===e?.type},l=o=>{const e="demo-free"!==u.session.examId,t=u.questions.find(e=>e.number===o),n=F(t?.type,e);console.log(),console.log(chalk.yellow(` Q${o} is a practical question — letters (A/B/C/D) don't apply here.`)),n?(console.log(chalk.white(" Enter the AI chat for this question: ")+chalk.bold.cyan(n)),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(/^[ABCDE]$/.test(o)){const a=u._lastQ||1;if(t(a))return l(a),void oe.prompt();if(n(a))return console.log(),console.log(chalk.yellow(` Q${a} is a short-answer question — type a number, not a letter.`)),console.log(chalk.gray(" Example: ")+chalk.green("42")+chalk.gray(" (or ")+chalk.green(`exam answer ${a} 42`)+chalk.gray(")")),console.log(),void oe.prompt();ee=!0;try{await e.parseAsync(["node","icoa","exam","answer",String(a),o])}catch{}return ee=!1,void oe.prompt()}if(/^-?\d+$/.test(o)){const t=u._lastQ||1;if(n(t)){ee=!0;try{await e.parseAsync(["node","icoa","exam","answer",String(t),o])}catch{}return ee=!1,void oe.prompt()}}const s=o.match(/^(\d+)\s+([ABCDE]|-?\d+)$/);if(s){const o=parseInt(s[1],10);if(t(o))return l(o),void oe.prompt();ee=!0;try{await e.parseAsync(["node","icoa","exam","answer",s[1],s[2]])}catch{}return ee=!1,void oe.prompt()}}const h=a.split(/\s+/)[0].toLowerCase(),f=/^python3?(\.\d+)?$/.test(h),b="ls"===h||"pwd"===h,x=a.startsWith("!")||h.startsWith("!")||f||b,O=new Set(["nano","vi","vim","emacs","pico","ed","code","cat","less","more","head","tail","ls","pwd","cd","base64","xxd","hexdump","od","openssl","md5sum","sha256sum","grep","find","awk","sed","strings","file","curl","wget","nc","ping","nmap","echo","bash","sh","zsh","pip","pip3","node","npm"]),j=()=>{console.log(chalk.gray(" Shell commands need ")+chalk.bold.cyan("!")+chalk.gray(" prefix. Try: ")+chalk.bold.cyan(`!${a}`)),console.log()};if("embodied"===Ko&&!x&&!["doctor","demo2","learn","exam","ctf4eai","ctf4vla","eai","arena","lang","ref","setup","env","aienv","ipynb","sim"].includes(h))return O.has(h)?(j(),void oe.prompt()):(console.log(chalk.gray(" Not available in Embodied AI Security mode.")),console.log(chalk.white(" Try ")+chalk.bold.cyan("demo2")+chalk.gray(" (intro) · ")+chalk.bold.cyan("learn")+chalk.gray(" (curriculum) · ")+chalk.bold.cyan("exam <PD-token>")+chalk.gray(" (Paper D)")),console.log(),void oe.prompt());if("selection"===Ko&&!x&&!["doctor","exam","demo","demo2","retry","nations","next","prev","continue","setup","lang","ref","ai4ctf","ctf4ai","mark","unmark","review","submit","env","aienv","ipynb","sim"].includes(h)){if(O.has(h))return j(),void oe.prompt();if(console.log(chalk.gray(" Not available in Selection mode.")),u){const o=u._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 oe.prompt()}if("organizer"===Ko&&!["doctor","join","exam","demo","retry","next","prev","logout","setup","lang","ref","ctf","mark","unmark","review","submit"].includes(h))return console.log(chalk.gray(" Not available in Organizer mode. Switch via: setup")),console.log(),void oe.prompt();if(!("olympiad"!==Ko||i()&&y()||g(h)))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 oe.prompt();if(!["join","activate","challenges","ch","open","submit","flag","scoreboard","sb","status","time","ref","shell","files","view","connect","challenge","note","notes","logbook","log","man","manual","lang","setup","env","aienv","ipynb","ai4ctf","model","ctf","exam","demo","retry","nations","next","prev","continue","logout","ctf4ai","mark","unmark","review","submit","learn","demo2","ctf4eai","ctf4vla","theme","sim","arena","eai","board","learn-pull","doctor"].includes(h)){if(Ro.has(h))return console.log(chalk.yellow(` "${h}" is a shell command — the ICOA prompt isn't a shell.`)),console.log(chalk.gray(" In real-exam mode, prefix with ! to run inside the sandbox: ")+chalk.cyan(`!${a}`)),console.log(),void oe.prompt();if(/(?:^|\s)(?:\/(?!home\/|Users\/|tmp\/)|\.\.\/|~\/)/.test(a)&&!a.startsWith("cd ")){const o=/(?:^|\s)\/(?!home\/\w+\/icoa-workspace|Users\/\w+\/icoa-workspace|tmp\/)/.test(a),e=/\.\./.test(a);if(o||e)return console.log(chalk.red(" Blocked: access outside workspace is not allowed.")),console.log(chalk.gray(` Workspace: ${Do}`)),console.log(),void oe.prompt()}let o=a.startsWith("!")?a.slice(1).trim():a;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=so(),n=lo(a);ao({cwd:e,input:a,riskFlags:n}),n.length>0&&P()&&(console.log(chalk.yellow(` ⚠ Action flagged: ${n.join(", ")} — logged.`)),console.log()),/^(\S*python3?(\.\d+)?)\s*$/.test(o)&&(o=`PYTHONSTARTUP="${function(){const o=So(Oo(),".icoa");Io(o)||$o(o,{recursive:!0});const e=So(o,"python-startup.py");let t="";if(Io(e))try{t=To(e,"utf-8")}catch{}return t!==jo&&Eo(e,jo),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, PIL.Image, 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()),ee=!0;try{await no()&&await eo()?await to(o,oe):await Wo(o,oe,e)}catch{console.log(chalk.yellow(` Command failed: ${h}`))}return ee=!1,console.log(),void oe.prompt()}ee=!0;const N=a.trim(),L=N.toLowerCase();let D,q=null,U="";if(u)if("submit"===L)q="final";else if(L.startsWith("submit ")){let o=N.slice(7).trim();/^submit\s+/i.test(o)&&(o=o.replace(/^submit\s+/i,"").trim()),o=o.replace(/^["'`]+|["'`]+$/g,"").trim(),o&&/^ICOA\{[^}]*\}?$/i.test(o)?(q="flag",U=o):o&&/^[ABCDE]$/i.test(o)&&(q="flag",U=o.toUpperCase())}else/^ICOA\{[^}]+\}$/i.test(N)&&(q="flag",U=N);D="final"===q?["exam","submit"]:"flag"===q?["exam","answer",String(u?._lastQ||1),U]:function(o){const e=o.split(/\s+/),t=e[0].toLowerCase(),n=e.slice(1);if("aienv"===t&&["run","python","py"].includes((n[0]??"").toLowerCase()))return v(o);if(("submit"===t||"flag"===t)&&n.length>=2)return["ctf","submit",n[0],n.slice(1).join(" ").trim().replace(/^["'`]+|["'`]+$/g,"")];if("connect"===t&&n.length>=2){const e=v(o);return["connect",e[1]??n[0],e.slice(2).join(" ")]}const l={demo:["exam","demo"],retry:["exam","demo-retry"],nations:["exam","nations"],next:["exam","next"],prev:["exam","prev"],mark:["exam","mark",...n],unmark:["exam","unmark",...n],review:["exam","review"],logout:["ctf","logout"],join:["ctf","join",...n],activate:["ctf","activate",...n],challenges:["ctf","challenges"],ch:["ctf","challenges"],open:["ctf","open",...n],submit:["ctf","submit",...n],flag:["ctf","submit",...n],scoreboard:["ctf","scoreboard",...n],sb:["ctf","scoreboard",...n],status:["ctf","status"],time:["ctf","time"]};return l[t]?l[t]:["ref","shell","files","view","connect","challenge","note","notes","logbook","log","man","manual","lang","setup","env","aienv","ipynb","ai4ctf","model","ctf","exam","ctf4ai","doctor"].includes(t)?[t,...n]:e}(a);const Y="ctf"===D[0]&&"join"===D[1];Y&&oe.pause(),process.exit=()=>{throw new Error(Po)};try{await e.parseAsync(["node","icoa",...D])}catch(o){const e=o instanceof Error?o.message:String(o);if(e===Po);else if(e.includes("commander.unknownCommand")){const{distance:o}=await import("fastest-levenshtein"),e=["ctf","ref","shell","files","view","connect","challenge","note","notes","logbook","log","manual","lang","setup","env","aienv","ipynb","ai4ctf","exam","ctf4ai","theme","eai","board","clear","cls","quit","exit","back","menu","help","continue","activate","demo","challenges","status","scoreboard","join","logout"],t=h.split(/\s+/)[0]||h;let n={word:"",dist:1/0};for(const l of e){const e=o(t.toLowerCase(),l);e<n.dist&&(n={word:l,dist:e})}console.log(chalk.yellow(` Unknown command: ${h}.`)),n.dist>0&&n.dist<=2&&console.log(chalk.gray(" Did you mean: ")+chalk.bold.cyan(n.word)+chalk.gray("?")),console.log(chalk.gray(" Type ")+chalk.cyan("help")+chalk.gray(" for the full command list."))}else e.includes("commander.missingArgument")||"commander.missingArgument"===o?.code?"submit"===h||"flag"===h?(console.log(chalk.yellow(" Incomplete — usage: ")+chalk.cyan("submit <challenge#> <flag>")),console.log(chalk.gray(" Put the number AND the flag on one line, e.g. ")+chalk.cyan("submit 44 pecan{...}"))):(console.log(chalk.yellow(` Incomplete command: ${h}`)),console.log(chalk.gray(" Type ")+chalk.cyan("help")+chalk.gray(" for usage."))):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=zo,ee=!1,Y&&oe.resume()}C()?oe.setPrompt(chalk.magenta("icoa ai4ctf> ")):I()?oe.setPrompt(chalk.red("icoa ctf4ai> ")):T()&&oe.setPrompt(chalk.bold.cyan("icoa ctf4eai> ")),console.log(),oe.prompt()}),oe.on("SIGINT",()=>{if(console.log(),C()||I())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(R()){const o="demo-free"!==R().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(),oe.prompt()}),oe.on("close",()=>{co(),yo(),uo(),z(),m(),oo(),zo(0)}),"1"===process.env.ICOA_AUTO_RESUME&&(delete process.env.ICOA_AUTO_RESUME,M()&&(ee=!0,await _o(e),ee=!1,oe.setPrompt(C()?chalk.magenta("icoa ai4ctf> "):Lo()),oe.prompt()))}function Wo(o,t,n){return new Promise(l=>{const a=process.stdin,s=!!a.isTTY&&!!a.isRaw;if(t.pause(),a.isTTY&&"function"==typeof a.setRawMode)try{a.setRawMode(!1)}catch{}const r=e(o,{shell:!0,stdio:"inherit",cwd:n||process.cwd()}),i=()=>{if(a.isTTY&&"function"==typeof a.setRawMode&&s)try{a.setRawMode(!0)}catch{}t.resume(),l()};r.on("close",i),r.on("error",i)})}function Qo(o,e="olympiad"){return 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")),void 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> <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 & timer")),console.log(chalk.white(" time ")+chalk.gray("Countdown timer")),console.log(),console.log(chalk.bold.white(" AI Teammate")),console.log(chalk.white(" ai4ctf ")+chalk.gray("Free-chat with your AI teammate")),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("Interactive CTF shell (sandbox if present, else host)")),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("Add a logbook entry (in-exam: tags current Q)")),console.log(chalk.white(" logbook [n] ")+chalk.gray("View your logbook (in-exam: filter by Q)")),console.log(chalk.white(" log ")+chalk.gray("Session history")),console.log(chalk.white(" man <tool> ")+chalk.bold.yellow("★ ")+chalk.gray("How to use a tool (steghide, nmap…); ")+chalk.cyan("man tools")+chalk.gray(" = list. Bare ")+chalk.cyan("man")+chalk.gray(" = this list.")),console.log(),console.log(chalk.bold.white(" System")),console.log(chalk.white(" resume ")+chalk.gray("Continue your last learn card / exam")),console.log(chalk.white(" #<candidate> ")+chalk.gray("Stage a draft answer (never auto-submits)")),console.log(chalk.white(" drafts ")+chalk.gray("Review staged draft answers")),console.log(chalk.white(" bg <command> ")+chalk.gray("Run a shell command in the background")),console.log(chalk.white(" tasks [id] ")+chalk.gray("Background task panel / view output")),console.log(chalk.white(" doctor ")+chalk.gray("Self-check: version, Python, server, tools")),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)")),void 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")),void console.log())}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "icoa-cli",
|
|
3
|
-
"version": "2.19.
|
|
3
|
+
"version": "2.19.375",
|
|
4
4
|
"description": "ICOA CLI — The world's first CLI-native cyber & AI security olympiad terminal: AI4CTF (Day 1), CTF4AI (Day 2), VLA4CTF (Pioneer Round — embodied AI)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|