icoa-cli 2.19.401 → 2.19.403
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.js +1 -1
- 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/lib/kaggle.js +1 -1
- package/dist/lib/learn-contest.d.ts +16 -3
- package/dist/lib/learn-contest.js +1 -1
- package/dist/lib/learn-outcome.d.ts +2 -0
- package/dist/lib/learn-outcome.js +1 -0
- package/dist/lib/learn-render.js +1 -1
- 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{execSync as t,spawn as n}from"node:child_process";import{getMainRl as r}from"../lib/main-rl.js";import{ai4ioaiPromptLabel as l,handleAi4ioaiMessage as s,startAi4ioaiChat as a}from"./ai4ioai.js";import{existsSync as i,mkdirSync as c,readFileSync as d,writeFileSync as u}from"node:fs";import{dirname as g,join as m}from"node:path";import{fileURLToPath as y}from"node:url";import{loadCurriculumById as p,validateEAToken as h,syncProgress as f,syncCardFeedback as w}from"../lib/learn-curricula.js";import{normalizeTokenBody as b}from"../lib/token-format.js";import{fetchMyScores as k,streamLearnTutor as $,submitPqs as v,viewPqsTop as C,viewHonors as A}from"../lib/learn-ai.js";import{contestReset as I,contestStart as _,contestSubmit as T,fetchGate as E,timeLeft as x}from"../lib/learn-contest.js";import{getConfig as S,saveConfig as M}from"../lib/config.js";import{loadLearnState as P,saveLearnState as L,newLearnState as q,updateStreak as O,markCardComplete as D,recordMCQ as R,recordCheck as N,markPracticalComplete as j,addAchievement as W}from"../lib/learn-state.js";import{renderWelcome as F,renderKnowledgeCard as B,renderMCQCard as J,renderMCQFeedback as U,renderPracticalCard as z,renderPracticalSuccess as V,renderSimDemoCard as Z,renderMilestone as H,renderArenaTaskCard as K,renderComingSoonCard as Q,renderStatus as Y,renderPhaseComplete as X,renderCurriculumComplete as G,setLearnAiEnabled as ee,setLearnPqsViewCards as oe,renderHonorWall as te}from"../lib/learn-render.js";import{printError as ne}from"../lib/ui.js";export function registerLearnCommand(re){re.command("learn [token]").description("Enter learn mode (free <TRACK>DEMO01 demo, or team-issued token for full curriculum)").action(async re=>{re&&re.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 <your token>")+chalk.gray(" (2 letters + 8 chars) — from your country team leader.")),console.log(),re="LEARNDEMO01");let le=re.trim().toUpperCase(),se=null;const ae=/^[A-Z0-9]+DEMO01$/i.test(le),ie=/^[A-Z]{2}[A-Z0-9]{8}$/i.test(le);let ce=!1,de=!1;if(ae)console.log(),console.log(chalk.gray(" Loading demo curriculum...")),se=await p(le,le);else if(ie){console.log(),console.log(chalk.gray(" Validating token..."));let e=await h(le);if(!e.ok){const o=b(le);if(o!==le){const t=await h(o);t.ok&&(console.log(chalk.yellow(` Corrected look-alike characters: ${le} → ${o}`)),e=t,le=o)}}if(!e.ok)return ne(`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"):"")),ce=!0===e.aiEnabled,de=!0===e.gated,console.log(chalk.gray(" Loading curriculum… (downloads ~1 MB; up to a minute on slow venue Wi-Fi — please wait)")),se=await p(e.curriculumId||"LEARNDEMO01",le),!se)return ne(`Could not open a learn curriculum for token '${le}'.`),console.log(),console.log(chalk.gray(" If this is an ")+chalk.bold.yellow("EXAM token")+chalk.gray(", start it with ")+chalk.bold.green(`exam ${le}`)+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 ${le}`)+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(!se)return ne(`Unknown learn token: ${le}`),console.log(),console.log(chalk.gray(" Available tokens:")),console.log(chalk.gray(" ")+chalk.bold.green("<TRACK>DEMO01")+chalk.gray(" free demo, anyone can use — e.g. ")+chalk.bold.green("LEARNDEMO01")),console.log(chalk.gray(" ")+chalk.bold.yellow("2 letters + 8 chars")+chalk.gray(" full curriculum (issued by team leader)")),console.log(),console.log(chalk.gray(" Every track has a free demo — ask for its name if you were not given one.")),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 ue=P(),ge=!1;try{ue&&ue.token===le?O(ue):(ue=q(le,se.id,se.totalCards),ge=!0),L(ue),F(se,ue,ge)}catch{return ne("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 ${le}`)+chalk.gray(".")),void console.log()}const me=r(),ye=null!==me,pe=ye?me.listeners("line").slice():[];ye&&me.removeAllListeners("line");const he=ye?me:o({input:process.stdin,output:process.stdout,terminal:!0}),fe=ye?he.prompt:null;ye&&(he.prompt=e.prototype.prompt);const we=()=>{ye&&fe&&(he.prompt=fe)},be=()=>{he.setPrompt(_e?chalk.bold.green(`icoa ${l()}> `):chalk.bold.cyan(Ae?"icoa learn ai> ":"icoa learn> ")),he.prompt()};let ke=null,$e=null,ve=null,Ce=null,Ae=!1,Ie=!1,_e=!1;const Te=new Map,Ee=()=>{let e=Te.get(ue.currentCard);return e||(e=[],Te.set(ue.currentCard,e)),e};let xe=!1;const Se=new Set;let Me=!1,Pe=0;const Le=(S().language||"en").toLowerCase().startsWith("zh");ee(ce),oe(Se);const qe=[];let Oe=null;const De=new Promise(e=>{Oe=e}),Re=()=>{const e=Oe;Oe=null,e?.()},Ne="1"===process.env.ICOA_LEARN_EMBEDDED,je=e=>se.cards.find(o=>o.number===e);let We=null,Fe=null;const Be=!le.endsWith("DEMO01")&&/^[A-Z]{2}[A-Z0-9]{8}$/i.test(le),Je=async()=>{if(!Be)return;const e=await E(le);e.ok&&e.data&&(We=e.data)},Ue=e=>{const o=se.modules.find(o=>e>=o.cardRange[0]&&e<=o.cardRange[1]);return o?o.number:1},ze=e=>{if(!de||!We)return null;const o=Ue(e);if(o<=We.unlocked_through_phase)return null;const t=We.phases.find(e=>e.phase===We.unlocked_through_phase),n=[chalk.yellow(Le?` 🔒 阶段 ${o} 未解锁 — 先完成阶段 ${We.unlocked_through_phase} 的核心任务:`:` 🔒 Phase ${o} is locked — finish phase ${We.unlocked_through_phase}'s core tasks first:`)];return t?.contest&&!t.contest.passed&&n.push(chalk.gray(Le?` ⚖ 判分赛: 最好 ${t.contest.best_ac}/${t.contest.n} AC,需 ${t.contest.need} — 输入 `:` ⚖ contest: best ${t.contest.best_ac}/${t.contest.n} AC, need ${t.contest.need} — type `)+chalk.bold.green("contest")),t?.research&&!t.research.passed&&n.push(chalk.gray(Le?` ⭐ 提问研究: ${t.research.good_cards}/${t.research.need_cards} 卡达到 ${t.research.min_score} 分 — 用 /ai 研究,再 /submit 评分`:` ⭐ research: ${t.research.good_cards}/${t.research.need_cards} cards at ${t.research.min_score}+ — research with /ai, then /submit`)),n.join("\n")},Ve=async e=>de?null===ze(e)?null:(await Je(),ze(e)):null;de&&await Je();const Ze=async e=>{const o=se.modules.find(o=>o.cardRange[1]===e);if(!o)return!1;const t=se.modules.find(e=>e.number===o.number+1);let n;if(ce){const e=await k({token:le,cardLo:o.cardRange[0],cardHi:o.cardRange[1]}).catch(()=>({ok:!1,scores:void 0}));n=e.ok?e.scores??[]:void 0}Be&&await Je();const r=We?.phases.find(e=>e.phase===o.number);return X(se,ue,o,t,n,r),!0},He=()=>{Me=!0,console.log(chalk.gray(Le?" 按 Enter 进入下一阶段…":" Press Enter to continue to the next phase…"))},Ke=()=>{const e=je(ue.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":B(e,se),e.check?(ve=e.number,Pe=Date.now()):(D(ue,e.number),L(ue));break;case"mcq":J(e,se),ke=e.number;break;case"practical":z(e,se),$e=e.number;break;case"sim_demo":Z(e,se),D(ue,e.number),L(ue);break;case"milestone":H(e,se),W(ue,e.badge),D(ue,e.number),L(ue);break;case"arena_task":K(e,se),D(ue,e.number),L(ue);break;case"coming_soon":Q(e,se),D(ue,e.number),L(ue)}},Qe=()=>{le.endsWith("DEMO01")?console.log(chalk.yellow(Le?" 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(Le?" 此 token 未开通 AI 导师(高级功能)。":" This token does not include the AI tutor (premium).")),console.log(chalk.gray(Le?" 找你的领队 / 协调人换开通 AI 的 token。":" Ask your team leader / coordinator for an AI-enabled token."))),console.log()},Ye=async(e,o)=>{if(ce){Ie=!0;try{console.log(),console.log(chalk.gray("deep"===o?Le?" 🤖 AI 导师(深度思考中,思考过程为灰色小字)…":" 🤖 AI tutor (deepthink — reasoning shown in gray)…":Le?" 🤖 AI 导师(快速模式)…":" 🤖 AI tutor (quick mode)…")),console.log();let t=!1,n=!1;const r=await $({token:le,cardNumber:ue.currentCard,question:e,mode:o,history:Ee().slice(-8)},e=>{"reasoning"===e.kind?(t||(process.stdout.write(" "),t=!0),process.stdout.write(chalk.dim(e.text.replace(/\n/g,"\n ")))):(n||(process.stdout.write(t?"\n\n ":" "),n=!0),process.stdout.write(chalk.white(e.text.replace(/\n/g,"\n "))))});(t||n)&&console.log(),r.ok?r.offtopic?(console.log(),console.log(chalk.yellow(Le?" (被判定与本卡主题无关 — 跑题的问题不会被回答,并会拉低本卡的提问评分)":" (judged unrelated to this card — off-topic questions aren't answered and will lower this card's prompt score)"))):(Ee().push({role:"user",content:e},{role:"assistant",content:r.answer}),null!=r.spent&&null!=r.cap&&r.spent/r.cap>.8&&console.log(chalk.gray(` (${Le?"AI 额度":"AI budget"}: ${Math.round(r.spent/1e3)}k / ${Math.round(r.cap/1e3)}k tokens)`))):(console.log(),console.log(chalk.yellow(` ${r.message||(Le?"AI 导师暂不可用。":"AI tutor unavailable.")}`))),null!==ve&&console.log(chalk.gray(Le?" (本卡的判断题还等着你作答 — y 或 n)":" (the y/n check on this card is still waiting — answer y or n)")),console.log()}finally{Ie=!1}}else Qe()},Xe=e=>e.match(/.{1,64}(\s|$)/g)?.map(e=>e.trim())??[e],Ge=async e=>{const o=e.trim().toLowerCase();if(!Ie){if(null!==Ce){const o=e.trim(),t=Ce;if(Ce=null,""===o)console.log(chalk.gray(Le?" 已取消(未提交)。":" Cancelled (nothing submitted)."));else{const e=Pe>0?Date.now()-Pe:void 0;await w(le,{curriculum_id:se.id,card_number:t,feedback_text:o,time_on_card_ms:e})?console.log(chalk.green(Le?" ✓ 已收到,谢谢帮助完善这张卡。":" ✓ Thanks — your report was recorded.")):console.log(chalk.yellow(Le?" ⚠ 未能送达服务器 — 这条报告没有保存,请稍后再按 e 重试。":" ⚠ Could not reach the server — this report was NOT saved. Try e again later."))}return console.log(),void be()}if(xe){xe=!1;const o=e.trim().toLowerCase();return"y"===o||"yes"===o?await(async()=>{Ie=!0;try{console.log(),console.log(chalk.gray(Le?" 📊 提交中 — Panda 正在评审你的提问(通常几十秒,最长一两分钟,请稍候)…":" 📊 Submitting — Panda is reviewing how you asked (usually well under two minutes)…"));const e=await v({token:le,cardNumber:ue.currentCard});if(console.log(),!e.ok)return console.log(chalk.yellow(` ${e.message||(Le?"评审暂不可用。":"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(Le?" 提问评分 ":" Prompt score ")+o(`${e.score??0} / 100`)+chalk.gray(" "+(e=>{switch(e){case"green":return"🟢 "+(Le?"你把 AI 当作被审视的研究伙伴":"you used the AI as a research partner");case"blue":return"🔵 "+(Le?"你把 AI 当作实验助手":"you used the AI as a lab assistant");case"yellow":return"🟡 "+(Le?"你把 AI 当作讲师":"you used the AI as a lecturer");default:return"🔴 "+(Le?"你把 AI 当作答案机":"you used the AI as an answer machine")}})(e.tier))),e.manipulation&&console.log(chalk.red(Le?" ⚠ 检测到试图操纵评分,分数已按规则大幅降低":" ⚠ an attempt to manipulate the score was detected — the score was reduced sharply")),null!=e.rank&&null!=e.entrants){const o=!0===e.inTop3,t=Le?` 本卡排名 #${e.rank} / ${e.entrants}`:` Rank on this card: #${e.rank} of ${e.entrants}`;console.log((o?chalk.bold.green(t):chalk.white(t))+(o?chalk.bold.magenta(Le?" — 进入 TOP 3!输入 /view 看另两名的提问":" — TOP 3! type /view to read the other top prompts"):""))}if(console.log(chalk.gray(Le?` 第 ${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 Xe(e.feedback))console.log(chalk.white(" "+o))}e.improve&&(console.log(),console.log(chalk.cyan(Le?" 改进提问: ":" 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(` (${Le?"AI 额度":"AI budget"}: ${Math.round(e.spent/1e3)}k / ${Math.round(e.cap/1e3)}k tokens)`)),console.log()})(e),!0===e.inTop3?Se.add(ue.currentCard):Se.delete(ue.currentCard);const o=await A({token:le,curriculumId:se.id,cardNumber:ue.currentCard});o.ok&&o.totals&&o.chapters&&(te({totals:o.totals,chapters:o.chapters,floor:o.floor}),console.log())}finally{Ie=!1}})():(console.log(chalk.gray(Le?" 已取消(未提交,不消耗次数)。":" Cancelled — nothing submitted, no attempt used.")),console.log()),null!==ve&&(console.log(chalk.gray(Le?" (本卡的判断题还等着你作答 — y 或 n)":" (the y/n check on this card is still waiting — answer y or n)")),console.log()),void be()}if(Me)return Me=!1,Ke(),void be();if(o){if(_e)return"exit"===await s(e.trim())&&(_e=!1),void be();if(Ae){if(["q","quit","exit","back","b","menu"].includes(o))return Ae=!1,console.log(chalk.gray(Le?" 已退出 AI 导师,回到卡片。":" Left the AI tutor — back to the card.")),console.log(),void be();const t=e.trim().startsWith("!"),n=t?e.trim().slice(1).trim():e.trim();return n&&await Ye(n,t?"fast":"deep"),void be()}if("tmux"===o||o.startsWith("tmux ")){try{t("command -v tmux",{stdio:"ignore"})}catch{return console.log(chalk.yellow(" tmux is not installed yet.")),console.log(chalk.gray(" Install it with ")+chalk.bold.cyan("darwin"===process.platform?"brew install tmux":"sudo apt install tmux")+chalk.gray(Le?" — 或 quit 回到 icoa> 后运行 ":" — or quit to icoa> and run ")+chalk.bold.cyan("env setup")+chalk.gray(".")),console.log(),void be()}return await new Promise(o=>{const t=process.stdin,r=!!t.isTTY&&!!t.isRaw;if(he.pause(),t.isTTY&&"function"==typeof t.setRawMode)try{t.setRawMode(!1)}catch{}const l=n(e.trim(),{shell:!0,stdio:"inherit"}),s=()=>{if(t.isTTY&&"function"==typeof t.setRawMode&&r)try{t.setRawMode(!0)}catch{}he.resume(),o()};l.on("close",s),l.on("error",s)}),console.log(),void be()}if("ai4ioai"===o||"/ai4ioai"===o)return await a()&&(_e=!0),void be();if("/ai"===o||"/ai!"===o||o.startsWith("/ai ")||o.startsWith("/ai! ")){if(!ce)return Qe(),void be();const t=o.startsWith("/ai!"),n=e.trim().slice(t?4:3).trim();if(!n){Ae=!0;const e=je(ue.currentCard),o=e?e.title??e.badge??"":"";return console.log(),console.log(chalk.bold.magenta(" 🤖 AI ")+chalk.bold.white(Le?"AI 导师":"AI tutor")+(o?chalk.gray(Le?` — 主题「${o}」`:` — topic “${o}”`):"")),console.log(chalk.gray(Le?" 直接输入问题(默认深度思考) · ! 开头 = 快答 · q = 返回卡片":" Type a question (deepthink by default) · prefix ! = quick answer · q = back to card")),console.log(),void be()}return await Ye(n,t?"fast":"deep"),void be()}if("/submit"===o||o.startsWith("/submit ")||"/research"===o||o.startsWith("/research ")){if(!ce)return Qe(),void be();const e=je(ue.currentCard),o=e?e.title??e.badge??"":"";return console.log(),console.log(chalk.bold.magenta(" 📊 ")+chalk.bold.white(Le?"Panda 提问评审":"Panda's prompt review")+(o?chalk.gray(Le?` — 主题「${o}」`:` — topic “${o}”`):"")),console.log(chalk.gray(Le?" 评的是你在本卡上「怎么问 AI」— 你与 /ai 导师的整段对话会被":" It scores HOW you asked the AI on this card — your whole /ai")),console.log(chalk.gray(Le?" 整体评审,给出分数、名次和改进建议。":" conversation is reviewed for a score, a rank, and advice.")),console.log(chalk.gray(Le?" 每张卡最多 2 次评分;评出的最好成绩进本卡 Top-3 榜。":" Max 2 graded attempts per card; your best enters the card’s top-3 board.")),console.log(),console.log(chalk.white(Le?" 确认提交本卡对话评分?":" Submit this card’s conversation for grading?")+chalk.gray(Le?" y = 提交 · 其他 = 取消":" y = submit · anything else = cancel")),console.log(),xe=!0,void be()}if("/view"===o)return ce?(await(async()=>{Ie=!0;try{console.log();const e=await C({token:le,cardNumber:ue.currentCard});if(!e.ok)return console.log(chalk.yellow(` ${e.message||(Le?"暂时无法查看。":"View unavailable right now.")}`)),"not_top3"===e.code&&Se.delete(ue.currentCard),void console.log();null!=e.myRank&&e.myRank<=3&&Se.add(ue.currentCard),console.log(chalk.bold.white(Le?` 🏆 本卡 Top-3 提问 — 第 ${ue.currentCard} 张 · ${e.entrants??0} 人上榜`:` 🏆 Top-3 prompts — card ${ue.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 t=Xe(e);console.log(chalk.gray(` Q${o+1} `)+chalk.white(t[0]??""));for(const e of t.slice(1))console.log(chalk.white(" "+e))}),0===o.questions.length&&console.log(chalk.gray(Le?" (无可显示的提问)":" (no questions to show)"))}console.log()}finally{Ie=!1}})(),void be()):(Qe(),void be());if("/honors"===o||"honors"===o)return ae?(console.log(),console.log(chalk.gray(Le?" 荣誉榜属于正式 learn token — 用团队签发的 token 进来即可查看和参赛。":" The honor board belongs to full learn tokens — join with a team-issued token to view and compete.")),console.log(),void be()):(await(async()=>{Ie=!0;try{console.log();const e=await A({token:le,curriculumId:se.id,cardNumber:ue.currentCard});if(!e.ok||!e.totals||!e.chapters)return console.log(chalk.yellow(` ${e.message||(Le?"荣誉榜暂时不可用。":"Honor board unavailable right now.")}`)),void console.log();if(console.log(chalk.bold.white(Le?` 🏆 本卡实时 Top-3 — 第 ${ue.currentCard} 张`:` 🏆 Live top-3 — card ${ue.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(Le?" (本卡还没有人上榜)":" (no one has medaled on this card yet)"));console.log(),te({totals:e.totals,chapters:e.chapters,floor:e.floor}),console.log()}finally{Ie=!1}})(),void be());if("menu"!==o&&"menu confirm"!==o)if("quit"!==o&&"exit"!==o&&"q"!==o){if("status"===o)return Y(se,ue),void be();if("lang"===o||o.startsWith("lang ")){const e=o.slice(4).trim();if(!e)return console.log(chalk.gray(" 当前语言 / current: ")+chalk.white(S().language||"en")+chalk.gray(" · ")+chalk.bold.green("lang zh")+chalk.gray(" / ")+chalk.bold.green("lang en")),void be();const t=se.cards.some(e=>null!=e._zh),n=e.toLowerCase().startsWith("zh")?"zh":e.toLowerCase(),r="en"===n||"zh"===n&&t,l=r?n:"en";return M({language:l}),r?console.log(chalk.green(" ✓ ")+chalk.gray("语言 / language → ")+chalk.white(l)):console.log(chalk.yellow(" ! ")+chalk.gray(`${e} 本课暂无翻译,已用英文 / not available — using English`)),ve=null,ke=null,$e=null,Ce=null,Me=!1,xe=!1,Ke(),void be()}if("sim"===o){const e=je(ue.currentCard);return e&&"sim_demo"===e.type?(function(e){const o=function(){const e=g(y(import.meta.url)),o=[m(e,"..","..","panda","mujoco-launcher.py"),m(e,"..","..","..","panda","mujoco-launcher.py")];for(const e of o)if(i(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 t={baseline:"baseline",prompt_injected:"prompt_inj",patch_attacked:"patch",modality_confused:"confused"}[e]||"baseline";console.log(chalk.gray(` Launching MuJoCo viewer (scenario: ${t})...`)),console.log(chalk.gray(" Close the window or press ESC to return to learn mode.")),n("python3",[o,t,"--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 be()):(console.log(chalk.gray(" (sim only available on simulation cards)")),void be())}if("bookmark"===o){const e=ue.currentCard;return ue.bookmarks.includes(e)||ue.bookmarks.push(e),L(ue),console.log(chalk.gray(` ✓ Card ${e} bookmarked.`)),void be()}if("e"===o)return Ce=ue.currentCard,console.log(chalk.gray(Le?" 描述问题(回车提交 / 空行取消):":" Describe the issue (Enter to submit / blank to cancel):")),void be();if("back"===o||"b"===o)return ue.currentCard>1&&(ue.currentCard-=1),ke=null,$e=null,ve=null,L(ue),Ke(),void be();if("contest"===o||"contest start"===o||"contest status"===o||"contest reset"===o){if(!Be)return console.log(chalk.yellow(Le?" 判分赛需要正式课程 token(demo 不支持)。":" Contests need a full curriculum token (demos not supported).")),void be();const e=Ue(ue.currentCard);if("contest reset"===o){const o=await I(le,e);return o.ok?(console.log(chalk.yellow(Le?` 已放弃当前计时(阶段 ${e})。输入 contest 重新开赛。`:` Live attempt abandoned (phase ${e}). Type contest to start a fresh one.`)),Fe=null):console.log(chalk.red(` ✗ ${o.message||"reset failed"}`)),void be()}console.log(chalk.gray(Le?" 连接判分赛…":" Contacting contest server…"));const t=await _(le,e);if(!t.ok||!t.data)return"no_contest"===t.code?console.log(chalk.gray(Le?` 本阶段(${e})暂无判分赛。`:` No contest configured for phase ${e} yet.`)):console.log(chalk.red(` ✗ ${t.message||"contest unavailable"}`)),void be();const n=t.data,r=m(process.cwd(),"icoa-contest",`${se.id}-phase${e}`);c(r,{recursive:!0}),n.problems.forEach((e,o)=>{const t=[`# ${e.title} (${e.id})`,"",e.statement,""];let n=0;for(const o of e.samples??[])n+=1,t.push(`## Sample ${n}`,"","Input:","```",o.in,"```","Output:","```",o.out,"```","");u(m(r,`problem-${e.id}.md`),t.join("\n"));const l=m(r,`sol-${e.id}.py`);if(!i(l)){const t=(e.samples??[])[0],n=t?`# Sample: input ${JSON.stringify(t.in)} -> output ${JSON.stringify(t.out)}\n`:"";u(l,`# ${e.title}\n# Full statement: problem-${e.id}.md (same folder)\n`+n+"# Read from stdin (input()), print the answer to stdout, no prompts.\n"+`# When ready, back in icoa: submit ${o+1}\n\n`)}}),Fe={phase:e,dir:r,data:n};const l=x(n.deadline);console.log(),console.log(chalk.bold.cyan(` ⚖ ${n.title||(Le?`阶段 ${e} 判分赛`:`Phase ${e} contest`)}`)+chalk.gray(Le?` · 第 ${n.attempt} 次尝试${n.resumed?"(续)":""}`:` · attempt ${n.attempt}${n.resumed?" (resumed)":""}`)),console.log(chalk.white(Le?` ${n.n} 题 · AC ${n.need} 题过关 · 限时 ${n.duration_minutes} 分钟`:` ${n.n} problems · AC ${n.need} to pass · ${n.duration_minutes} min`)+(l?chalk.bold.yellow(Le?` ⏱ 剩 ${l}`:` ⏱ ${l} left`):chalk.red(Le?" ⏱ 已超时 — contest reset 重开":" ⏱ time up — contest reset to retry"))),console.log(),n.problems.forEach((e,o)=>{const t=n.solved.includes(e.id);console.log(chalk.gray(` ${o+1}. `)+(t?chalk.green("✓ "):chalk.gray("· "))+chalk.white(e.title)+chalk.gray(` (${e.id})`))}),console.log(),console.log(chalk.gray(Le?" 题面与答题文件已写入:":" Statements + solution files written to:")),console.log(chalk.bold.white(` ${r}`)),console.log();const s=n.problems.findIndex(e=>!n.solved.includes(e.id))+1||1;return console.log(chalk.white(Le?" 怎么做题(三步循环):":" How to work (3-step loop):")),console.log(chalk.gray(Le?" ① 读题 ":" ① Read ")+chalk.cyan(`problem-…q${s}.md`)+chalk.gray(Le?" (任意编辑器/预览打开)":" (open in any editor/viewer)")),console.log(chalk.gray(Le?" ② 写解 ":" ② Solve ")+chalk.cyan(`sol-…q${s}.py`)+chalk.gray(Le?" (VS Code / nano / 记事本都行,存盘)":" (VS Code / nano / any editor, save it)")),console.log(chalk.gray(Le?" ③ 提交 ":" ③ Submit ")+chalk.bold.green(`submit ${s}`)+chalk.gray(Le?" ← 回到这里输入,立刻出判分":" ← type here, verdict comes back live")),console.log(),console.log(chalk.gray(Le?" submit <题号> <文件路径> 可指定别的文件 · contest 随时回看此列表 · contest reset 重开计时":" submit <id> <file> for a custom path · contest re-shows this list · contest reset restarts the clock")),void be()}if("submit"===o)return console.log(chalk.yellow(Le?" 用法: ":" Usage: ")+chalk.bold.green("submit <题号>")+chalk.gray(Le?" 例如 submit 1 判第 1 题(默认判 sol-…q1.py;submit 1 <路径> 判指定文件)":" e.g. submit 1 grades problem 1 (defaults to sol-…q1.py; submit 1 <path> for a custom file)")),Fe||console.log(chalk.gray(Le?" 还没开赛 — 先输入 contest。":" No contest open yet — type contest first.")),void be();if(o.startsWith("submit ")){const e=o.split(/\s+/);let t=e[1]||"";const n=e[2];if(!Fe)return console.log(chalk.yellow(Le?" 先输入 contest 进入本阶段判分赛。":" Type contest first to open this phase's contest.")),void be();const r=Fe.data.problems,l=parseInt(t,10);if(Number.isInteger(l)&&l>=1&&l<=r.length&&!r.some(e=>e.id===t)&&(t=r[l-1].id),!r.some(e=>e.id===t))return console.log(chalk.yellow(Le?` 未知题号 ${t} — 输入 contest 查看题目列表。`:` Unknown problem ${t} — type contest to list problems.`)),void be();const s=n||m(Fe.dir,`sol-${t}.py`);let a;try{a=d(s,"utf-8")}catch{return console.log(chalk.red(Le?` 读不到文件: ${s}`:` Cannot read file: ${s}`)),void be()}console.log(chalk.gray(Le?" ⚖ 判题中…(隐藏用例,最长约 1-2 分钟)":" ⚖ Judging… (hidden tests, may take a minute)"));const i=await T(le,Fe.phase,t,a);if(!i.ok||!i.data)return"no_active_session"===i.code?console.log(chalk.yellow(Le?" 没有进行中的计时(可能已超时)— 输入 contest 查看/重开。":" No live timed attempt (time may be up) — type contest.")):console.log(chalk.red(` ✗ ${i.message||"judging failed"}`)),void be();const c=i.data;if(c.already_ac)console.log(chalk.green(Le?" 该题已 AC(不重复判)。":" Already AC on this problem."));else if("AC"===c.verdict)console.log(chalk.bold.green(" ✓ AC")+chalk.gray(` · ${c.total_tests??"?"} tests · ${c.time_ms??"?"}ms`)),Fe.data.solved.includes(t)||Fe.data.solved.push(t);else{const e="TLE"===c.verdict?chalk.yellow:chalk.red;console.log(e(` ✗ ${c.verdict}`)+chalk.gray(c.test?Le?` · 挂在第 ${c.test} 个用例(过 ${c.passed_tests}/${c.total_tests})`:` · failed test ${c.test} (passed ${c.passed_tests}/${c.total_tests})`:""))}c.late&&console.log(chalk.yellow(Le?" ⏱ 超时提交 — 不计入本次成绩。":" ⏱ Submitted past the deadline — not counted."));const u=x(c.deadline||"");if(console.log(chalk.white(Le?` 进度: ${c.ac_count}/${c.need} 题(共 ${c.n})`:` Progress: ${c.ac_count}/${c.need} AC (of ${c.n})`)+(u?chalk.gray(` · ⏱ ${u}`):"")),c.passed&&!c.late)console.log(chalk.bold.green(Le?" 🏁 本阶段判分赛已过关!":" 🏁 Contest passed!")),await Je();else if(!c.late){const e=r.findIndex(e=>!Fe.data.solved.includes(e.id));e>=0&&console.log(chalk.gray(Le?" 下一题: ":" Next: ")+chalk.bold.green(`submit ${e+1}`)+chalk.gray(` ${r[e].title}`))}return void be()}if(o.startsWith("card ")){const e=o.slice(5).trim(),t=parseInt(e,10);if(!Number.isInteger(t)||t<1||t>se.totalCards)return console.log(chalk.yellow(` Card number must be 1..${se.totalCards}. Try: `)+chalk.bold.green("card 1")),void be();const n=await Ve(t);return n?(console.log(n),void be()):(ue.currentCard=t,ke=null,$e=null,ve=null,L(ue),Ke(),void be())}if(null!==ve&&["y","yes","n","no"].includes(o)){const e=je(ve);if(e&&"knowledge"===e.type&&e.check){const t=o.startsWith("y")?"y":"n",n=t===e.check.answer,r=Date.now()-Pe;D(ue,e.number),N(ue,e.number,{answer:t,correct:n,submittedAt:(new Date).toISOString()}),L(ue);const{renderCheckFeedback:l}=await import("../lib/learn-render.js");if(l(e,t,n,!1),qe.push(f(le,{card_number:e.number,event_type:"check_answered",check_answer:t,check_correct:n,time_on_card_ms:r}).catch(()=>{})),ve=null,e.solution&&e.solution.length>0)return void be();if(ue.currentCard<se.totalCards){const e=ue.currentCard,o=await Ve(e+1);if(o)return await Ze(e),console.log(o),void be();ue.currentCard+=1,L(ue),await Ze(e)?He():Ke(),be()}else G(se,ue),be();return}}if(null!==ke&&["a","b","c","d"].includes(o)){const e=je(ke);if(e&&"mcq"===e.type){const t=o.toUpperCase(),n=t===e.answer;if(R(ue,e.number,{answer:t,correct:n,submittedAt:(new Date).toISOString()}),D(ue,e.number),L(ue),U(e,t,n,ue),qe.push(f(le,{card_number:e.number,event_type:"mcq_answered",mcq_answer:t,mcq_correct:n}).catch(()=>{})),ke=null,n)if(ue.currentCard<se.totalCards){const e=await Ve(ue.currentCard+1);if(e)return console.log(e),void be();ue.currentCard+=1,L(ue),Ke()}else G(se,ue),be();else be();return}}if(null!==$e){if("done"===o){const e=je($e);if(e&&"practical"===e.type)return j(ue,e.number),D(ue,e.number),L(ue),V(e),$e=null,void be()}if("skip"===o)return D(ue,$e),L(ue),console.log(chalk.gray(" Skipped (counts as not completed).")),console.log(),$e=null,void be()}if("ok"===o||"next"===o||"continue"===o||"n"===o){if(null!==ke)return console.log(chalk.yellow(" Please answer the MCQ first (A / B / C / D).")),void be();if(null!==$e)return console.log(chalk.yellow(" Please type ")+chalk.bold.green("done")+chalk.yellow(" or ")+chalk.bold.yellow("skip")+chalk.yellow(" for the practical.")),void be();if(null!==ve)return console.log(chalk.yellow(" Please answer the check above (")+chalk.bold.green("y")+chalk.yellow(" or ")+chalk.bold.green("n")+chalk.yellow(").")),void be();const e=ue.currentCard,o=e<se.totalCards?await Ve(e+1):null;return o?(await Ze(e),console.log(o),void be()):(ue.currentCard+=1,L(ue),ue.currentCard>se.totalCards?G(se,ue):await Ze(e)?He():Ke(),void be())}if(null!==ve)return console.log(chalk.yellow(" Please answer the check above (")+chalk.bold.green("y")+chalk.yellow(" or ")+chalk.bold.green("n")+chalk.yellow(").")),void be();if(null!==ke)return console.log(chalk.yellow(" Please answer the MCQ first (A / B / C / D).")),void be();if(null!==$e)return console.log(chalk.yellow(" Please type ")+chalk.bold.green("done")+chalk.yellow(" or ")+chalk.bold.yellow("skip")+chalk.yellow(" for the practical.")),void be();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")),be()}else if(qe.length>0&&await Promise.race([Promise.allSettled(qe),new Promise(e=>setTimeout(e,5e3))]),console.log(),console.log(chalk.gray(" Saved. See you next session.")),console.log(chalk.gray(" Streak: ")+chalk.yellow(`🔥 ${ue.streakDays} day(s)`)),console.log(),ye){he.removeAllListeners("line"),we();for(const e of pe)he.on("line",e);he.prompt(),Re()}else he.removeAllListeners("line"),he.close();else if(qe.length>0&&await Promise.race([Promise.allSettled(qe),new Promise(e=>setTimeout(e,3e3))]),he.removeAllListeners("line"),we(),ye||!Ne){const{returnToMainMenu:e}=await import("../lib/menu-nav.js");e(ye?he:void 0),Re()}else he.close()}else be()}};he.on("line",e=>{Ge(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.")),be()})}),ye||he.on("close",async()=>{qe.length>0&&await Promise.race([Promise.allSettled(qe),new Promise(e=>setTimeout(e,5e3))]),Ne?Re():process.exit(0)});try{Ke(),be()}catch{if(ne("Could not render this card — the curriculum may be malformed."),ye){he.removeAllListeners("line"),we();for(const e of pe)he.on("line",e);he.prompt()}else he.close();Re()}await De}),re.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 t=0,n=0,r=0;for(const e of o){process.stdout.write(` ${e.padEnd(22)} `);const o=Date.now(),l=await p(e),s=Date.now()-o;if(l){const e=l.cards,o=Array.isArray(e)?e.length:0,n=JSON.stringify(l).length;r+=n,console.log(chalk.green("✓ ")+chalk.gray(`${o.toString().padStart(3)} cards · ${(n/1024).toFixed(1).padStart(6)} KB · ${s.toString().padStart(4)} ms`)),t++}else console.log(chalk.red("✗ failed (server unreachable or unknown id)")),n++}console.log(),console.log(chalk.gray(" Summary: ")+chalk.green.bold(`${t} cached`)+chalk.gray(` · ${n} failed · total ${(r/1024).toFixed(0)} KB`)),console.log(chalk.gray(" Cache: ")+chalk.cyan("~/.icoa/learn-cache/")),console.log()})}
|
|
1
|
+
import chalk from"chalk";import{Interface as e,createInterface as o}from"node:readline";import{execSync as t,spawn as n}from"node:child_process";import{getMainRl as r}from"../lib/main-rl.js";import{ai4ioaiPromptLabel as l,handleAi4ioaiMessage as s,startAi4ioaiChat as a}from"./ai4ioai.js";import{existsSync as i,mkdirSync as c,readFileSync as d,writeFileSync as u}from"node:fs";import{dirname as g,join as m}from"node:path";import{fileURLToPath as y}from"node:url";import{loadCurriculumById as p,validateEAToken as h,syncProgress as f,syncCardFeedback as w}from"../lib/learn-curricula.js";import{normalizeTokenBody as b}from"../lib/token-format.js";import{fetchMyScores as k,streamLearnTutor as $,submitPqs as v,viewPqsTop as C,viewHonors as A}from"../lib/learn-ai.js";import{contestReset as I,contestStart as _,contestSubmit as T,fetchGate as E,timeLeft as S}from"../lib/learn-contest.js";import{getConfig as x,saveConfig as M}from"../lib/config.js";import{loadLearnState as P,saveLearnState as L,newLearnState as q,updateStreak as O,markCardComplete as D,recordMCQ as R,recordCheck as N,markPracticalComplete as j,addAchievement as W}from"../lib/learn-state.js";import{renderWelcome as F,renderKnowledgeCard as B,renderMCQCard as J,renderMCQFeedback as U,renderPracticalCard as z,renderPracticalSuccess as V,renderSimDemoCard as Z,renderMilestone as H,renderArenaTaskCard as K,renderComingSoonCard as Q,renderStatus as Y,renderPhaseComplete as X,renderCurriculumComplete as G,setLearnAiEnabled as ee,setLearnPqsViewCards as oe,renderHonorWall as te}from"../lib/learn-render.js";import{printError as ne}from"../lib/ui.js";export function registerLearnCommand(re){re.command("learn [token]").description("Enter learn mode (free <TRACK>DEMO01 demo, or team-issued token for full curriculum)").action(async re=>{re&&re.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 <your token>")+chalk.gray(" (2 letters + 8 chars) — from your country team leader.")),console.log(),re="LEARNDEMO01");let le=re.trim().toUpperCase(),se=null;const ae=/^[A-Z0-9]+DEMO01$/i.test(le),ie=/^[A-Z]{2}[A-Z0-9]{8}$/i.test(le);let ce=!1,de=!1;if(ae)console.log(),console.log(chalk.gray(" Loading demo curriculum...")),se=await p(le,le);else if(ie){console.log(),console.log(chalk.gray(" Validating token..."));let e=await h(le);if(!e.ok){const o=b(le);if(o!==le){const t=await h(o);t.ok&&(console.log(chalk.yellow(` Corrected look-alike characters: ${le} → ${o}`)),e=t,le=o)}}if(!e.ok)return ne(`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"):"")),ce=!0===e.aiEnabled,de=!0===e.gated,console.log(chalk.gray(" Loading curriculum… (downloads ~1 MB; up to a minute on slow venue Wi-Fi — please wait)")),se=await p(e.curriculumId||"LEARNDEMO01",le),!se)return ne(`Could not open a learn curriculum for token '${le}'.`),console.log(),console.log(chalk.gray(" If this is an ")+chalk.bold.yellow("EXAM token")+chalk.gray(", start it with ")+chalk.bold.green(`exam ${le}`)+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 ${le}`)+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(!se)return ne(`Unknown learn token: ${le}`),console.log(),console.log(chalk.gray(" Available tokens:")),console.log(chalk.gray(" ")+chalk.bold.green("<TRACK>DEMO01")+chalk.gray(" free demo, anyone can use — e.g. ")+chalk.bold.green("LEARNDEMO01")),console.log(chalk.gray(" ")+chalk.bold.yellow("2 letters + 8 chars")+chalk.gray(" full curriculum (issued by team leader)")),console.log(),console.log(chalk.gray(" Every track has a free demo — ask for its name if you were not given one.")),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 ue=P(),ge=!1;try{ue&&ue.token===le?O(ue):(ue=q(le,se.id,se.totalCards),ge=!0),L(ue),F(se,ue,ge)}catch{return ne("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 ${le}`)+chalk.gray(".")),void console.log()}const me=r(),ye=null!==me,pe=ye?me.listeners("line").slice():[];ye&&me.removeAllListeners("line");const he=ye?me:o({input:process.stdin,output:process.stdout,terminal:!0}),fe=ye?he.prompt:null;ye&&(he.prompt=e.prototype.prompt);const we=()=>{ye&&fe&&(he.prompt=fe)},be=()=>{he.setPrompt(_e?chalk.bold.green(`icoa ${l()}> `):chalk.bold.cyan(Ae?"icoa learn ai> ":"icoa learn> ")),he.prompt()};let ke=null,$e=null,ve=null,Ce=null,Ae=!1,Ie=!1,_e=!1;const Te=new Map,Ee=()=>{let e=Te.get(ue.currentCard);return e||(e=[],Te.set(ue.currentCard,e)),e};let Se=!1;const xe=new Set;let Me=!1,Pe=0;const Le=()=>(x().language||"en").toLowerCase().startsWith("zh");ee(ce),oe(xe);const qe=[];let Oe=null;const De=new Promise(e=>{Oe=e}),Re=()=>{const e=Oe;Oe=null,e?.()},Ne="1"===process.env.ICOA_LEARN_EMBEDDED,je=e=>se.cards.find(o=>o.number===e);let We=null,Fe=null;const Be=!le.endsWith("DEMO01")&&/^[A-Z]{2}[A-Z0-9]{8}$/i.test(le),Je=async()=>{if(!Be)return;const e=await E(le);e.ok&&e.data&&(We=e.data)},Ue=e=>{const o=se.modules.find(o=>e>=o.cardRange[0]&&e<=o.cardRange[1]);return o?o.number:1},ze=e=>{if(!de||!We)return null;const o=Ue(e);if(o<=We.unlocked_through_phase)return null;const t=We.phases.find(e=>e.phase===We.unlocked_through_phase),n=[chalk.yellow(Le()?` 🔒 阶段 ${o} 未解锁 — 先完成阶段 ${We.unlocked_through_phase} 的核心任务:`:` 🔒 Phase ${o} is locked — finish phase ${We.unlocked_through_phase}'s core tasks first:`)];return t?.contest&&!t.contest.passed&&n.push(chalk.gray(Le()?` ⚖ 判分赛: 最好 ${t.contest.best_ac}/${t.contest.n} AC,需 ${t.contest.need} — 输入 `:` ⚖ contest: best ${t.contest.best_ac}/${t.contest.n} AC, need ${t.contest.need} — type `)+chalk.bold.green("contest")),t?.research&&!t.research.passed&&n.push(chalk.gray(Le()?` ⭐ 提问研究: ${t.research.good_cards}/${t.research.need_cards} 卡达到 ${t.research.min_score} 分 — 用 /ai 研究,再 /submit 评分`:` ⭐ research: ${t.research.good_cards}/${t.research.need_cards} cards at ${t.research.min_score}+ — research with /ai, then /submit`)),n.join("\n")},Ve=async e=>de?null===ze(e)?null:(await Je(),ze(e)):null;de&&await Je();const Ze=async e=>{const o=se.modules.find(o=>o.cardRange[1]===e);if(!o)return!1;const t=se.modules.find(e=>e.number===o.number+1);let n;if(ce){const e=await k({token:le,cardLo:o.cardRange[0],cardHi:o.cardRange[1]}).catch(()=>({ok:!1,scores:void 0}));n=e.ok?e.scores??[]:void 0}Be&&await Je();const r=We?.phases.find(e=>e.phase===o.number);return X(se,ue,o,t,n,r),!0},He=()=>{Me=!0,console.log(chalk.gray(Le()?" 按 Enter 进入下一阶段…":" Press Enter to continue to the next phase…"))},Ke=()=>{const e=je(ue.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":B(e,se),e.check?(ve=e.number,Pe=Date.now()):(D(ue,e.number),L(ue));break;case"mcq":J(e,se),ke=e.number;break;case"practical":z(e,se),$e=e.number;break;case"sim_demo":Z(e,se),D(ue,e.number),L(ue);break;case"milestone":H(e,se),W(ue,e.badge),D(ue,e.number),L(ue);break;case"arena_task":K(e,se),D(ue,e.number),L(ue);break;case"coming_soon":Q(e,se),D(ue,e.number),L(ue)}},Qe=()=>{le.endsWith("DEMO01")?console.log(chalk.yellow(Le()?" 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(Le()?" 此 token 未开通 AI 导师(高级功能)。":" This token does not include the AI tutor (premium).")),console.log(chalk.gray(Le()?" 找你的领队 / 协调人换开通 AI 的 token。":" Ask your team leader / coordinator for an AI-enabled token."))),console.log()},Ye=async(e,o)=>{if(ce){Ie=!0;try{console.log(),console.log(chalk.gray("deep"===o?Le()?" 🤖 AI 导师(深度思考中,思考过程为灰色小字)…":" 🤖 AI tutor (deepthink — reasoning shown in gray)…":Le()?" 🤖 AI 导师(快速模式)…":" 🤖 AI tutor (quick mode)…")),console.log();let t=!1,n=!1;const r=await $({token:le,cardNumber:ue.currentCard,question:e,mode:o,history:Ee().slice(-8)},e=>{"reasoning"===e.kind?(t||(process.stdout.write(" "),t=!0),process.stdout.write(chalk.dim(e.text.replace(/\n/g,"\n ")))):(n||(process.stdout.write(t?"\n\n ":" "),n=!0),process.stdout.write(chalk.white(e.text.replace(/\n/g,"\n "))))});(t||n)&&console.log(),r.ok?r.offtopic?(console.log(),console.log(chalk.yellow(Le()?" (被判定与本卡主题无关 — 跑题的问题不会被回答,并会拉低本卡的提问评分)":" (judged unrelated to this card — off-topic questions aren't answered and will lower this card's prompt score)"))):(Ee().push({role:"user",content:e},{role:"assistant",content:r.answer}),null!=r.spent&&null!=r.cap&&r.spent/r.cap>.8&&console.log(chalk.gray(` (${Le()?"AI 额度":"AI budget"}: ${Math.round(r.spent/1e3)}k / ${Math.round(r.cap/1e3)}k tokens)`))):(console.log(),console.log(chalk.yellow(` ${r.message||(Le()?"AI 导师暂不可用。":"AI tutor unavailable.")}`))),null!==ve&&console.log(chalk.gray(Le()?" (本卡的判断题还等着你作答 — y 或 n)":" (the y/n check on this card is still waiting — answer y or n)")),console.log()}finally{Ie=!1}}else Qe()},Xe=e=>e.match(/.{1,64}(\s|$)/g)?.map(e=>e.trim())??[e],Ge=async e=>{const o=e.trim().toLowerCase();if(!Ie){if(null!==Ce){const o=e.trim(),t=Ce;if(Ce=null,""===o)console.log(chalk.gray(Le()?" 已取消(未提交)。":" Cancelled (nothing submitted)."));else{const e=Pe>0?Date.now()-Pe:void 0;await w(le,{curriculum_id:se.id,card_number:t,feedback_text:o,time_on_card_ms:e})?console.log(chalk.green(Le()?" ✓ 已收到,谢谢帮助完善这张卡。":" ✓ Thanks — your report was recorded.")):console.log(chalk.yellow(Le()?" ⚠ 未能送达服务器 — 这条报告没有保存,请稍后再按 e 重试。":" ⚠ Could not reach the server — this report was NOT saved. Try e again later."))}return console.log(),void be()}if(Se){Se=!1;const o=e.trim().toLowerCase();return"y"===o||"yes"===o?await(async()=>{Ie=!0;try{console.log(),console.log(chalk.gray(Le()?" 📊 提交中 — Panda 正在评审你的提问(通常几十秒,最长一两分钟,请稍候)…":" 📊 Submitting — Panda is reviewing how you asked (usually well under two minutes)…"));const e=await v({token:le,cardNumber:ue.currentCard});if(console.log(),!e.ok)return console.log(chalk.yellow(` ${e.message||(Le()?"评审暂不可用。":"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(Le()?" 提问评分 ":" Prompt score ")+o(`${e.score??0} / 100`)+chalk.gray(" "+(e=>{switch(e){case"green":return"🟢 "+(Le()?"你把 AI 当作被审视的研究伙伴":"you used the AI as a research partner");case"blue":return"🔵 "+(Le()?"你把 AI 当作实验助手":"you used the AI as a lab assistant");case"yellow":return"🟡 "+(Le()?"你把 AI 当作讲师":"you used the AI as a lecturer");default:return"🔴 "+(Le()?"你把 AI 当作答案机":"you used the AI as an answer machine")}})(e.tier))),e.manipulation&&console.log(chalk.red(Le()?" ⚠ 检测到试图操纵评分,分数已按规则大幅降低":" ⚠ an attempt to manipulate the score was detected — the score was reduced sharply")),null!=e.rank&&null!=e.entrants){const o=!0===e.inTop3,t=Le()?` 本卡排名 #${e.rank} / ${e.entrants}`:` Rank on this card: #${e.rank} of ${e.entrants}`;console.log((o?chalk.bold.green(t):chalk.white(t))+(o?chalk.bold.magenta(Le()?" — 进入 TOP 3!输入 /view 看另两名的提问":" — TOP 3! type /view to read the other top prompts"):""))}if(console.log(chalk.gray(Le()?` 第 ${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 Xe(e.feedback))console.log(chalk.white(" "+o))}e.improve&&(console.log(),console.log(chalk.cyan(Le()?" 改进提问: ":" 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(` (${Le()?"AI 额度":"AI budget"}: ${Math.round(e.spent/1e3)}k / ${Math.round(e.cap/1e3)}k tokens)`)),console.log()})(e),!0===e.inTop3?xe.add(ue.currentCard):xe.delete(ue.currentCard);const o=await A({token:le,curriculumId:se.id,cardNumber:ue.currentCard});o.ok&&o.totals&&o.chapters&&(te({totals:o.totals,chapters:o.chapters,floor:o.floor}),console.log())}finally{Ie=!1}})():(console.log(chalk.gray(Le()?" 已取消(未提交,不消耗次数)。":" Cancelled — nothing submitted, no attempt used.")),console.log()),null!==ve&&(console.log(chalk.gray(Le()?" (本卡的判断题还等着你作答 — y 或 n)":" (the y/n check on this card is still waiting — answer y or n)")),console.log()),void be()}if(Me)return Me=!1,Ke(),void be();if(o){if(_e)return"exit"===await s(e.trim())&&(_e=!1),void be();if(Ae){if(["q","quit","exit","back","b","menu"].includes(o))return Ae=!1,console.log(chalk.gray(Le()?" 已退出 AI 导师,回到卡片。":" Left the AI tutor — back to the card.")),console.log(),void be();const t=e.trim().startsWith("!"),n=t?e.trim().slice(1).trim():e.trim();return n&&await Ye(n,t?"fast":"deep"),void be()}if("tmux"===o||o.startsWith("tmux ")){try{t("command -v tmux",{stdio:"ignore"})}catch{return console.log(chalk.yellow(" tmux is not installed yet.")),console.log(chalk.gray(" Install it with ")+chalk.bold.cyan("darwin"===process.platform?"brew install tmux":"sudo apt install tmux")+chalk.gray(Le()?" — 或 quit 回到 icoa> 后运行 ":" — or quit to icoa> and run ")+chalk.bold.cyan("env setup")+chalk.gray(".")),console.log(),void be()}return await new Promise(o=>{const t=process.stdin,r=!!t.isTTY&&!!t.isRaw;if(he.pause(),t.isTTY&&"function"==typeof t.setRawMode)try{t.setRawMode(!1)}catch{}const l=n(e.trim(),{shell:!0,stdio:"inherit"}),s=()=>{if(t.isTTY&&"function"==typeof t.setRawMode&&r)try{t.setRawMode(!0)}catch{}he.resume(),o()};l.on("close",s),l.on("error",s)}),console.log(),void be()}if("ai4ioai"===o||"/ai4ioai"===o)return await a()&&(_e=!0),void be();if("/ai"===o||"/ai!"===o||o.startsWith("/ai ")||o.startsWith("/ai! ")){if(!ce)return Qe(),void be();const t=o.startsWith("/ai!"),n=e.trim().slice(t?4:3).trim();if(!n){Ae=!0;const e=je(ue.currentCard),o=e?e.title??e.badge??"":"";return console.log(),console.log(chalk.bold.magenta(" 🤖 AI ")+chalk.bold.white(Le()?"AI 导师":"AI tutor")+(o?chalk.gray(Le()?` — 主题「${o}」`:` — topic “${o}”`):"")),console.log(chalk.gray(Le()?" 直接输入问题(默认深度思考) · ! 开头 = 快答 · q = 返回卡片":" Type a question (deepthink by default) · prefix ! = quick answer · q = back to card")),console.log(),void be()}return await Ye(n,t?"fast":"deep"),void be()}if("/submit"===o||o.startsWith("/submit ")||"/research"===o||o.startsWith("/research ")){if(!ce)return Qe(),void be();const e=je(ue.currentCard),o=e?e.title??e.badge??"":"";return console.log(),console.log(chalk.bold.magenta(" 📊 ")+chalk.bold.white(Le()?"Panda 提问评审":"Panda's prompt review")+(o?chalk.gray(Le()?` — 主题「${o}」`:` — topic “${o}”`):"")),console.log(chalk.gray(Le()?" 评的是你在本卡上「怎么问 AI」— 你与 /ai 导师的整段对话会被":" It scores HOW you asked the AI on this card — your whole /ai")),console.log(chalk.gray(Le()?" 整体评审,给出分数、名次和改进建议。":" conversation is reviewed for a score, a rank, and advice.")),console.log(chalk.gray(Le()?" 每张卡最多 2 次评分;评出的最好成绩进本卡 Top-3 榜。":" Max 2 graded attempts per card; your best enters the card’s top-3 board.")),console.log(),console.log(chalk.white(Le()?" 确认提交本卡对话评分?":" Submit this card’s conversation for grading?")+chalk.gray(Le()?" y = 提交 · 其他 = 取消":" y = submit · anything else = cancel")),console.log(),Se=!0,void be()}if("/view"===o)return ce?(await(async()=>{Ie=!0;try{console.log();const e=await C({token:le,cardNumber:ue.currentCard});if(!e.ok)return console.log(chalk.yellow(` ${e.message||(Le()?"暂时无法查看。":"View unavailable right now.")}`)),"not_top3"===e.code&&xe.delete(ue.currentCard),void console.log();null!=e.myRank&&e.myRank<=3&&xe.add(ue.currentCard),console.log(chalk.bold.white(Le()?` 🏆 本卡 Top-3 提问 — 第 ${ue.currentCard} 张 · ${e.entrants??0} 人上榜`:` 🏆 Top-3 prompts — card ${ue.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 t=Xe(e);console.log(chalk.gray(` Q${o+1} `)+chalk.white(t[0]??""));for(const e of t.slice(1))console.log(chalk.white(" "+e))}),0===o.questions.length&&console.log(chalk.gray(Le()?" (无可显示的提问)":" (no questions to show)"))}console.log()}finally{Ie=!1}})(),void be()):(Qe(),void be());if("/honors"===o||"honors"===o)return ae?(console.log(),console.log(chalk.gray(Le()?" 荣誉榜属于正式 learn token — 用团队签发的 token 进来即可查看和参赛。":" The honor board belongs to full learn tokens — join with a team-issued token to view and compete.")),console.log(),void be()):(await(async()=>{Ie=!0;try{console.log();const e=await A({token:le,curriculumId:se.id,cardNumber:ue.currentCard});if(!e.ok||!e.totals||!e.chapters)return console.log(chalk.yellow(` ${e.message||(Le()?"荣誉榜暂时不可用。":"Honor board unavailable right now.")}`)),void console.log();if(console.log(chalk.bold.white(Le()?` 🏆 本卡实时 Top-3 — 第 ${ue.currentCard} 张`:` 🏆 Live top-3 — card ${ue.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(Le()?" (本卡还没有人上榜)":" (no one has medaled on this card yet)"));console.log(),te({totals:e.totals,chapters:e.chapters,floor:e.floor}),console.log()}finally{Ie=!1}})(),void be());if("menu"!==o&&"menu confirm"!==o)if("quit"!==o&&"exit"!==o&&"q"!==o){if("status"===o)return Y(se,ue),void be();if("lang"===o||o.startsWith("lang ")){const e=o.slice(4).trim();if(!e)return console.log(chalk.gray(" 当前语言 / current: ")+chalk.white(x().language||"en")+chalk.gray(" · ")+chalk.bold.green("lang zh")+chalk.gray(" / ")+chalk.bold.green("lang en")),void be();const t=se.cards.some(e=>null!=e._zh),n=e.toLowerCase().startsWith("zh")?"zh":e.toLowerCase(),r="en"===n||"zh"===n&&t,l=r?n:"en";return M({language:l}),r?console.log(chalk.green(" ✓ ")+chalk.gray("语言 / language → ")+chalk.white(l)):console.log(chalk.yellow(" ! ")+chalk.gray(`${e} 本课暂无翻译,已用英文 / not available — using English`)),ve=null,ke=null,$e=null,Ce=null,Me=!1,Se=!1,Ke(),void be()}if("sim"===o){const e=je(ue.currentCard);return e&&"sim_demo"===e.type?(function(e){const o=function(){const e=g(y(import.meta.url)),o=[m(e,"..","..","panda","mujoco-launcher.py"),m(e,"..","..","..","panda","mujoco-launcher.py")];for(const e of o)if(i(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 t={baseline:"baseline",prompt_injected:"prompt_inj",patch_attacked:"patch",modality_confused:"confused"}[e]||"baseline";console.log(chalk.gray(` Launching MuJoCo viewer (scenario: ${t})...`)),console.log(chalk.gray(" Close the window or press ESC to return to learn mode.")),n("python3",[o,t,"--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 be()):(console.log(chalk.gray(" (sim only available on simulation cards)")),void be())}if("bookmark"===o){const e=ue.currentCard;return ue.bookmarks.includes(e)||ue.bookmarks.push(e),L(ue),console.log(chalk.gray(` ✓ Card ${e} bookmarked.`)),void be()}if("e"===o)return Ce=ue.currentCard,console.log(chalk.gray(Le()?" 描述问题(回车提交 / 空行取消):":" Describe the issue (Enter to submit / blank to cancel):")),void be();if("back"===o||"b"===o)return ue.currentCard>1&&(ue.currentCard-=1),ke=null,$e=null,ve=null,L(ue),Ke(),void be();if("contest"===o||"contest start"===o||"contest status"===o||"contest reset"===o){if(!Be)return console.log(chalk.yellow(Le()?" 判分赛需要正式课程 token(demo 不支持)。":" Contests need a full curriculum token (demos not supported).")),void be();const e=Ue(ue.currentCard);if("contest reset"===o){const o=await I(le,e);return o.ok?(console.log(chalk.yellow(Le()?` 已放弃当前计时(阶段 ${e})。输入 contest 重新开赛。`:` Live attempt abandoned (phase ${e}). Type contest to start a fresh one.`)),Fe=null):console.log(chalk.red(` ✗ ${o.message||"reset failed"}`)),void be()}console.log(chalk.gray(Le()?" 连接判分赛…":" Contacting contest server…"));const t=await _(le,e);if(!t.ok||!t.data)return"no_contest"===t.code?console.log(chalk.gray(Le()?` 本阶段(${e})暂无判分赛。`:` No contest configured for phase ${e} yet.`)):console.log(chalk.red(` ✗ ${t.message||"contest unavailable"}`)),void be();const n=t.data,r=m(process.cwd(),"icoa-contest",`${se.id}-phase${e}`);c(r,{recursive:!0}),n.problems.forEach((e,o)=>{const t=[`# ${e.title} (${e.id})`,"",e.statement,""];let n=0;for(const o of e.samples??[])n+=1,t.push(`## Sample ${n}`,"","Input:","```",o.in,"```","Output:","```",o.out,"```","");u(m(r,`problem-${e.id}.md`),t.join("\n"));const l=m(r,`sol-${e.id}.py`);if(!i(l)){const t=(e.samples??[])[0],n=t?`# Sample: input ${JSON.stringify(t.in)} -> output ${JSON.stringify(t.out)}\n`:"";u(l,`# ${e.title}\n# Full statement: problem-${e.id}.md (same folder)\n`+n+"# Read from stdin (input()), print the answer to stdout, no prompts.\n"+`# When ready, back in icoa: submit ${o+1}\n\n`)}}),Fe={phase:e,dir:r,data:n};const l=S(n.deadline);console.log(),console.log(chalk.bold.cyan(` ⚖ ${n.title||(Le()?`阶段 ${e} 判分赛`:`Phase ${e} contest`)}`)+chalk.gray(Le()?` · 第 ${n.attempt} 次尝试${n.resumed?"(续)":""}`:` · attempt ${n.attempt}${n.resumed?" (resumed)":""}`)),console.log(chalk.white(Le()?` ${n.n} 题 · AC ${n.need} 题过关 · 限时 ${n.duration_minutes} 分钟`:` ${n.n} problems · AC ${n.need} to pass · ${n.duration_minutes} min`)+(l?chalk.bold.yellow(Le()?` ⏱ 剩 ${l}`:` ⏱ ${l} left`):chalk.red(Le()?" ⏱ 已超时 — contest reset 重开":" ⏱ time up — contest reset to retry"))),console.log(),n.problems.forEach((e,o)=>{const t=n.solved.includes(e.id);console.log(chalk.gray(` ${o+1}. `)+(t?chalk.green("✓ "):chalk.gray("· "))+chalk.white(e.title)+chalk.gray(` (${e.id})`))}),console.log(),console.log(chalk.gray(Le()?" 题面与答题文件已写入:":" Statements + solution files written to:")),console.log(chalk.bold.white(` ${r}`)),console.log();const s=n.problems.findIndex(e=>!n.solved.includes(e.id))+1||1;return console.log(chalk.white(Le()?" 怎么做题(三步循环):":" How to work (3-step loop):")),console.log(chalk.gray(Le()?" ① 读题 ":" ① Read ")+chalk.cyan(`problem-…q${s}.md`)+chalk.gray(Le()?" (任意编辑器/预览打开)":" (open in any editor/viewer)")),console.log(chalk.gray(Le()?" ② 写解 ":" ② Solve ")+chalk.cyan(`sol-…q${s}.py`)+chalk.gray(Le()?" (VS Code / nano / 记事本都行,存盘)":" (VS Code / nano / any editor, save it)")),console.log(chalk.gray(Le()?" ③ 提交 ":" ③ Submit ")+chalk.bold.green(`submit ${s}`)+chalk.gray(Le()?" ← 回到这里输入,立刻出判分":" ← type here, verdict comes back live")),console.log(),console.log(chalk.gray(Le()?" submit <题号> <文件路径> 可指定别的文件 · contest 随时回看此列表 · contest reset 重开计时":" submit <id> <file> for a custom path · contest re-shows this list · contest reset restarts the clock")),void be()}if("submit"===o)return console.log(chalk.yellow(Le()?" 用法: ":" Usage: ")+chalk.bold.green("submit <题号>")+chalk.gray(Le()?" 例如 submit 1 判第 1 题(默认判 sol-…q1.py;submit 1 <路径> 判指定文件)":" e.g. submit 1 grades problem 1 (defaults to sol-…q1.py; submit 1 <path> for a custom file)")),Fe||console.log(chalk.gray(Le()?" 还没开赛 — 先输入 contest。":" No contest open yet — type contest first.")),void be();if(o.startsWith("submit ")){const e=o.split(/\s+/);let t=e[1]||"";const n=e[2];if(!Fe)return console.log(chalk.yellow(Le()?" 先输入 contest 进入本阶段判分赛。":" Type contest first to open this phase's contest.")),void be();const r=Fe.data.problems,l=parseInt(t,10);if(Number.isInteger(l)&&l>=1&&l<=r.length&&!r.some(e=>e.id===t)&&(t=r[l-1].id),!r.some(e=>e.id===t))return console.log(chalk.yellow(Le()?` 未知题号 ${t} — 输入 contest 查看题目列表。`:` Unknown problem ${t} — type contest to list problems.`)),void be();const s=n||m(Fe.dir,`sol-${t}.py`);let a;try{a=d(s,"utf-8")}catch{return console.log(chalk.red(Le()?` 读不到文件: ${s}`:` Cannot read file: ${s}`)),void be()}console.log(chalk.gray(Le()?" ⚖ 判题中…(隐藏用例,最长约 1-2 分钟)":" ⚖ Judging… (hidden tests, may take a minute)"));const i=await T(le,Fe.phase,t,a);if(!i.ok||!i.data)return"no_active_session"===i.code?console.log(chalk.yellow(Le()?" 没有进行中的计时(可能已超时)— 输入 contest 查看/重开。":" No live timed attempt (time may be up) — type contest.")):console.log(chalk.red(` ✗ ${i.message||"judging failed"}`)),void be();const c=i.data;if(c.already_ac)console.log(chalk.green(Le()?" 该题已 AC(不重复判)。":" Already AC on this problem."));else if("AC"===c.verdict)console.log(chalk.bold.green(" ✓ AC")+chalk.gray(` · ${c.total_tests??"?"} tests · ${c.time_ms??"?"}ms`)),Fe.data.solved.includes(t)||Fe.data.solved.push(t);else{const e="TLE"===c.verdict?chalk.yellow:chalk.red;console.log(e(` ✗ ${c.verdict}`)+chalk.gray(c.test?Le()?` · 挂在第 ${c.test} 个用例(过 ${c.passed_tests}/${c.total_tests})`:` · failed test ${c.test} (passed ${c.passed_tests}/${c.total_tests})`:""))}c.late&&console.log(chalk.yellow(Le()?" ⏱ 超时提交 — 不计入本次成绩。":" ⏱ Submitted past the deadline — not counted."));const u=S(c.deadline||"");if(console.log(chalk.white(Le()?` 进度: ${c.ac_count}/${c.need} 题(共 ${c.n})`:` Progress: ${c.ac_count}/${c.need} AC (of ${c.n})`)+(u?chalk.gray(` · ⏱ ${u}`):"")),c.passed&&!c.late)console.log(chalk.bold.green(Le()?" 🏁 本阶段判分赛已过关!":" 🏁 Contest passed!")),await Je();else if(!c.late){const e=r.findIndex(e=>!Fe.data.solved.includes(e.id));e>=0&&console.log(chalk.gray(Le()?" 下一题: ":" Next: ")+chalk.bold.green(`submit ${e+1}`)+chalk.gray(` ${r[e].title}`))}return void be()}if(o.startsWith("card ")){const e=o.slice(5).trim(),t=parseInt(e,10);if(!Number.isInteger(t)||t<1||t>se.totalCards)return console.log(chalk.yellow(` Card number must be 1..${se.totalCards}. Try: `)+chalk.bold.green("card 1")),void be();const n=await Ve(t);return n?(console.log(n),void be()):(ue.currentCard=t,ke=null,$e=null,ve=null,L(ue),Ke(),void be())}if(null!==ve&&["y","yes","n","no"].includes(o)){const e=je(ve);if(e&&"knowledge"===e.type&&e.check){const t=o.startsWith("y")?"y":"n",n=t===e.check.answer,r=Date.now()-Pe;D(ue,e.number),N(ue,e.number,{answer:t,correct:n,submittedAt:(new Date).toISOString()}),L(ue);const{renderCheckFeedback:l}=await import("../lib/learn-render.js");if(l(e,t,n,!1),qe.push(f(le,{card_number:e.number,event_type:"check_answered",check_answer:t,check_correct:n,time_on_card_ms:r}).catch(()=>{})),ve=null,e.solution&&e.solution.length>0)return void be();if(ue.currentCard<se.totalCards){const e=ue.currentCard,o=await Ve(e+1);if(o)return await Ze(e),console.log(o),void be();ue.currentCard+=1,L(ue),await Ze(e)?He():Ke(),be()}else G(se,ue),be();return}}if(null!==ke&&["a","b","c","d"].includes(o)){const e=je(ke);if(e&&"mcq"===e.type){const t=o.toUpperCase(),n=t===e.answer;if(R(ue,e.number,{answer:t,correct:n,submittedAt:(new Date).toISOString()}),D(ue,e.number),L(ue),U(e,t,n,ue),qe.push(f(le,{card_number:e.number,event_type:"mcq_answered",mcq_answer:t,mcq_correct:n}).catch(()=>{})),ke=null,n)if(ue.currentCard<se.totalCards){const e=await Ve(ue.currentCard+1);if(e)return console.log(e),void be();ue.currentCard+=1,L(ue),Ke()}else G(se,ue),be();else be();return}}if(null!==$e){if("done"===o){const e=je($e);if(e&&"practical"===e.type)return j(ue,e.number),D(ue,e.number),L(ue),V(e),$e=null,void be()}if("skip"===o)return D(ue,$e),L(ue),console.log(chalk.gray(" Skipped (counts as not completed).")),console.log(),$e=null,void be()}if("ok"===o||"next"===o||"continue"===o||"n"===o){if(null!==ke)return console.log(chalk.yellow(" Please answer the MCQ first (A / B / C / D).")),void be();if(null!==$e)return console.log(chalk.yellow(" Please type ")+chalk.bold.green("done")+chalk.yellow(" or ")+chalk.bold.yellow("skip")+chalk.yellow(" for the practical.")),void be();if(null!==ve)return console.log(chalk.yellow(" Please answer the check above (")+chalk.bold.green("y")+chalk.yellow(" or ")+chalk.bold.green("n")+chalk.yellow(").")),void be();const e=ue.currentCard,o=e<se.totalCards?await Ve(e+1):null;return o?(await Ze(e),console.log(o),void be()):(ue.currentCard+=1,L(ue),ue.currentCard>se.totalCards?G(se,ue):await Ze(e)?He():Ke(),void be())}if(null!==ve)return console.log(chalk.yellow(" Please answer the check above (")+chalk.bold.green("y")+chalk.yellow(" or ")+chalk.bold.green("n")+chalk.yellow(").")),void be();if(null!==ke)return console.log(chalk.yellow(" Please answer the MCQ first (A / B / C / D).")),void be();if(null!==$e)return console.log(chalk.yellow(" Please type ")+chalk.bold.green("done")+chalk.yellow(" or ")+chalk.bold.yellow("skip")+chalk.yellow(" for the practical.")),void be();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")),be()}else if(qe.length>0&&await Promise.race([Promise.allSettled(qe),new Promise(e=>setTimeout(e,5e3))]),console.log(),console.log(chalk.gray(" Saved. See you next session.")),console.log(chalk.gray(" Streak: ")+chalk.yellow(`🔥 ${ue.streakDays} day(s)`)),console.log(),ye){he.removeAllListeners("line"),we();for(const e of pe)he.on("line",e);he.prompt(),Re()}else he.removeAllListeners("line"),he.close();else if(qe.length>0&&await Promise.race([Promise.allSettled(qe),new Promise(e=>setTimeout(e,3e3))]),he.removeAllListeners("line"),we(),ye||!Ne){const{returnToMainMenu:e}=await import("../lib/menu-nav.js");e(ye?he:void 0),Re()}else he.close()}else be()}};he.on("line",e=>{Ge(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.")),be()})}),ye||he.on("close",async()=>{qe.length>0&&await Promise.race([Promise.allSettled(qe),new Promise(e=>setTimeout(e,5e3))]),Ne?Re():process.exit(0)});try{Ke(),be();const{setLearnSessionStarted:e}=await import("../lib/learn-outcome.js");e(!0)}catch{if(ne("Could not render this card — the curriculum may be malformed."),ye){he.removeAllListeners("line"),we();for(const e of pe)he.on("line",e);he.prompt()}else he.close();Re()}await De}),re.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 t=0,n=0,r=0;for(const e of o){process.stdout.write(` ${e.padEnd(22)} `);const o=Date.now(),l=await p(e),s=Date.now()-o;if(l){const e=l.cards,o=Array.isArray(e)?e.length:0,n=JSON.stringify(l).length;r+=n,console.log(chalk.green("✓ ")+chalk.gray(`${o.toString().padStart(3)} cards · ${(n/1024).toFixed(1).padStart(6)} KB · ${s.toString().padStart(4)} ms`)),t++}else console.log(chalk.red("✗ failed (server unreachable or unknown id)")),n++}console.log(),console.log(chalk.gray(" Summary: ")+chalk.green.bold(`${t} cached`)+chalk.gray(` · ${n} failed · total ${(r/1024).toFixed(0)} KB`)),console.log(chalk.gray(" Cache: ")+chalk.cyan("~/.icoa/learn-cache/")),console.log()})}
|
package/dist/lib/kaggle.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export function parseKaggleTaskList(e){const t=e??{},r=(t.data??t).tasks;if(!Array.isArray(r))return[];const i=[];for(const e of r){if(!e||"object"!=typeof e)continue;const t=e;"string"==typeof t.comp_id&&""!==t.comp_id&&i.push({comp_id:t.comp_id,title:"string"==typeof t.title?t.title:t.comp_id,metric:"string"==typeof t.metric?t.metric:"accuracy",description:"string"==typeof t.description&&""!==t.description?t.description:void 0,kaggle_url:"string"==typeof t.kaggle_url?t.kaggle_url:null,licence:"string"==typeof t.licence?t.licence:void 0,attribution:"string"==typeof t.attribution?t.attribution:void 0,data_base:"string"==typeof t.data_base&&""!==t.data_base?t.data_base:null,id_col:"string"==typeof t.id_col?t.id_col:void 0,target_col:"string"==typeof t.target_col?t.target_col:void 0,n_train:"number"==typeof t.n_train?t.n_train:void 0,n_test:"number"==typeof t.n_test?t.n_test:void 0,judge:"string"==typeof t.judge&&""!==t.judge?t.judge:void 0,board_event:"string"==typeof t.board_event&&""!==t.board_event?t.board_event:null,needs_gpu:!0===t.needs_gpu})}return i}export function dataFileUrls(e){const t=e.data_base;if("string"!=typeof t||""===t)return null;const r=t.replace(/\/$/,"");return{train:`${r}/train.csv`,test:`${r}/test.csv`,sample_submission:`${r}/sample_submission.csv`}}export function buildStarterCells(e,t){const r=t.id_col??"id",i=t.target_col??"target",a=(o=t.metric,/rmse|mse|mae|error/i.test(o));var o;const s="icoa-holdout"===t.judge,n=e=>({kind:"markdown",source:e,result:null}),l=e=>({kind:"code",source:e.join("\n"),result:null}),c=[`# ${t.title}`,"",...t.description?[t.description,""]:[],`Kaggle-style practice — metric: **${t.metric}**.`,"","- `train.csv` — labelled training data",`- \`test.csv\` — rows to predict (no \`${i}\` column)`,"- `sample_submission.csv` — the exact format Kaggle expects","","Run all cells top to bottom: a baseline model trains on your CPU and","`submission.csv` appears next to the data. Then improve the model cell",...s?["and re-run — `icoa kaggle submit` scores it and ranks you on the board."]:["and re-run — your score is whatever Kaggle says it is."],"","**AI help:** split your terminal (`Ctrl-b %`), run `icoa` in the right pane","and type `ai4ioai` for a live Gemma
|
|
1
|
+
export function parseKaggleTaskList(e){const t=e??{},r=(t.data??t).tasks;if(!Array.isArray(r))return[];const i=[];for(const e of r){if(!e||"object"!=typeof e)continue;const t=e;"string"==typeof t.comp_id&&""!==t.comp_id&&i.push({comp_id:t.comp_id,title:"string"==typeof t.title?t.title:t.comp_id,metric:"string"==typeof t.metric?t.metric:"accuracy",description:"string"==typeof t.description&&""!==t.description?t.description:void 0,kaggle_url:"string"==typeof t.kaggle_url?t.kaggle_url:null,licence:"string"==typeof t.licence?t.licence:void 0,attribution:"string"==typeof t.attribution?t.attribution:void 0,data_base:"string"==typeof t.data_base&&""!==t.data_base?t.data_base:null,id_col:"string"==typeof t.id_col?t.id_col:void 0,target_col:"string"==typeof t.target_col?t.target_col:void 0,n_train:"number"==typeof t.n_train?t.n_train:void 0,n_test:"number"==typeof t.n_test?t.n_test:void 0,judge:"string"==typeof t.judge&&""!==t.judge?t.judge:void 0,board_event:"string"==typeof t.board_event&&""!==t.board_event?t.board_event:null,needs_gpu:!0===t.needs_gpu})}return i}export function dataFileUrls(e){const t=e.data_base;if("string"!=typeof t||""===t)return null;const r=t.replace(/\/$/,"");return{train:`${r}/train.csv`,test:`${r}/test.csv`,sample_submission:`${r}/sample_submission.csv`}}export function buildStarterCells(e,t){const r=t.id_col??"id",i=t.target_col??"target",a=(o=t.metric,/rmse|mse|mae|error/i.test(o));var o;const s="icoa-holdout"===t.judge,n=e=>({kind:"markdown",source:e,result:null}),l=e=>({kind:"code",source:e.join("\n"),result:null}),c=[`# ${t.title}`,"",...t.description?[t.description,""]:[],`Kaggle-style practice — metric: **${t.metric}**.`,"","- `train.csv` — labelled training data",`- \`test.csv\` — rows to predict (no \`${i}\` column)`,"- `sample_submission.csv` — the exact format Kaggle expects","","Run all cells top to bottom: a baseline model trains on your CPU and","`submission.csv` appears next to the data. Then improve the model cell",...s?["and re-run — `icoa kaggle submit` scores it and ranks you on the board."]:["and re-run — your score is whatever Kaggle says it is."],"","**AI help:** split your terminal (`Ctrl-b %`), run `icoa` in the right pane","and type `ai4ioai` for a live Gemma 3n E4B. Ask it for feature ideas or bug fixes —","then verify every suggestion with the val score. A tip that lowers it, drop it.",...t.licence?["",`**Data licence:** ${t.licence}`]:[],...t.attribution?[`**Attribution:** ${t.attribution}`]:[]].join("\n"),d=l(["# setup — load the data (leave as is)","import os","import pandas as pd","from pandas.api.types import is_numeric_dtype","from sklearn.model_selection import train_test_split",a?"from sklearn.metrics import mean_squared_error":"from sklearn.metrics import accuracy_score","","# same code everywhere: in a GPU session these env vars point at the","# box's own dataset copy + workspace — locally they are unset.",`DIR = os.environ.get("ICOA_KAGGLE_DATA") or r"${e}"`,'OUT = os.environ.get("ICOA_KAGGLE_OUT") or DIR',...t.data_base?[`DATA_BASE = "${t.data_base.replace(/\/$/,"")}"`,'if not os.path.exists(DIR + "/train.csv"):'," import urllib.request"," os.makedirs(DIR, exist_ok=True)",' for f in ("train.csv", "test.csv", "sample_submission.csv"):',' urllib.request.urlretrieve(DATA_BASE + "/" + f, DIR + "/" + f)']:[],'train = pd.read_csv(DIR + "/train.csv")','test = pd.read_csv(DIR + "/test.csv")',`ID, TARGET = "${r}", "${i}"`,'print(f"train {train.shape} · test {test.shape}")']),m=l(["# FEATURES — turn raw columns into numbers a model can use (improve THIS cell).","# Numbers: fill gaps with the median. Text/category: one-hot the low-cardinality","# ones (<= 20 distinct); drop high-cardinality text like names/IDs (rarely helps","# a baseline). Train and test end up aligned to the exact same columns.","feat = [c for c in train.columns if c not in (ID, TARGET)]","Xtr_raw, Xte_raw = train[feat].copy(), test[feat].copy()","num = [c for c in feat if is_numeric_dtype(Xtr_raw[c])]","cat = [c for c in feat if c not in num and Xtr_raw[c].nunique() <= 20]","for c in num:"," med = Xtr_raw[c].median()"," Xtr_raw[c] = Xtr_raw[c].fillna(med)"," Xte_raw[c] = Xte_raw[c].fillna(med)","X = pd.get_dummies(Xtr_raw[num + cat], columns=cat, dummy_na=True)","X_test = pd.get_dummies(Xte_raw[num + cat], columns=cat, dummy_na=True)",'X, X_test = X.align(X_test, join="left", axis=1, fill_value=0)',"y = train[TARGET]","X_tr, X_val, y_tr, y_val = train_test_split(X, y, test_size=0.2, random_state=0)",'print(f"{X.shape[1]} model-ready features "',' f"(dropped {len(feat) - len(num) - len(cat)} high-cardinality/text cols)")']),u=l(a?["# MODEL — a working baseline. Improve THIS cell to climb the Kaggle board.","from sklearn.linear_model import Ridge","model = Ridge()"]:["# MODEL — a working baseline. Improve THIS cell to climb the Kaggle board.","from sklearn.linear_model import LogisticRegression","model = LogisticRegression(max_iter=2000)"]),p=l(["# validate — quick local score on a held-out fifth of train","model.fit(X_tr, y_tr)",a?'print("val RMSE:", round(mean_squared_error(y_val, model.predict(X_val)) ** 0.5, 4))':'print("val accuracy:", round(accuracy_score(y_val, model.predict(X_val)), 4))']),g=l(["# submission — retrain on ALL data, write submission.csv (Kaggle format)","model.fit(X, y)","pred = model.predict(X_test)",'out = OUT + "/submission.csv"',`pd.DataFrame({"${r}": test["${r}"], "${i}": pred}).to_csv(out, index=False)`,'print("wrote", out)',s?`print("score it: icoa kaggle submit ${t.comp_id}")`:'print("format check: icoa kaggle check")']),_=s?["## Submit to ICOA (scored on the server)","",`1. In the ICOA CLI run \`icoa kaggle submit ${t.comp_id}\``,"2. The server scores `submission.csv` against the secret test labels"," and your rank lands on the live board (`icoa board`).","3. Improve the MODEL cell, re-run, submit again — best score counts."]:t.kaggle_url?["## Submit on Kaggle (manual, your own account)","",`1. Open ${t.kaggle_url}`,"2. **Submit Predictions** → upload `submission.csv`","3. Read your leaderboard score, come back, improve the MODEL cell, repeat."]:["## Submit on Kaggle (manual, your own account)","","_This is a placeholder practice dataset — it has no live Kaggle page yet._","When the real competition dataset lands, this cell will link straight to","its Kaggle submission page. The loop is identical: upload `submission.csv`,","read the score, improve the MODEL cell, repeat."];return[n(c),d,m,u,p,g,n(_.join("\n"))]}export function parseSubmitResponse(e){const t=e&&"object"==typeof e?e:{},r=t.data??t,i=r&&"object"==typeof r?r:{};return"number"==typeof i.score&&"string"==typeof i.metric?{ok:!0,score:i.score,metric:i.metric,rank:"number"==typeof i.rank_at_time?i.rank_at_time:null,fieldSize:"number"==typeof i.field_size?i.field_size:null,deduped:!0===i.deduped,boardEvent:"string"==typeof i.board_event?i.board_event:null}:"string"==typeof i.reason&&""!==i.reason?{ok:!1,reason:i.reason,error:"string"==typeof i.error?i.error:void 0,problems:Array.isArray(i.problems)?i.problems.filter(e=>"string"==typeof e):void 0}:{ok:!1,reason:"network"}}export function validateSubmissionText(e,t){const r=[],i=e.trim().split(/\r?\n/),a=t.trim().split(/\r?\n/),o=i[0]??"",s=a[0]??"";o.trim()!==s.trim()&&r.push(`header mismatch: expected "${s}", got "${o}"`);const n=i.slice(1).filter(e=>""!==e.trim()),l=a.slice(1).filter(e=>""!==e.trim());n.length!==l.length&&r.push(`row count mismatch: expected ${l.length}, got ${n.length}`);const c=e=>new Set(e.map(e=>(e.split(",")[0]??"").trim())),d=c(n),m=c(l),u=[...m].filter(e=>!d.has(e)),p=[...d].filter(e=>!m.has(e));if(u.length>0||p.length>0){const e=[];u.length>0&&e.push(`${u.length} missing (e.g. ${u[0]})`),p.length>0&&e.push(`${p.length} unknown (e.g. ${p[0]})`),r.push(`id mismatch vs sample_submission: ${e.join(", ")}`)}for(let e=0;e<n.length;e++)if(n[e].split(",").some(e=>""===e.trim())){r.push(`empty cell in data row ${e+1}`);break}return{ok:0===r.length,problems:r}}
|
|
@@ -9,6 +9,8 @@ export type ContestProblem = {
|
|
|
9
9
|
};
|
|
10
10
|
export type ContestStartData = {
|
|
11
11
|
phase: number;
|
|
12
|
+
slot: number;
|
|
13
|
+
group_count: number;
|
|
12
14
|
title?: string;
|
|
13
15
|
attempt: number;
|
|
14
16
|
resumed: boolean;
|
|
@@ -20,6 +22,17 @@ export type ContestStartData = {
|
|
|
20
22
|
solved: string[];
|
|
21
23
|
problems: ContestProblem[];
|
|
22
24
|
};
|
|
25
|
+
export type SlotState = {
|
|
26
|
+
slot: number;
|
|
27
|
+
title?: string;
|
|
28
|
+
passed: boolean;
|
|
29
|
+
best_ac: number;
|
|
30
|
+
need: number;
|
|
31
|
+
n: number;
|
|
32
|
+
attempts: number;
|
|
33
|
+
duration_minutes: number;
|
|
34
|
+
active_deadline: string | null;
|
|
35
|
+
};
|
|
23
36
|
export type ContestSubmitData = {
|
|
24
37
|
verdict: string;
|
|
25
38
|
late?: boolean;
|
|
@@ -63,9 +76,9 @@ type ApiResult<T> = {
|
|
|
63
76
|
code?: string;
|
|
64
77
|
message?: string;
|
|
65
78
|
};
|
|
66
|
-
export declare function contestStart(token: string, phase: number): Promise<ApiResult<ContestStartData>>;
|
|
67
|
-
export declare function contestSubmit(token: string, phase: number, problemId: string, code: string): Promise<ApiResult<ContestSubmitData>>;
|
|
68
|
-
export declare function contestReset(token: string, phase: number): Promise<ApiResult<{
|
|
79
|
+
export declare function contestStart(token: string, phase: number, slot?: number): Promise<ApiResult<ContestStartData>>;
|
|
80
|
+
export declare function contestSubmit(token: string, phase: number, problemId: string, code: string, slot?: number): Promise<ApiResult<ContestSubmitData>>;
|
|
81
|
+
export declare function contestReset(token: string, phase: number, slot?: number): Promise<ApiResult<{
|
|
69
82
|
reset_sessions: number;
|
|
70
83
|
}>>;
|
|
71
84
|
export declare function fetchGate(token: string): Promise<ApiResult<GateData>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getDeviceFingerprint as t}from"./access.js";import{LEARN_SERVER as e}from"./learn-curricula.js";async function o(o,r,
|
|
1
|
+
import{getDeviceFingerprint as t}from"./access.js";import{LEARN_SERVER as e}from"./learn-curricula.js";async function o(o,r,n){const a=e.replace(/\/$/,"")+o;try{const e=await fetch(a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({...r,device_fingerprint:t()}),signal:AbortSignal.timeout(n)}),o=await e.json().catch(()=>({}));return e.ok&&o.success&&void 0!==o.data?{ok:!0,data:o.data}:{ok:!1,message:o.message||`HTTP ${e.status}`,code:o.code}}catch(t){return{ok:!1,message:`Network error: ${t instanceof Error?t.message:String(t)}`}}}export function contestStart(t,e,r){return o("/api/icoa/learn/contest/start",{token:t.toUpperCase(),phase:e,...null!=r?{slot:r}:{}},15e3)}export function contestSubmit(t,e,r,n,a){return o("/api/icoa/learn/contest/submit",{token:t.toUpperCase(),phase:e,problem_id:r,code:n,...null!=a?{slot:a}:{}},12e4)}export function contestReset(t,e,r){return o("/api/icoa/learn/contest/reset",{token:t.toUpperCase(),phase:e,...null!=r?{slot:r}:{}},15e3)}export function fetchGate(t){return o("/api/icoa/learn/gate",{token:t.toUpperCase()},15e3)}export function timeLeft(t){const e=Date.parse(t);if(Number.isNaN(e))return null;const o=e-Date.now();if(o<=0)return null;const r=Math.floor(o/6e4),n=Math.floor(o%6e4/1e3);return`${r}:${String(n).padStart(2,"0")}`}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let e=!1;export function setLearnSessionStarted(t){e=t}export function learnSessionStarted(){return e}
|
package/dist/lib/learn-render.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import chalk from"chalk";import o from"string-width";import{localized as e}from"./learn-curricula.js";import{t as n}from"./learn-i18n.js";import{getConfig as l}from"./config.js";import{icoaBannerLines as t}from"./banner.js";import{renderCharts as r,detectCaps as s}from"./render-card.js";function a(){return(l().language||"en").toLowerCase()}const c=64;function g(o="─"){return o.repeat(66)}function i(o,e,n=20){const l=e>0?o/e:0,t=Math.floor(l*n),r=n-t;return`${chalk.green("█".repeat(t))+chalk.gray("░".repeat(r))} ${(100*l).toFixed(1)}%`}function d(o){return o>=30?chalk.red("🔥🔥🔥 "+o+" days"):o>=7?chalk.yellow("🔥 "+o+" days"):o>=1?chalk.gray("· "+o+" day"+(o>1?"s":"")):chalk.gray("—")}export function renderWelcome(o,e,l){const t=a(),r=e.cardsCompleted.length,s=o.totalCards,c=s-r,y=o.modules.find(o=>e.currentCard>=o.cardRange[0]&&e.currentCard<=o.cardRange[1]),u=/demo/i.test(o.id);if(console.log(),console.log(chalk.cyan(" ╭"+g("═"))),console.log(chalk.cyan(" ║")),console.log(chalk.cyan(" ║ ")+chalk.bold.white(" "+(o.name?.trim()||n("academy_title",t)))),console.log(chalk.cyan(" ║")),l)console.log(chalk.cyan(" ║ ")+chalk.white(" "+n(u?"welcome_new_demo":"welcome_new",t)));else{const o=new Date(e.lastSeenAt),l=Math.floor((Date.now()-o.getTime())/36e5),r=l<1?n("just_now",t):l<24?`${l}${n("ago_hours",t)}`:`${Math.floor(l/24)}${n("ago_days",t)}`;console.log(chalk.cyan(" ║ ")+chalk.white(` ${n("welcome_back",t)} ${r}`))}console.log(chalk.cyan(" ║"));const h=y?`${y.number}. ${y.name}`:"—";console.log(chalk.cyan(" ║ ")+" "+chalk.gray(n("module",t).padEnd(11))+chalk.white(h)),console.log(chalk.cyan(" ║ ")+" "+chalk.gray(n("progress",t).padEnd(11))+i(r,s)+chalk.gray(` (${r}/${s})`)),console.log(chalk.cyan(" ║ ")+" "+chalk.gray(n("streak",t).padEnd(11))+d(e.streakDays)),c>0&&e.currentCard<=s&&console.log(chalk.cyan(" ║ ")+" "+chalk.gray(n("next_card",t).padEnd(11))+chalk.white(`#${e.currentCard} ${n("of",t)} ${s}`)),console.log(chalk.cyan(" ║")),console.log(chalk.cyan(" ╰"+g("═"))),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────"));const m=0===r?n("continue_start",t):`${n("continue_resume",t)} ${e.currentCard}`;console.log(chalk.bold.green(" continue")+chalk.gray(" "+m)),console.log(chalk.yellow(" status")+chalk.gray(" "+n("status_full",t))),e.bookmarks.length>0&&console.log(chalk.yellow(" bookmarks")+chalk.gray(` ${e.bookmarks.length} ${n("bookmarks_desc",t)}`)),console.log(chalk.gray(" quit")+chalk.gray(" "+n("quit_desc",t))),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log()}function y(o,e){const l=a(),t=o.module,r=e.modules.find(o=>o.number===t),s=r?r.name:n("unknown_module",l),g=n("module",l).replace(":","").trim(),i=`${n("card",l)} ${o.number} / ${e.totalCards} · ${g} ${t} · ${s}`;console.log(),console.log(chalk.cyan(" ╭─ ")+chalk.bold.white(i)+" "+chalk.cyan("─".repeat(Math.max(0,c-i.length-4))))}function u(){console.log(chalk.cyan(" ╰"+g())),console.log()}function h(o){console.log(chalk.cyan(" │ ")+chalk.white(o))}function m(){console.log(chalk.cyan(" │"))}const b=/[┌┐└┘├┤┬┴┼─│╔╗╚╝═║╠╣╦╩╬▓▒░█]/;function f(o){return/^(?: {4}|\t)/.test(o)||b.test(o)||/ {3,}/.test(o)}function p(e,n){const l=[];let t="",r=0,s=!1;for(const a of function(e){const n=[],l=e=>{let l="";const t=()=>{l&&(n.push({t:"atom",s:l}),l="")};for(const r of e)" "===r?(t(),n.push({t:"space"})):2===o(r)?(t(),n.push({t:"wide",s:r})):l+=r;t()},t=/`[^`]*`/g;let r,s=0;for(;null!==(r=t.exec(e));)l(e.slice(s,r.index)),n.push({t:"code",s:r[0]}),s=r.index+r[0].length;return l(e.slice(s)),n}(e)){if("space"===a.t){""!==t&&(s=!0);continue}const e=a.s,c=o("code"===a.t?e.replace(/`/g,""):e),g=s?1:0,i="atom"===a.t&&e.length<=2&&/^[.,;:!?)\]}…'"]+$/.test(e);""!==t&&!i&&r+g+c>n?(l.push(t),t=e,r=c,s=!1):(s&&(t+=" ",r+=1,s=!1),t+=e,r+=c)}return t&&l.push(t),l}const $=/\b(?:Common bug|Common mistake|Common error|Pitfall|Gotcha|Footgun)\b[::]?|\b(?:Warning|Caution)\b[::]|(?:常见错误|常见陷阱|常见误区|陷阱|注意|警告|误区)[::]/gi;function w(o,e){const n=e?chalk.bold.white:chalk.white;let l,t="",r=0;for($.lastIndex=0;null!==(l=$.exec(o));)l.index>r&&(t+=n(o.slice(r,l.index))),t+=chalk.bold.yellow(l[0]),r=l.index+l[0].length;return r<o.length&&(t+=n(o.slice(r))),t}function _(o,e=!1){let n="",l=0;const t=/`([^`]*)`/g;let r;for(;null!==(r=t.exec(o));)r.index>l&&(n+=w(o.slice(l,r.index),e)),n+=chalk.cyan(r[1]),l=r.index+r[0].length;return l<o.length&&(n+=w(o.slice(l),e)),n}function k(o,e=!1){let n=!1;for(const l of o.split("\n"))if(/^\s*```/.test(l))n=!n;else if(n)console.log(chalk.cyan(" │ ")+chalk.cyan(` ${l}`));else if(""!==l)if(f(l))console.log(chalk.cyan(" │ ")+chalk.white(l));else for(const o of p(l,60))console.log(chalk.cyan(" │ ")+` ${_(o,e)}`);else m()}function C(o,e){const n=[];for(const l of o.split("\n")){if(""===l){n.push("");continue}if(f(l)){n.push(l);continue}let o="";for(const t of l.split(" "))(o+" "+t).trim().length>e?(n.push(o.trim()),o=t):o=(o+" "+t).trim();o&&n.push(o)}return n}let x=!1;export function setLearnAiEnabled(o){x=o}let v=new Set;export function setLearnPqsViewCards(o){v=o}export function renderHonorWall(o){const e=a().startsWith("zh"),{totals:n,chapters:l}=o,t=n.gold+n.silver+n.bronze,r=(o,e)=>e>0?chalk.bold.white(`${o}×${e}`):chalk.gray(`${o}×0`);if(console.log(chalk.gray(" ─────────────────────────────────────────")),console.log(chalk.bold.white(e?" 🏅 奖牌墙 ":" 🏅 Medal wall ")+r("🥇",n.gold)+chalk.gray(" · ")+r("🥈",n.silver)+chalk.gray(" · ")+r("🥉",n.bronze)+chalk.gray(e?" (永久 — 挂上墙就不会摘下)":" (permanent — once earned, never removed)")),0===t){const n=null!=o.floor?`${o.floor}+`:e?"高分":"a high score";return console.log(chalk.gray(e?` 还没有奖牌 — 单卡提问评分 ${n} 并进入该卡前三,即可永久挂墙 🥇🥈🥉`:` No medals yet — score ${n} on a card and hold its top-3 to hang one 🥇🥈🥉`)),void console.log(chalk.gray(" ─────────────────────────────────────────"))}const s=l.map(o=>{const e=`Ch${o.chapter}`,n=(o.gold>0?`🥇${o.gold}`:"")+(o.silver>0?`🥈${o.silver}`:"")+(o.bronze>0?`🥉${o.bronze}`:"");return n?chalk.white(`${e} ${n}`):chalk.gray(`${e} —`)}).join(chalk.gray(" "));console.log(" "+s);const c=l.flatMap(o=>o.cards.map(e=>({...e,chapter:o.chapter})));c.sort((o,e)=>o.rank-e.rank||o.chapter-e.chapter||o.card-e.card);for(const o of c.slice(0,24)){const n=(o.since||"").slice(0,10);console.log(chalk.white(` ${o.icon} `)+chalk.gray(`Ch${o.chapter} · `)+chalk.white(e?`第 ${o.card} 张`:`card ${o.card}`)+chalk.gray(` · ${o.score}`)+(n?chalk.gray(e?` · 始于 ${n}`:` · since ${n}`):""))}c.length>24&&console.log(chalk.gray(e?` …还有 ${c.length-24} 枚`:` …and ${c.length-24} more`)),console.log(chalk.gray(" ─────────────────────────────────────────"))}export function renderKnowledgeCard(l,t){const g=a(),i=e(l,g);y(i,t),m();const d=p(i.title,60);for(const o of d)h(chalk.bold.yellow(o));h(chalk.gray("─".repeat(Math.min(Math.max(...d.map(e=>o(e))),c))));const b=!!(l.chart_ids&&l.chart_ids.length>0);if(b&&r(l.chart_ids.slice(0,1),t.charts,s()),i.key_point&&""!==i.key_point.trim()?(b||m(),function(o,e,n){const l=a().startsWith("zh"),t=s().cols,r="-".repeat(Math.min(c,Math.max(24,t-6))),g=l?"要点":"KEY POINT",i=l?`第 ${e} / ${n} 张`:`Card ${e} / ${n}`;console.log(chalk.cyan(" │ ")+chalk.green(r)),console.log(chalk.cyan(" │ ")+chalk.green(`${g} · ${i}`));for(const e of p(o,60))console.log(chalk.cyan(" │ ")+chalk.bold.green(" "+e));console.log(chalk.cyan(" │ ")+chalk.green(r)),m()}(i.key_point,i.number,t.totalCards)):b||m(),i.body.filter(o=>null!=o&&""!==o.trim()).forEach((o,e)=>{const n=0===e?o.match(/^([\s\S]*?(?:[.!?](?=\s|$)|[。!?]))(\s[\s\S]*)?$/):null;if(n&&n[1].trim().length<=180){k(n[1],!0);const o=n[2]?.trimStart();o&&k(o,!1)}else k(o,!1);m()}),i.icoaConnection){h(chalk.magenta(" "+n("icoa_connection",g))),h(chalk.gray(" "+"─".repeat(20)));for(const o of p(i.icoaConnection,60))h(" "+chalk.magenta(o));m()}if(l.check){const o=g.startsWith("zh"),e=l._zh?.checkStatement??l._zh?.check?.statement,n=o?e??l.check.statement:l.check.statement;h(chalk.cyan(" 🤔 "+(o?"请回答 (输 y 或 n):":"Quick comprehension check (type y or n):"))),h(chalk.gray(" "+"─".repeat(20)));for(const o of p(n,60))h(" "+chalk.white(o));m()}u(),function(o,e){const n=a().startsWith("zh"),l=s().cols,t="-".repeat(Math.min(c,Math.max(24,l-6))),r=chalk.gray(" "),g=[];o.check?(g.push(chalk.bold.green("[y]")+chalk.gray(n?" 是":" yes")),g.push(chalk.bold.green("[n]")+chalk.gray(n?" 否":" no"))):g.push(chalk.bold.green("[ok]")+chalk.gray(n?" 继续":" next")),x&&(g.push(chalk.bold.magenta("[/ai]")+chalk.gray(n?" AI导师":" AI tutor")),g.push(chalk.bold.magenta("[/submit]")+chalk.gray(n?" 提问评分":" prompt score")),v.has(o.number)&&g.push(chalk.bold.magenta("[/view]")+chalk.gray(n?" 榜单":" top-3"))),g.push(chalk.bold.yellow("[e]")+chalk.gray(n?" 报告问题":" report")),g.push(chalk.bold.cyan("[b]")+chalk.gray(n?" 上一张":" back")),g.push(chalk.bold.cyan("[card N]")+chalk.gray(n?" 跳转":" jump")),g.push(chalk.gray("[quit]")+chalk.gray(n?" 退出":" quit")),console.log(chalk.gray(" "+t)),console.log(chalk.gray(" "+(n?`第 ${o.number} / ${e.totalCards} 张`:`Card ${o.number} / ${e.totalCards}`))),console.log(" "+g.join(r)),console.log(chalk.gray(" "+t)),console.log(chalk.gray(" "+(n?"e = 报告本卡错误,不算作答;提交后回到本题继续":"e = report a problem with this card (not an answer); you return here after"))),console.log(chalk.gray(" "+(n?"更多: bookmark · status · /honors 奖牌墙 · lang zh/en":"more: bookmark · status · /honors · lang zh/en"))),console.log()}(l,t)}export function renderCheckFeedback(o,e,n,l=!1){const t=a().startsWith("zh");if(console.log(),n)console.log(chalk.green(" ✓ ")+chalk.white(t?"答对了。":"Correct."));else if(console.log(chalk.yellow(" ✗ ")+chalk.white(t?"记下了,继续。":"Noted — moving on.")),l){const e="y"===o.check?.answer?t?"是":"Yes":t?"否":"No";console.log(chalk.gray(" "+(t?"正确答案: ":"Answer: ")+e+". "+(t?"回看上面那段。":"Re-read the paragraph above.")))}const r=t?o._zh?.solution??o.solution:o.solution;if(r&&r.length>0){console.log(),console.log(chalk.cyan(" 📝 "+(t?"解答:":"Worked solution:")));for(const o of r){for(const e of p(o,60))console.log(chalk.white(" "+e));console.log()}}console.log()}function z(o){return[chalk.white(" "+(o?"编著:Charlie Zhu":"Created & edited by Charlie Zhu")),chalk.gray(" "+(o?"ICOA 2026 创始人 · 课程总架构师":"Founder & Chief Curriculum Architect · ICOA 2026"))]}export function renderPhaseComplete(o,e,n,l,r,s){const c=a().startsWith("zh"),[g,i]=n.cardRange,d=e.checkResults||{};let y=0,u=0;for(let o=g;o<=i;o++){const e=d[String(o)];e&&(y++,e.correct&&u++)}const h="-".repeat(48);console.log();for(const o of t(" "))console.log(o);console.log(),console.log(chalk.bold.yellow(" "+h)),console.log(chalk.bold.yellow(" "+(c?`阶段 ${n.number} 完成`:`Phase ${n.number} complete`))+chalk.gray(" · ")+chalk.white(n.name));const m=y>0&&u===y?chalk.bold.green:chalk.bold.white;console.log(chalk.gray(" "+(c?"理解检查得分: ":"comprehension score: "))+m(`${u}/${y}`)+chalk.gray(0===y?c?" (本阶段未作答 y/n)":" (no y/n answered)":""));const b=String(i).length;for(let e=g;e<=i;e++){const n=o.cards.find(o=>o.number===e);if(!n)continue;const l=n,t=l.title??l.badge??"",r=(c?l._zh?.title??l._zh?.badge??t:t)||"",s=null!=n.check,a=d[String(e)];let g;g=a?a.correct?chalk.green("✓"):chalk.red("✗"):s?chalk.gray("○"):chalk.gray("·");const i=p(r,54-b),y=i[0]+(i.length>1?"…":"");console.log(chalk.gray(` ${String(e).padStart(b)} `)+g+chalk.white(" "+y))}if(console.log(chalk.gray(c?" ✓ 答对 · ✗ 答错 · ○ 未作答 · 无 y/n 的卡不计分":" ✓ correct · ✗ wrong · ○ unanswered · cards without a y/n are unscored")),r){const o=o=>"green"===o?chalk.bold.green:"blue"===o?chalk.bold.cyan:"yellow"===o?chalk.bold.yellow:chalk.bold.red;console.log(chalk.bold.magenta(" ⭐ "+(c?"提问得分 (best): ":"Prompt scores (best): "))+(0===r.length?chalk.gray(c?"本阶段尚未提问 — 用 /ai 研究,再 /submit 评分":"none this phase — research with /ai, then /submit"):r.map(e=>chalk.gray((c?"卡":"#")+e.card+" ")+o(e.tier)(String(e.score))).join(chalk.gray(" · "))))}if(s?.configured&&s.contest){const o=s.contest,e=chalk.bold.cyan(" ⚖ "+(c?"判分赛: ":"Contest: "));o.passed?console.log(e+chalk.bold.green(`${o.best_ac}/${o.n} AC ✅ `+(c?"已过关":"passed"))):0===o.attempts?console.log(e+chalk.white(c?`${o.n} 题 · 过 ${o.need} 题解锁 — 输入 `:`${o.n} problems · AC ${o.need} to pass — type `)+chalk.bold.green("contest")+chalk.white(c?" 开始":" to start")):console.log(e+chalk.yellow(c?`最好 ${o.best_ac}/${o.n} AC(需 ${o.need})· 已试 ${o.attempts} 次 — 输入 `:`best ${o.best_ac}/${o.n} AC (need ${o.need}) · ${o.attempts} attempt(s) — type `)+chalk.bold.green("contest")+chalk.yellow(c?" 再战":" to retry"))}l&&console.log(chalk.gray(" "+(c?"下一阶段: ":"next: "))+chalk.bold.cyan(c?`阶段 ${l.number}`:`Phase ${l.number}`)+chalk.gray(" · ")+chalk.white(l.name)),console.log(chalk.bold.yellow(" "+h));for(const o of z(c))console.log(o)}export function renderCurriculumComplete(o,e){const n=a().startsWith("zh"),l=e.checkResults||{},r="-".repeat(48);let s=0,c=0;console.log();for(const o of t(" "))console.log(o);console.log(),console.log(chalk.bold.green(" "+r)),console.log(chalk.bold.green(" 🏆 "+(n?`${o.name} — 全部完成!`:`${o.name} — complete!`))),console.log(chalk.bold.green(" "+r));for(const e of o.modules){const[o,t]=e.cardRange;let r=0,a=0;for(let e=o;e<=t;e++){const o=l[String(e)];o&&(r++,o.correct&&a++)}s+=r,c+=a;const g=r>0&&a===r?chalk.green:chalk.white;console.log(chalk.gray(" "+(n?`阶段 ${e.number}`:`Phase ${e.number}`).padEnd(8))+g(`${a}/${r}`.padEnd(7))+chalk.gray(e.name))}console.log(chalk.bold.green(" "+r)),console.log(chalk.bold.green(" "+(n?"总分 / overall: ":"Overall: "))+chalk.bold.white(`${c}/${s}`)),console.log(chalk.bold.green(" "+r));for(const o of z(n))console.log(o);console.log()}export function renderMCQCard(o,l){const t=a(),r=e(o,t);y(r,l),m(),h(chalk.bold.yellow("🎯 "+r.title)),h(chalk.gray("─".repeat(Math.min(r.title.length+4,c)))),m();for(const o of C(r.question,60))h(" "+o);m();for(const o of["A","B","C","D"])h(chalk.cyan(` ${o}.`)+" "+chalk.white(r.options[o]));m(),u(),console.log(chalk.gray(" "+(t.startsWith("zh")?"输入 ":"Type "))+chalk.bold.green("A")+chalk.gray(" / ")+chalk.bold.green("B")+chalk.gray(" / ")+chalk.bold.green("C")+chalk.gray(" / ")+chalk.bold.green("D")+chalk.gray(" "+n("type_to_answer",t))),console.log()}export function renderMCQFeedback(o,l,t,r){const s=a(),c=e(o,s);console.log(),t?console.log(" "+chalk.bold.green(n("correct",s)+" ")+chalk.gray(`${n("one_point",s)} · ${l} = ${c.options[l]}`)):console.log(" "+chalk.bold.red(n("not_quite",s)+" ")+chalk.gray(`${n("you_chose",s)} ${l}; ${n("answer_is",s)} ${c.answer}.`)),console.log(),console.log(chalk.gray(" "+n("explanation",s)));for(const o of C(c.explanation,60))console.log(chalk.gray(" "+o));const g=Object.values(r.mcqResults),i=g.filter(o=>o.correct).length;console.log(),console.log(chalk.gray(" "+n("mcq_accuracy_so_far",s)+" ")+chalk.white(`${i}/${g.length}`)),console.log(),t||(console.log(chalk.gray(" "+n("press_ok_continue",s)+" ")+chalk.bold.green("ok")+chalk.gray(" "+n("to_continue",s))),console.log())}export function renderPracticalCard(o,l){const t=a(),r=e(o,t);y(r,l),m(),h(chalk.bold.yellow("🛠 "+r.title)),h(chalk.gray("─".repeat(Math.min(r.title.length+4,c)))),m();for(const o of C(r.task,60))h(" "+o);if(r.starterCode){m(),h(chalk.gray(" "+n("starter_code",t))),m();for(const o of r.starterCode.split("\n"))h(" "+chalk.cyan(o))}m(),u(),console.log(chalk.gray(" "+n("try_in_sandbox",t)+" ")+chalk.bold.cyan("!python3")+chalk.gray(" "+n("drops_into_python",t))),console.log(chalk.gray(" "+n("when_done",t))),console.log(chalk.gray(" ")+chalk.bold.green("done")+chalk.gray(" "+n("done_desc",t))),console.log(chalk.gray(" ")+chalk.bold.yellow("skip")+chalk.gray(" "+n("skip_desc",t))),console.log(chalk.gray(" card N jump to a card by number")),console.log()}export function renderPracticalSuccess(o){const l=a(),t=e(o,l);console.log(),console.log(" "+chalk.bold.green(n("practical_recorded",l))),console.log(),console.log(chalk.gray(" "+n("reference_answer",l)));for(const o of C(t.successHint,60))console.log(chalk.gray(" "+o));console.log(),console.log(chalk.gray(" "+n("press_ok_continue",l)+" ")+chalk.bold.green("ok")+chalk.gray(" "+n("to_continue",l))),console.log()}export function renderSimDemoCard(o,l){const t=a(),r=e(o,t);y(r,l),m(),h(chalk.bold.yellow("🎬 "+r.title)),m();for(const o of C(r.description,60))h(" "+o);m(),h(chalk.gray(" "+n("sim_requires",t))),m(),u(),console.log(chalk.gray(" ")+chalk.bold.cyan("sim")+chalk.gray(" "+n("sim_launch_desc",t))),console.log(chalk.gray(" ")+chalk.bold.green("ok")+chalk.gray(" / ")+chalk.bold.green("next")+chalk.gray(" "+n("continue_to_next",t))),console.log(chalk.gray(" bookmark "+n("bookmark_desc",t))),console.log(chalk.gray(" back "+n("back_desc",t))),console.log(chalk.gray(" card N jump to a card by number")),console.log(chalk.gray(" quit "+n("quit_desc",t))),console.log()}export function renderArenaTaskCard(o,n){const l=a(),t=l.startsWith("zh"),r=e(o,l);y(r,n),m(),h(chalk.bold.cyan("🏟 "+r.title)),h(chalk.gray(" "+r.origin+" · "+r.domain+" · "+r.metric)),m();for(const o of r.statement){for(const e of C(o,60))h(" "+e);m()}h(chalk.bold.green(t?" 核心要点 — baseline → 解法的关键变化":" CORE DELTA — baseline → solution"));for(const o of r.core_delta)for(const e of C("• "+o,58))h(" "+chalk.white(e));m(),h(chalk.gray((t?" 提交格式: ":" Submission: ")+r.submission_format)),r.hints&&h(chalk.gray(t?" 本题有 5 级提示 (a–e),进入 arena 后逐级解锁。":" 5 progressive hints (a–e) unlock inside the arena.")),m(),u(),console.log(chalk.gray(" ")+chalk.bold.cyan("arena "+r.task_id)+chalk.gray(t?" 动手做这道题(下载 / 提示 / 提交 / 榜单)":" open the arena (download / hints / submit / board)")),console.log(chalk.gray(" ")+chalk.bold.green("ok")+chalk.gray(" / ")+chalk.bold.green("next")+chalk.gray(t?" 继续下一张":" continue to next")),console.log(chalk.gray(" back "+(t?"上一张":"previous"))),console.log(chalk.gray(" card N jump to a card by number")),console.log(chalk.gray(" quit "+(t?"退出":"exit"))),console.log()}export function renderComingSoonCard(o,n){const l=a(),t=l.startsWith("zh"),r=e(o,l);y(r,n),m(),h(chalk.bold.yellow("🔜 "+r.title)),m();const s=r.note||(t?"这道竞技场题目正在编写中,敬请期待。":"This arena task is coming soon.");for(const o of C(s,60))h(" "+chalk.gray(o));m(),u(),console.log(chalk.gray(" ")+chalk.bold.green("ok")+chalk.gray(" / ")+chalk.bold.green("next")+chalk.gray(t?" 继续":" continue")),console.log(chalk.gray(" card N jump to a card by number")),console.log(chalk.gray(" quit "+(t?"退出":"exit"))),console.log()}export function renderMilestone(o,l){const t=a(),r=e(o,t),s=o.number===l.totalCards;console.log(),console.log(chalk.bold.yellow(" ╭"+g("═"))),console.log(chalk.bold.yellow(" ║")),console.log(chalk.bold.yellow(" ║ ")+chalk.white(" "+n("milestone_header",t))),console.log(chalk.bold.yellow(" ║"));const c=` ${r.badge} ${r.emoji}`;console.log(chalk.bold.yellow(" ║ ")+chalk.bold.green(c)),console.log(chalk.bold.yellow(" ║ ")+chalk.gray(" ─".repeat(Math.max(1,Math.floor(r.badge.length/2+2))))),console.log(chalk.bold.yellow(" ║")),console.log(chalk.bold.yellow(" ║ ")+" "+chalk.gray(n("in_wild_corresponds",t)));for(const o of C(r.realWorldLevel,60))console.log(chalk.bold.yellow(" ║ ")+" "+chalk.white(o));console.log(chalk.bold.yellow(" ║")),console.log(chalk.bold.yellow(" ║ ")+" "+chalk.gray(n("whats_next",t)));for(const o of C(r.unlockedNext,60))console.log(chalk.bold.yellow(" ║ ")+" "+chalk.white(o));console.log(chalk.bold.yellow(" ║")),console.log(chalk.bold.yellow(" ╰"+g("═"))),console.log(),s?(console.log(chalk.gray(" "+n("demo_complete",t))),console.log(),console.log(chalk.gray(" "+n("unlock_full",t)+" ")+chalk.white(n("curriculum_name",t))+chalk.gray(",")),console.log(chalk.gray(" "+n("contact_team_leader",t)+" ")+chalk.bold.yellow("EA")+chalk.gray(" "+n("learn_token",t))),console.log(chalk.gray(" "+n("or_email",t)+" ")+chalk.cyan("australia@icoa2026.au")+chalk.gray(" "+n("for_partnership",t))),console.log(),console.log(chalk.gray(" "+n("type_quit",t)+" ")+chalk.bold.green("quit")+chalk.gray(" "+n("to_exit",t)+" ")+chalk.bold.green("status")+chalk.gray(" "+n("for_dashboard",t)))):(console.log(chalk.gray(" ")+chalk.bold.green("ok")+chalk.gray(" / ")+chalk.bold.green("next")+chalk.gray(" "+n("continue_to_next",t))),console.log(chalk.gray(" status "+n("status_full",t))),console.log(chalk.gray(" card N jump to a card by number")),console.log(chalk.gray(" quit "+n("quit_desc",t)))),console.log()}export function renderStatus(o,e){const l=a(),t=e.cardsCompleted.length,r=o.totalCards,s=Object.values(e.mcqResults),c=s.filter(o=>o.correct).length;console.log(),console.log(chalk.cyan(" ╭─ ")+chalk.bold.white(n("status_title",l))+" "+chalk.cyan("─".repeat(25))),m(),h(" "+chalk.gray(n("total_progress",l)+" ")+i(t,r)+chalk.gray(` (${t}/${r})`)),h(" "+chalk.gray(n("streak",l)+" ")+d(e.streakDays)+chalk.gray(` (${n("longest",l)} ${e.longestStreak})`)),h(" "+chalk.gray(n("mcq_accuracy",l)+" ")+chalk.white(`${c}/${s.length}`)),h(" "+chalk.gray(n("practicals_done",l)+" ")+chalk.white(`${e.practicalsCompleted.length}`)),h(" "+chalk.gray(n("bookmarked",l)+" ")+chalk.white(`${e.bookmarks.length}`)),m();const g=n("module",l).replace(":","").trim();for(const n of o.modules){const[o,l]=n.cardRange,t=e.cardsCompleted.filter(e=>e>=o&&e<=l).length,r=l-o+1;h(" "+(t===r?chalk.green("✓"):t>0?chalk.yellow("▶"):chalk.gray("□"))+" "+chalk.gray(`${g} ${n.number}: `)+chalk.white(`${t}/${r}`)+chalk.gray(" "+n.name))}if(m(),e.achievements.length>0){h(" "+chalk.gray(n("achievements",l)));for(const o of e.achievements)h(" "+chalk.bold.yellow("★ ")+chalk.white(o))}else h(" "+chalk.gray(n("achievements_none",l)));m(),u()}
|
|
1
|
+
import chalk from"chalk";import o from"string-width";import{localized as e}from"./learn-curricula.js";import{t as n}from"./learn-i18n.js";import{getConfig as l}from"./config.js";import{icoaBannerLines as t}from"./banner.js";import{renderCharts as r,detectCaps as s}from"./render-card.js";function c(){return(l().language||"en").toLowerCase()}const a=64;function g(o="─"){return o.repeat(66)}function i(o,e,n=20){const l=e>0?o/e:0,t=Math.floor(l*n),r=n-t;return`${chalk.green("█".repeat(t))+chalk.gray("░".repeat(r))} ${(100*l).toFixed(1)}%`}function d(o){return o>=30?chalk.red("🔥🔥🔥 "+o+" days"):o>=7?chalk.yellow("🔥 "+o+" days"):o>=1?chalk.gray("· "+o+" day"+(o>1?"s":"")):chalk.gray("—")}export function renderWelcome(o,e,l){const t=c(),r=e.cardsCompleted.length,s=o.totalCards,a=s-r,y=o.modules.find(o=>e.currentCard>=o.cardRange[0]&&e.currentCard<=o.cardRange[1]),u=/demo/i.test(o.id);if(console.log(),console.log(chalk.cyan(" ╭"+g("═"))),console.log(chalk.cyan(" ║")),console.log(chalk.cyan(" ║ ")+chalk.bold.white(" "+(o.name?.trim()||n("academy_title",t)))),console.log(chalk.cyan(" ║")),l)console.log(chalk.cyan(" ║ ")+chalk.white(" "+n(u?"welcome_new_demo":"welcome_new",t)));else{const o=new Date(e.lastSeenAt),l=Math.floor((Date.now()-o.getTime())/36e5),r=l<1?n("just_now",t):l<24?`${l}${n("ago_hours",t)}`:`${Math.floor(l/24)}${n("ago_days",t)}`;console.log(chalk.cyan(" ║ ")+chalk.white(` ${n("welcome_back",t)} ${r}`))}console.log(chalk.cyan(" ║"));const h=y?`${y.number}. ${y.name}`:"—";console.log(chalk.cyan(" ║ ")+" "+chalk.gray(n("module",t).padEnd(11))+chalk.white(h)),console.log(chalk.cyan(" ║ ")+" "+chalk.gray(n("progress",t).padEnd(11))+i(r,s)+chalk.gray(` (${r}/${s})`)),console.log(chalk.cyan(" ║ ")+" "+chalk.gray(n("streak",t).padEnd(11))+d(e.streakDays)),a>0&&e.currentCard<=s&&console.log(chalk.cyan(" ║ ")+" "+chalk.gray(n("next_card",t).padEnd(11))+chalk.white(`#${e.currentCard} ${n("of",t)} ${s}`)),console.log(chalk.cyan(" ║")),console.log(chalk.cyan(" ╰"+g("═"))),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────"));const m=0===r?n("continue_start",t):`${n("continue_resume",t)} ${e.currentCard}`;console.log(chalk.bold.green(" continue")+chalk.gray(" "+m)),console.log(chalk.yellow(" status")+chalk.gray(" "+n("status_full",t))),e.bookmarks.length>0&&console.log(chalk.yellow(" bookmarks")+chalk.gray(` ${e.bookmarks.length} ${n("bookmarks_desc",t)}`)),console.log(chalk.gray(" quit")+chalk.gray(" "+n("quit_desc",t))),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log()}function y(o,e){const l=c(),t=o.module,r=e.modules.find(o=>o.number===t),s=r?r.name:n("unknown_module",l),g=n("module",l).replace(":","").trim(),i=`${n("card",l)} ${o.number} / ${e.totalCards} · ${g} ${t} · ${s}`;console.log(),console.log(chalk.cyan(" ╭─ ")+chalk.bold.white(i)+" "+chalk.cyan("─".repeat(Math.max(0,a-i.length-4))))}function u(){console.log(chalk.cyan(" ╰"+g())),console.log()}function h(o){console.log(chalk.cyan(" │ ")+chalk.white(o))}function m(){console.log(chalk.cyan(" │"))}const b=/[┌┐└┘├┤┬┴┼─│╔╗╚╝═║╠╣╦╩╬▓▒░█]/;function f(o){return/^(?: {4}|\t)/.test(o)||b.test(o)||/ {3,}/.test(o)}function p(e,n){const l=[];let t="",r=0,s=!1;for(const c of function(e){const n=[],l=e=>{let l="";const t=()=>{l&&(n.push({t:"atom",s:l}),l="")};for(const r of e)" "===r?(t(),n.push({t:"space"})):2===o(r)?(t(),n.push({t:"wide",s:r})):l+=r;t()},t=/`[^`]*`/g;let r,s=0;for(;null!==(r=t.exec(e));)l(e.slice(s,r.index)),n.push({t:"code",s:r[0]}),s=r.index+r[0].length;return l(e.slice(s)),n}(e)){if("space"===c.t){""!==t&&(s=!0);continue}const e=c.s,a=o("code"===c.t?e.replace(/`/g,""):e),g=s?1:0,i="atom"===c.t&&e.length<=2&&/^[.,;:!?)\]}…'"]+$/.test(e);""!==t&&!i&&r+g+a>n?(l.push(t),t=e,r=a,s=!1):(s&&(t+=" ",r+=1,s=!1),t+=e,r+=a)}return t&&l.push(t),l}const $=/\b(?:Common bug|Common mistake|Common error|Pitfall|Gotcha|Footgun)\b[::]?|\b(?:Warning|Caution)\b[::]|(?:常见错误|常见陷阱|常见误区|陷阱|注意|警告|误区)[::]/gi;function w(o,e){const n=e?chalk.bold.white:chalk.white;let l,t="",r=0;for($.lastIndex=0;null!==(l=$.exec(o));)l.index>r&&(t+=n(o.slice(r,l.index))),t+=chalk.bold.yellow(l[0]),r=l.index+l[0].length;return r<o.length&&(t+=n(o.slice(r))),t}function _(o,e=!1){let n="",l=0;const t=/`([^`]*)`/g;let r;for(;null!==(r=t.exec(o));)r.index>l&&(n+=w(o.slice(l,r.index),e)),n+=chalk.cyan(r[1]),l=r.index+r[0].length;return l<o.length&&(n+=w(o.slice(l),e)),n}function k(o,e=!1){let n=!1;for(const l of o.split("\n"))if(/^\s*```/.test(l))n=!n;else if(n)console.log(chalk.cyan(" │ ")+chalk.cyan(` ${l}`));else if(""!==l)if(f(l))console.log(chalk.cyan(" │ ")+chalk.white(l));else for(const o of p(l,60))console.log(chalk.cyan(" │ ")+` ${_(o,e)}`);else m()}function C(o,e){const n=[];for(const l of o.split("\n")){if(""===l){n.push("");continue}if(f(l)){n.push(l);continue}let o="";for(const t of l.split(" "))(o+" "+t).trim().length>e?(n.push(o.trim()),o=t):o=(o+" "+t).trim();o&&n.push(o)}return n}let x=!1;export function setLearnAiEnabled(o){x=o}let v=new Set;export function setLearnPqsViewCards(o){v=o}export function renderHonorWall(o){const e=c().startsWith("zh"),{totals:n,chapters:l}=o,t=n.gold+n.silver+n.bronze,r=(o,e)=>e>0?chalk.bold.white(`${o}×${e}`):chalk.gray(`${o}×0`);if(console.log(chalk.gray(" ─────────────────────────────────────────")),console.log(chalk.bold.white(e?" 🏅 奖牌墙 ":" 🏅 Medal wall ")+r("🥇",n.gold)+chalk.gray(" · ")+r("🥈",n.silver)+chalk.gray(" · ")+r("🥉",n.bronze)+chalk.gray(e?" (永久 — 挂上墙就不会摘下)":" (permanent — once earned, never removed)")),0===t){const n=null!=o.floor?`${o.floor}+`:e?"高分":"a high score";return console.log(chalk.gray(e?` 还没有奖牌 — 单卡提问评分 ${n} 并进入该卡前三,即可永久挂墙 🥇🥈🥉`:` No medals yet — score ${n} on a card and hold its top-3 to hang one 🥇🥈🥉`)),void console.log(chalk.gray(" ─────────────────────────────────────────"))}const s=l.map(o=>{const e=`Ch${o.chapter}`,n=(o.gold>0?`🥇${o.gold}`:"")+(o.silver>0?`🥈${o.silver}`:"")+(o.bronze>0?`🥉${o.bronze}`:"");return n?chalk.white(`${e} ${n}`):chalk.gray(`${e} —`)}).join(chalk.gray(" "));console.log(" "+s);const a=l.flatMap(o=>o.cards.map(e=>({...e,chapter:o.chapter})));a.sort((o,e)=>o.rank-e.rank||o.chapter-e.chapter||o.card-e.card);for(const o of a.slice(0,24)){const n=(o.since||"").slice(0,10);console.log(chalk.white(` ${o.icon} `)+chalk.gray(`Ch${o.chapter} · `)+chalk.white(e?`第 ${o.card} 张`:`card ${o.card}`)+chalk.gray(` · ${o.score}`)+(n?chalk.gray(e?` · 始于 ${n}`:` · since ${n}`):""))}a.length>24&&console.log(chalk.gray(e?` …还有 ${a.length-24} 枚`:` …and ${a.length-24} more`)),console.log(chalk.gray(" ─────────────────────────────────────────"))}export function renderKnowledgeCard(l,t){const g=c(),i=e(l,g);y(i,t),m();const d=p(i.title,60);for(const o of d)h(chalk.bold.yellow(o));h(chalk.gray("─".repeat(Math.min(Math.max(...d.map(e=>o(e))),a))));const b=!!(l.chart_ids&&l.chart_ids.length>0);if(b&&r(l.chart_ids.slice(0,1),t.charts,s()),i.key_point&&""!==i.key_point.trim()?(b||m(),function(o,e,n){const l=c().startsWith("zh"),t=s().cols,r="-".repeat(Math.min(a,Math.max(24,t-6))),g=l?"要点":"KEY POINT",i=l?`第 ${e} / ${n} 张`:`Card ${e} / ${n}`;console.log(chalk.cyan(" │ ")+chalk.green(r)),console.log(chalk.cyan(" │ ")+chalk.green(`${g} · ${i}`));for(const e of p(o,60))console.log(chalk.cyan(" │ ")+chalk.bold.green(" "+e));console.log(chalk.cyan(" │ ")+chalk.green(r)),m()}(i.key_point,i.number,t.totalCards)):b||m(),i.body.filter(o=>null!=o&&""!==o.trim()).forEach((o,e)=>{const n=0===e?o.match(/^([\s\S]*?(?:[.!?](?=\s|$)|[。!?]))(\s[\s\S]*)?$/):null;if(n&&n[1].trim().length<=180){k(n[1],!0);const o=n[2]?.trimStart();o&&k(o,!1)}else k(o,!1);m()}),i.icoaConnection){h(chalk.magenta(" "+n("icoa_connection",g))),h(chalk.gray(" "+"─".repeat(20)));for(const o of p(i.icoaConnection,60))h(" "+chalk.magenta(o));m()}if(l.check){const o=g.startsWith("zh"),e=l._zh?.checkStatement??l._zh?.check?.statement,n=o?e??l.check.statement:l.check.statement;h(chalk.cyan(" 🤔 "+(o?"请回答 (输 y 或 n):":"Quick comprehension check (type y or n):"))),h(chalk.gray(" "+"─".repeat(20)));for(const o of p(n,60))h(" "+chalk.white(o));m()}u(),function(e,n){const l=c().startsWith("zh"),t=s().cols,r="-".repeat(Math.min(a,Math.max(24,t-6))),g=chalk.gray(" "),i=[];e.check?(i.push(chalk.bold.green("[y]")+chalk.gray(l?" 是":" yes")),i.push(chalk.bold.green("[n]")+chalk.gray(l?" 否":" no"))):i.push(chalk.bold.green("[ok]")+chalk.gray(l?" 继续":" next")),x&&(i.push(chalk.bold.magenta("[/ai]")+chalk.gray(l?" AI导师":" AI tutor")),i.push(chalk.bold.magenta("[/submit]")+chalk.gray(l?" 提问评分":" prompt score")),v.has(e.number)&&i.push(chalk.bold.magenta("[/view]")+chalk.gray(l?" 榜单":" top-3"))),i.push(chalk.bold.yellow("[e]")+chalk.gray(l?" 报告问题":" report")),i.push(chalk.bold.cyan("[b]")+chalk.gray(l?" 上一张":" back")),i.push(chalk.bold.cyan("[card N]")+chalk.gray(l?" 跳转":" jump")),i.push(chalk.gray("[quit]")+(l?chalk.gray(" 退出"):""));const d=o(r);console.log(chalk.gray(" "+r)),console.log(chalk.gray(" "+(l?`第 ${e.number} / ${n.totalCards} 张`:`Card ${e.number} / ${n.totalCards}`)));let y="",u=0;for(const e of i){const n=o(e);y&&u+2+n>d?(console.log(" "+y),y=e,u=n):(y=y?y+g+e:e,u=y===e?n:u+2+n)}y&&console.log(" "+y),console.log(chalk.gray(" "+r));const h=[l?"e = 报告本卡错误,不算作答;提交后回到本题继续":"e = report a card problem, not an answer — you return here after",l?"更多: bookmark · status · /honors 奖牌墙 · lang zh/en":"more: bookmark · status · /honors · lang zh/en"];for(const o of h)for(const e of p(o,d))console.log(chalk.gray(" "+e));console.log()}(l,t)}export function renderCheckFeedback(o,e,n,l=!1){const t=c().startsWith("zh");if(console.log(),n)console.log(chalk.green(" ✓ ")+chalk.white(t?"答对了。":"Correct."));else if(console.log(chalk.yellow(" ✗ ")+chalk.white(t?"记下了,继续。":"Noted — moving on.")),l){const e="y"===o.check?.answer?t?"是":"Yes":t?"否":"No";console.log(chalk.gray(" "+(t?"正确答案: ":"Answer: ")+e+". "+(t?"回看上面那段。":"Re-read the paragraph above.")))}const r=t?o._zh?.solution??o.solution:o.solution;if(r&&r.length>0){console.log(),console.log(chalk.cyan(" 📝 "+(t?"解答:":"Worked solution:")));for(const o of r){for(const e of p(o,60))console.log(chalk.white(" "+e));console.log()}}console.log()}function z(o){return[chalk.white(" "+(o?"编著:Charlie Zhu":"Created & edited by Charlie Zhu")),chalk.gray(" "+(o?"ICOA 2026 创始人 · 课程总架构师":"Founder & Chief Curriculum Architect · ICOA 2026"))]}export function renderPhaseComplete(o,e,n,l,r,s){const a=c().startsWith("zh"),[g,i]=n.cardRange,d=e.checkResults||{};let y=0,u=0;for(let o=g;o<=i;o++){const e=d[String(o)];e&&(y++,e.correct&&u++)}const h="-".repeat(48);console.log();for(const o of t(" "))console.log(o);console.log(),console.log(chalk.bold.yellow(" "+h)),console.log(chalk.bold.yellow(" "+(a?`阶段 ${n.number} 完成`:`Phase ${n.number} complete`))+chalk.gray(" · ")+chalk.white(n.name));const m=y>0&&u===y?chalk.bold.green:chalk.bold.white;console.log(chalk.gray(" "+(a?"理解检查得分: ":"comprehension score: "))+m(`${u}/${y}`)+chalk.gray(0===y?a?" (本阶段未作答 y/n)":" (no y/n answered)":""));const b=String(i).length;for(let e=g;e<=i;e++){const n=o.cards.find(o=>o.number===e);if(!n)continue;const l=n,t=l.title??l.badge??"",r=(a?l._zh?.title??l._zh?.badge??t:t)||"",s=null!=n.check,c=d[String(e)];let g;g=c?c.correct?chalk.green("✓"):chalk.red("✗"):s?chalk.gray("○"):chalk.gray("·");const i=p(r,54-b),y=i[0]+(i.length>1?"…":"");console.log(chalk.gray(` ${String(e).padStart(b)} `)+g+chalk.white(" "+y))}if(console.log(chalk.gray(a?" ✓ 答对 · ✗ 答错 · ○ 未作答 · 无 y/n 的卡不计分":" ✓ correct · ✗ wrong · ○ unanswered · cards without a y/n are unscored")),r){const o=o=>"green"===o?chalk.bold.green:"blue"===o?chalk.bold.cyan:"yellow"===o?chalk.bold.yellow:chalk.bold.red;console.log(chalk.bold.magenta(" ⭐ "+(a?"提问得分 (best): ":"Prompt scores (best): "))+(0===r.length?chalk.gray(a?"本阶段尚未提问 — 用 /ai 研究,再 /submit 评分":"none this phase — research with /ai, then /submit"):r.map(e=>chalk.gray((a?"卡":"#")+e.card+" ")+o(e.tier)(String(e.score))).join(chalk.gray(" · "))))}if(s?.configured&&s.contest){const o=s.contest,e=chalk.bold.cyan(" ⚖ "+(a?"判分赛: ":"Contest: "));o.passed?console.log(e+chalk.bold.green(`${o.best_ac}/${o.n} AC ✅ `+(a?"已过关":"passed"))):0===o.attempts?console.log(e+chalk.white(a?`${o.n} 题 · 过 ${o.need} 题解锁 — 输入 `:`${o.n} problems · AC ${o.need} to pass — type `)+chalk.bold.green("contest")+chalk.white(a?" 开始":" to start")):console.log(e+chalk.yellow(a?`最好 ${o.best_ac}/${o.n} AC(需 ${o.need})· 已试 ${o.attempts} 次 — 输入 `:`best ${o.best_ac}/${o.n} AC (need ${o.need}) · ${o.attempts} attempt(s) — type `)+chalk.bold.green("contest")+chalk.yellow(a?" 再战":" to retry"))}l&&console.log(chalk.gray(" "+(a?"下一阶段: ":"next: "))+chalk.bold.cyan(a?`阶段 ${l.number}`:`Phase ${l.number}`)+chalk.gray(" · ")+chalk.white(l.name)),console.log(chalk.bold.yellow(" "+h));for(const o of z(a))console.log(o)}export function renderCurriculumComplete(o,e){const n=c().startsWith("zh"),l=e.checkResults||{},r="-".repeat(48);let s=0,a=0;console.log();for(const o of t(" "))console.log(o);console.log(),console.log(chalk.bold.green(" "+r)),console.log(chalk.bold.green(" 🏆 "+(n?`${o.name} — 全部完成!`:`${o.name} — complete!`))),console.log(chalk.bold.green(" "+r));for(const e of o.modules){const[o,t]=e.cardRange;let r=0,c=0;for(let e=o;e<=t;e++){const o=l[String(e)];o&&(r++,o.correct&&c++)}s+=r,a+=c;const g=r>0&&c===r?chalk.green:chalk.white;console.log(chalk.gray(" "+(n?`阶段 ${e.number}`:`Phase ${e.number}`).padEnd(8))+g(`${c}/${r}`.padEnd(7))+chalk.gray(e.name))}console.log(chalk.bold.green(" "+r)),console.log(chalk.bold.green(" "+(n?"总分 / overall: ":"Overall: "))+chalk.bold.white(`${a}/${s}`)),console.log(chalk.bold.green(" "+r));for(const o of z(n))console.log(o);console.log()}export function renderMCQCard(o,l){const t=c(),r=e(o,t);y(r,l),m(),h(chalk.bold.yellow("🎯 "+r.title)),h(chalk.gray("─".repeat(Math.min(r.title.length+4,a)))),m();for(const o of C(r.question,60))h(" "+o);m();for(const o of["A","B","C","D"])h(chalk.cyan(` ${o}.`)+" "+chalk.white(r.options[o]));m(),u(),console.log(chalk.gray(" "+(t.startsWith("zh")?"输入 ":"Type "))+chalk.bold.green("A")+chalk.gray(" / ")+chalk.bold.green("B")+chalk.gray(" / ")+chalk.bold.green("C")+chalk.gray(" / ")+chalk.bold.green("D")+chalk.gray(" "+n("type_to_answer",t))),console.log()}export function renderMCQFeedback(o,l,t,r){const s=c(),a=e(o,s);console.log(),t?console.log(" "+chalk.bold.green(n("correct",s)+" ")+chalk.gray(`${n("one_point",s)} · ${l} = ${a.options[l]}`)):console.log(" "+chalk.bold.red(n("not_quite",s)+" ")+chalk.gray(`${n("you_chose",s)} ${l}; ${n("answer_is",s)} ${a.answer}.`)),console.log(),console.log(chalk.gray(" "+n("explanation",s)));for(const o of C(a.explanation,60))console.log(chalk.gray(" "+o));const g=Object.values(r.mcqResults),i=g.filter(o=>o.correct).length;console.log(),console.log(chalk.gray(" "+n("mcq_accuracy_so_far",s)+" ")+chalk.white(`${i}/${g.length}`)),console.log(),t||(console.log(chalk.gray(" "+n("press_ok_continue",s)+" ")+chalk.bold.green("ok")+chalk.gray(" "+n("to_continue",s))),console.log())}export function renderPracticalCard(o,l){const t=c(),r=e(o,t);y(r,l),m(),h(chalk.bold.yellow("🛠 "+r.title)),h(chalk.gray("─".repeat(Math.min(r.title.length+4,a)))),m();for(const o of C(r.task,60))h(" "+o);if(r.starterCode){m(),h(chalk.gray(" "+n("starter_code",t))),m();for(const o of r.starterCode.split("\n"))h(" "+chalk.cyan(o))}m(),u(),console.log(chalk.gray(" "+n("try_in_sandbox",t)+" ")+chalk.bold.cyan("!python3")+chalk.gray(" "+n("drops_into_python",t))),console.log(chalk.gray(" "+n("when_done",t))),console.log(chalk.gray(" ")+chalk.bold.green("done")+chalk.gray(" "+n("done_desc",t))),console.log(chalk.gray(" ")+chalk.bold.yellow("skip")+chalk.gray(" "+n("skip_desc",t))),console.log(chalk.gray(" card N jump to a card by number")),console.log()}export function renderPracticalSuccess(o){const l=c(),t=e(o,l);console.log(),console.log(" "+chalk.bold.green(n("practical_recorded",l))),console.log(),console.log(chalk.gray(" "+n("reference_answer",l)));for(const o of C(t.successHint,60))console.log(chalk.gray(" "+o));console.log(),console.log(chalk.gray(" "+n("press_ok_continue",l)+" ")+chalk.bold.green("ok")+chalk.gray(" "+n("to_continue",l))),console.log()}export function renderSimDemoCard(o,l){const t=c(),r=e(o,t);y(r,l),m(),h(chalk.bold.yellow("🎬 "+r.title)),m();for(const o of C(r.description,60))h(" "+o);m(),h(chalk.gray(" "+n("sim_requires",t))),m(),u(),console.log(chalk.gray(" ")+chalk.bold.cyan("sim")+chalk.gray(" "+n("sim_launch_desc",t))),console.log(chalk.gray(" ")+chalk.bold.green("ok")+chalk.gray(" / ")+chalk.bold.green("next")+chalk.gray(" "+n("continue_to_next",t))),console.log(chalk.gray(" bookmark "+n("bookmark_desc",t))),console.log(chalk.gray(" back "+n("back_desc",t))),console.log(chalk.gray(" card N jump to a card by number")),console.log(chalk.gray(" quit "+n("quit_desc",t))),console.log()}export function renderArenaTaskCard(o,n){const l=c(),t=l.startsWith("zh"),r=e(o,l);y(r,n),m(),h(chalk.bold.cyan("🏟 "+r.title)),h(chalk.gray(" "+r.origin+" · "+r.domain+" · "+r.metric)),m();for(const o of r.statement){for(const e of C(o,60))h(" "+e);m()}h(chalk.bold.green(t?" 核心要点 — baseline → 解法的关键变化":" CORE DELTA — baseline → solution"));for(const o of r.core_delta)for(const e of C("• "+o,58))h(" "+chalk.white(e));m(),h(chalk.gray((t?" 提交格式: ":" Submission: ")+r.submission_format)),r.hints&&h(chalk.gray(t?" 本题有 5 级提示 (a–e),进入 arena 后逐级解锁。":" 5 progressive hints (a–e) unlock inside the arena.")),m(),u(),console.log(chalk.gray(" ")+chalk.bold.cyan("arena "+r.task_id)+chalk.gray(t?" 动手做这道题(下载 / 提示 / 提交 / 榜单)":" open the arena (download / hints / submit / board)")),console.log(chalk.gray(" ")+chalk.bold.green("ok")+chalk.gray(" / ")+chalk.bold.green("next")+chalk.gray(t?" 继续下一张":" continue to next")),console.log(chalk.gray(" back "+(t?"上一张":"previous"))),console.log(chalk.gray(" card N jump to a card by number")),console.log(chalk.gray(" quit "+(t?"退出":"exit"))),console.log()}export function renderComingSoonCard(o,n){const l=c(),t=l.startsWith("zh"),r=e(o,l);y(r,n),m(),h(chalk.bold.yellow("🔜 "+r.title)),m();const s=r.note||(t?"这道竞技场题目正在编写中,敬请期待。":"This arena task is coming soon.");for(const o of C(s,60))h(" "+chalk.gray(o));m(),u(),console.log(chalk.gray(" ")+chalk.bold.green("ok")+chalk.gray(" / ")+chalk.bold.green("next")+chalk.gray(t?" 继续":" continue")),console.log(chalk.gray(" card N jump to a card by number")),console.log(chalk.gray(" quit "+(t?"退出":"exit"))),console.log()}export function renderMilestone(o,l){const t=c(),r=e(o,t),s=o.number===l.totalCards;console.log(),console.log(chalk.bold.yellow(" ╭"+g("═"))),console.log(chalk.bold.yellow(" ║")),console.log(chalk.bold.yellow(" ║ ")+chalk.white(" "+n("milestone_header",t))),console.log(chalk.bold.yellow(" ║"));const a=` ${r.badge} ${r.emoji}`;console.log(chalk.bold.yellow(" ║ ")+chalk.bold.green(a)),console.log(chalk.bold.yellow(" ║ ")+chalk.gray(" ─".repeat(Math.max(1,Math.floor(r.badge.length/2+2))))),console.log(chalk.bold.yellow(" ║")),console.log(chalk.bold.yellow(" ║ ")+" "+chalk.gray(n("in_wild_corresponds",t)));for(const o of C(r.realWorldLevel,60))console.log(chalk.bold.yellow(" ║ ")+" "+chalk.white(o));console.log(chalk.bold.yellow(" ║")),console.log(chalk.bold.yellow(" ║ ")+" "+chalk.gray(n("whats_next",t)));for(const o of C(r.unlockedNext,60))console.log(chalk.bold.yellow(" ║ ")+" "+chalk.white(o));console.log(chalk.bold.yellow(" ║")),console.log(chalk.bold.yellow(" ╰"+g("═"))),console.log(),s?(console.log(chalk.gray(" "+n("demo_complete",t))),console.log(),console.log(chalk.gray(" "+n("unlock_full",t)+" ")+chalk.white(n("curriculum_name",t))+chalk.gray(",")),console.log(chalk.gray(" "+n("contact_team_leader",t)+" ")+chalk.bold.yellow("EA")+chalk.gray(" "+n("learn_token",t))),console.log(chalk.gray(" "+n("or_email",t)+" ")+chalk.cyan("australia@icoa2026.au")+chalk.gray(" "+n("for_partnership",t))),console.log(),console.log(chalk.gray(" "+n("type_quit",t)+" ")+chalk.bold.green("quit")+chalk.gray(" "+n("to_exit",t)+" ")+chalk.bold.green("status")+chalk.gray(" "+n("for_dashboard",t)))):(console.log(chalk.gray(" ")+chalk.bold.green("ok")+chalk.gray(" / ")+chalk.bold.green("next")+chalk.gray(" "+n("continue_to_next",t))),console.log(chalk.gray(" status "+n("status_full",t))),console.log(chalk.gray(" card N jump to a card by number")),console.log(chalk.gray(" quit "+n("quit_desc",t)))),console.log()}export function renderStatus(o,e){const l=c(),t=e.cardsCompleted.length,r=o.totalCards,s=Object.values(e.mcqResults),a=s.filter(o=>o.correct).length;console.log(),console.log(chalk.cyan(" ╭─ ")+chalk.bold.white(n("status_title",l))+" "+chalk.cyan("─".repeat(25))),m(),h(" "+chalk.gray(n("total_progress",l)+" ")+i(t,r)+chalk.gray(` (${t}/${r})`)),h(" "+chalk.gray(n("streak",l)+" ")+d(e.streakDays)+chalk.gray(` (${n("longest",l)} ${e.longestStreak})`)),h(" "+chalk.gray(n("mcq_accuracy",l)+" ")+chalk.white(`${a}/${s.length}`)),h(" "+chalk.gray(n("practicals_done",l)+" ")+chalk.white(`${e.practicalsCompleted.length}`)),h(" "+chalk.gray(n("bookmarked",l)+" ")+chalk.white(`${e.bookmarks.length}`)),m();const g=n("module",l).replace(":","").trim();for(const n of o.modules){const[o,l]=n.cardRange,t=e.cardsCompleted.filter(e=>e>=o&&e<=l).length,r=l-o+1;h(" "+(t===r?chalk.green("✓"):t>0?chalk.yellow("▶"):chalk.gray("□"))+" "+chalk.gray(`${g} ${n.number}: `)+chalk.white(`${t}/${r}`)+chalk.gray(" "+n.name))}if(m(),e.achievements.length>0){h(" "+chalk.gray(n("achievements",l)));for(const o of e.achievements)h(" "+chalk.bold.yellow("★ ")+chalk.white(o))}else h(" "+chalk.gray(n("achievements_none",l)));m(),u()}
|