icoa-cli 2.19.517 → 2.19.519
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/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/learn-curricula.js +1 -1
- package/dist/lib/learn-profile.js +1 -0
- package/dist/lib/learn-render.js +1 -1
- package/dist/lib/learn-study-timer.js +1 -0
- package/dist/repl.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
export function localized(r
|
|
1
|
+
export function localized(e,r){if(!r.startsWith("zh")||!e._zh)return e;const t=e._zh,n={...e};for(const e of Object.keys(t))void 0!==t[e]&&(n[e]=t[e]);return n}export function isValidCurriculum(e){if(!e||"object"!=typeof e)return!1;const r=e;return"string"==typeof r.id&&"number"==typeof r.totalCards&&Array.isArray(r.cards)&&Array.isArray(r.modules)}import{readFileSync as e,writeFileSync as r,mkdirSync as t,existsSync as n,statSync as a,utimesSync as o}from"node:fs";import{join as i}from"node:path";import{homedir as c}from"node:os";import{getDeviceFingerprint as s}from"./access.js";import{getCliVersion as u}from"./version.js";import{httpFetch as l}from"./http.js";import{parseHometask as d}from"./learn-hometask.js";import{parseFaqs as p,parseReplies as m}from"./learn-replies.js";const f=i(c(),".icoa","learn-cache");export const LEARN_SERVER="https://practice.icoa2026.au";function g(e){const r=e.replace(/[^A-Za-z0-9_-]/g,"_");return i(f,`${r}.json`)}function _(e){const r=e.replace(/[^A-Za-z0-9_-]/g,"_");return i(f,`${r}.etag`)}let h=null;export function getLastCurriculumFailure(){return h}export async function loadCurriculumById(i,c,s=!1){"ctf4eai-12"===i&&(i="LEARNDEMO01");const l=function(r){const t=g(r);if(!n(t))return null;try{const r=a(t),n=Date.now()-r.mtimeMs;return{curriculum:JSON.parse(e(t,"utf-8")),ageMs:n}}catch{return null}}(i);if(!s&&l&&l.ageMs<6048e5&&isValidCurriculum(l.curriculum))return l.curriculum;h=null;const d=await async function(r,t){const a=LEARN_SERVER.replace(/\/$/,"")+"/api/icoa/learn/curriculum/"+encodeURIComponent(r),o={},i=function(r){const t=_(r);if(!n(t))return null;try{return e(t,"utf-8").trim()||null}catch{return null}}(r);i&&(o["If-None-Match"]=i),t&&(o["X-Learn-Token"]=t.toUpperCase()),o["X-ICOA-Client"]=u();try{const e=await fetch(a,{headers:o,signal:AbortSignal.timeout(6e4)});if(304===e.status)return{kind:"unchanged"};if(404===e.status)return{kind:"notfound"};if(!e.ok)return{kind:"error"};const r=await e.json();if(!r.success||!r.data)return{kind:"error"};if(!isValidCurriculum(r.data))return{kind:"error"};const t=e.headers.get("etag")||e.headers.get("ETag")||void 0;return{kind:"fresh",curriculum:r.data,etag:t||void 0}}catch{return{kind:"error"}}}(i,c);return"fresh"===d.kind?(function(e,n,a){try{t(f,{recursive:!0}),r(g(e),JSON.stringify(n)),a&&r(_(e),a)}catch{}}(i,d.curriculum,d.etag),d.curriculum):"unchanged"===d.kind&&l&&isValidCurriculum(l.curriculum)?(function(e){try{const r=g(e);if(n(r)){const e=Date.now()/1e3;o(r,e,e)}}catch{}}(i),l.curriculum):l&&isValidCurriculum(l.curriculum)?l.curriculum:(h="notfound"===d.kind?"notfound":"offline",null)}export function validateResultShape(e){return{curriculumId:String(e.curriculum_id??""),status:String(e.status??""),validUntil:String(e.valid_until??""),aiEnabled:1===e.ai_enabled||!0===e.ai_enabled,gated:1===e.gated||!0===e.gated,replies:m(e.replies),hometask:d(e.hometask)}}export async function validateEAToken(e){const r=LEARN_SERVER.replace(/\/$/,"")+"/api/icoa/learn/validate";try{const t=await l(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:e.toUpperCase(),device_fingerprint:s()}),signal:AbortSignal.timeout(8e3)});if(!t.ok){const e=await t.json().catch(()=>({}));return{ok:!1,message:e.message||`HTTP ${t.status}`,code:e.code}}const n=await t.json();return n.success&&n.data?{ok:!0,...validateResultShape(n.data)}:{ok:!1,message:n.message||"Validation failed"}}catch(e){return{ok:!1,message:`Network error: ${e instanceof Error?e.message:String(e)}`}}}export async function syncProgress(e,r,t){if("LEARNDEMO01"===e.toUpperCase())return;const n=LEARN_SERVER.replace(/\/$/,"")+"/api/icoa/learn/progress/"+e.toUpperCase();try{await l(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({card_number:r.card_number,event_type:r.event_type,mcq_answer:r.mcq_answer,mcq_correct:r.mcq_correct?1:0,check_answer:r.check_answer,check_correct:r.check_correct?1:0,time_on_card_ms:r.time_on_card_ms,device_fingerprint:s(),reply_ack:t&&t.length>0?t:void 0}),signal:AbortSignal.timeout(5e3)})}catch{}}export async function postFocusSession(e,r){if("LEARNDEMO01"===e.toUpperCase())return!0;const t=LEARN_SERVER.replace(/\/$/,"")+"/api/icoa/learn/focus/"+e.toUpperCase();try{return(await l(t,{method:"POST",headers:{"Content-Type":"application/json","X-ICOA-Client":u()},body:JSON.stringify({...r,device_fingerprint:s()}),signal:AbortSignal.timeout(5e3)})).ok}catch{return!1}}export async function fetchServerProgress(e){if("LEARNDEMO01"===e.toUpperCase())return null;const r=LEARN_SERVER.replace(/\/$/,"")+"/api/icoa/learn/progress/"+e.toUpperCase();try{const e=await l(r,{signal:AbortSignal.timeout(5e3)});if(!e.ok)return null;const t=await e.json();return t?.success&&Array.isArray(t.data?.events)?t.data.events:null}catch{return null}}export async function fetchCardFaqs(e){const r=LEARN_SERVER.replace(/\/$/,"")+"/api/icoa/learn/faq/"+encodeURIComponent(e.toUpperCase());try{const e=await l(r,{signal:AbortSignal.timeout(5e3)});if(!e.ok)return[];const t=await e.json();return t.success&&t.data?p(t.data.faqs):[]}catch{return[]}}export async function syncCardFeedback(e,r){const t=LEARN_SERVER.replace(/\/$/,"")+"/api/icoa/learn/feedback/"+e.toUpperCase();try{return(await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({curriculum_id:r.curriculum_id,card_number:r.card_number,feedback_text:r.feedback_text,time_on_card_ms:r.time_on_card_ms}),signal:AbortSignal.timeout(5e3)})).ok}catch{return!1}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import chalk from"chalk";import{LEARN_SERVER as e}from"./learn-curricula.js";import{httpFetch as t}from"./http.js";import{getCliVersion as s}from"./version.js";import{wrapMixed as a}from"./learn-render.js";export async function fetchLearnProfile(a){if(a.toUpperCase().endsWith("DEMO01"))return{kind:"demo"};try{const r=await t(`${e.replace(/\/$/,"")}/api/icoa/learn/profile`,{method:"POST",headers:{"Content-Type":"application/json","X-ICOA-Client":s()},body:JSON.stringify({token:a.toUpperCase()}),signal:AbortSignal.timeout(15e3)}),o=await r.json();return o.success&&Array.isArray(o.tokens)?{kind:"ok",profile:o}:"demo"===o.code?{kind:"demo"}:{kind:"error",message:o.message||`HTTP ${r.status}`}}catch{return{kind:"offline"}}}function r(e,t){if(!e)return t?"还没开始":"not started";const s=new Date(e);if(Number.isNaN(s.getTime()))return"";const a=e=>new Date(e.getFullYear(),e.getMonth(),e.getDate()).getTime(),r=Math.round((a(new Date)-a(s))/864e5);return r<=0?t?"今天":"today":1===r?t?"昨天":"yesterday":r<30?t?`${r} 天前`:`${r} days ago`:t?"很久以前":"a while back"}export function renderLearnProfile(e,t,s,o="card"){const n=Math.max(40,s),c=e=>console.log(e),i=e=>c(chalk.gray(e)),d=(e,t=" ")=>{for(const s of a(e,n-t.length))c(t+s)};c("");const l=chalk.bold.white(t?"👤 我的 ICOA 档案":"👤 Your ICOA profile")+chalk.gray((e.linked>0?t?` · 🔗 ${e.linked+1} 个 token 已联号`:` · 🔗 ${e.linked+1} tokens linked`:"")+(e.streak_days>0?t?` · 🔥 连续 ${e.streak_days} 天`:` · 🔥 ${e.streak_days}-day streak`:t?" · 今天学一张卡,把连续天数接上":" · one card today starts a streak")+(e.active_days>0?t?` · 共 ${e.active_days} 个学习日`:` · ${e.active_days} active days`:""));if(c(" "+l),c(""),e.ladders.length>0){i(t?" 你的阶梯(每条线上你站在第几级)":" Your ladders — where you stand on each track");const s=Math.max(...e.ladders.map(e=>e.name.length))+2;for(const a of e.ladders){const e=a.steps.some(e=>"done"===e.state||"in"===e.state);let r=!1;const o=a.steps.map(s=>"done"===s.state?(r=!0,chalk.green(`${s.curriculum_id} ✅`)):"in"===s.state?(r=!0,chalk.bold.white(`${s.curriculum_id} ${s.cards_done}/${s.total_cards}`)):!r&&e?chalk.gray(`${s.curriculum_id}`):chalk.gray(`${s.curriculum_id} (${"soon"===s.state?t?"即将上线":"soon":t?"下一级":"next"})`));d(chalk.white(a.name.padEnd(s))+chalk.gray("· ")+o.join(chalk.gray(" → "))," ")}c("")}const u=Math.max(...e.tokens.map(e=>e.curriculum_id.length),8)+2;for(const s of e.tokens){const e=[];if(e.push(t?`${s.cards_done}/${s.total_cards} 张卡`:`${s.cards_done}/${s.total_cards} cards`),s.contests.total>0){const a=s.contests.carried>0?` (${s.contests.carried} ✅↗)`:"";e.push(t?`⚔ 赛场 ${s.contests.passed}/${s.contests.total}${a}`:`⚔ ${s.contests.passed}/${s.contests.total} contests${a}`)}s.practice.total>0&&e.push(t?`练习 ${s.practice.ac}/${s.practice.total}`:`practice ${s.practice.ac}/${s.practice.total}`),s.research_cards>0&&e.push(t?`⭐ 研究 ${s.research_cards} 卡`:`⭐ research ${s.research_cards} cards`),e.push(r(s.last_seen,t)),d(chalk.white(s.curriculum_id.padEnd(u))+chalk.gray(e.join(" · "))," ")}c("");const $=e.medals,p=(e,t)=>c(chalk.gray(" "+e.padEnd(11))+t);if(p(t?"🏅 奖牌":"🏅 medals",chalk.white(`${$.gold} 🥇 ${$.silver} 🥈 ${$.bronze} 🥉`)+($.gold+$.silver+$.bronze===0?chalk.gray(t?" /ai 研究 + /submit 可上榜":" earn them: /ai research then /submit"):"")),e.focus.sessions>0&&e.focus.last){const s=e.focus.last;p(t?"⏱ 专注":"⏱ focus",chalk.white(t?`${e.focus.sessions} 场 · 共 ${e.focus.total_min} 分钟`:`${e.focus.sessions} sessions · ${e.focus.total_min} min total`)+chalk.gray(t?` · 最近:${s.planned_min} 分钟 · ${s.cards_viewed} 卡 · ${s.checks_correct}/${s.checks_answered} ✓ · ${r(s.started_at,t)}`:` · last: ${s.planned_min} min · ${s.cards_viewed} cards · ${s.checks_correct}/${s.checks_answered} ✓ · ${r(s.started_at,t)}`))}else p(t?"⏱ 专注":"⏱ focus",chalk.gray(t?"还没有 · 试试 ":"none yet · try ")+chalk.bold.green("focus 30"));p(t?"💬 回复":"💬 replies",e.replies_unread>0?chalk.bold.cyan(t?`${e.replies_unread} 条新回复 → `:`${e.replies_unread} new → `)+chalk.bold.green("r"):chalk.gray(t?"没有新回复":"nothing new")),c(""),i("card"===o?t?" ↩ 回到卡片:ok":" ↩ back to the card: ok":t?" ▸ 贴上 token 继续学习":" ▸ paste your token to continue learning"),c("")}
|
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 t}from"./config.js";import{icoaBannerLines as l}from"./banner.js";import{renderCharts as r,detectCaps as a}from"./render-card.js";import{replyChipLabel as s}from"./learn-replies.js";function c(){return(t().language||"en").toLowerCase()}let g="normal";export function resolveLearnWidth(o){return"normal"===o.learnWidth||"wide"===o.learnWidth||"wider"===o.learnWidth?o.learnWidth:o.learnWide?"wide":"normal"}export function setLearnWidth(o){g=o}export function learnWidthCap(o=g){return"wider"===o?120:"wide"===o?96:64}export function learnW(){if("normal"===g)return 64;const o=process.stdout.columns||64;return Math.max(64,Math.min(o-6,learnWidthCap()))}const i=learnW;function d(o="─"){return o.repeat(i()+2)}function y(o,e,n=20){const t=e>0?o/e:0,l=Math.floor(t*n),r=n-l;return`${chalk.green("█".repeat(l))+chalk.gray("░".repeat(r))} ${(100*t).toFixed(1)}%`}function u(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,t){const l=c(),r=e.cardsCompleted.length,a=o.totalCards,s=a-r,g=o.modules.find(o=>e.currentCard>=o.cardRange[0]&&e.currentCard<=o.cardRange[1]),i=/demo/i.test(o.id);if(console.log(),console.log(chalk.cyan(" ╭"+d("═"))),console.log(chalk.cyan(" ║")),console.log(chalk.cyan(" ║ ")+chalk.bold.white(" "+(o.name?.trim()||n("academy_title",l)))),console.log(chalk.cyan(" ║")),t)console.log(chalk.cyan(" ║ ")+chalk.white(" "+n(i?"welcome_new_demo":"welcome_new",l)));else{const o=new Date(e.lastSeenAt),t=Math.floor((Date.now()-o.getTime())/36e5),r=t<1?n("just_now",l):t<24?`${t}${n("ago_hours",l)}`:`${Math.floor(t/24)}${n("ago_days",l)}`;console.log(chalk.cyan(" ║ ")+chalk.white(` ${n("welcome_back",l)} ${r}`))}console.log(chalk.cyan(" ║"));const h=g?`${g.number}. ${g.name}`:"—";console.log(chalk.cyan(" ║ ")+" "+chalk.gray(n("module",l).padEnd(11))+chalk.white(h)),console.log(chalk.cyan(" ║ ")+" "+chalk.gray(n("progress",l).padEnd(11))+y(r,a)+chalk.gray(` (${r}/${a})`)),console.log(chalk.cyan(" ║ ")+" "+chalk.gray(n("streak",l).padEnd(11))+u(e.streakDays)),s>0&&e.currentCard<=a&&console.log(chalk.cyan(" ║ ")+" "+chalk.gray(n("next_card",l).padEnd(11))+chalk.white(`#${e.currentCard} ${n("of",l)} ${a}`)),console.log(chalk.cyan(" ║")),console.log(chalk.cyan(" ╰"+d("═"))),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────"));const p=0===r?n("continue_start",l):`${n("continue_resume",l)} ${e.currentCard}`;console.log(chalk.bold.green(" continue")+chalk.gray(" "+p)),console.log(chalk.yellow(" status")+chalk.gray(" "+n("status_full",l))),e.bookmarks.length>0&&console.log(chalk.yellow(" bookmarks")+chalk.gray(` ${e.bookmarks.length} ${n("bookmarks_desc",l)}`)),console.log(chalk.gray(" quit")+chalk.gray(" "+n("quit_desc",l))),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log()}function h(o,e){const t=c(),l=o.module,r=e.modules.find(o=>o.number===l),a=r?r.name:n("unknown_module",t),s=n("module",t).replace(":","").trim(),g=`${n("card",t)} ${o.number} / ${e.totalCards} · ${s} ${l} · ${a}`;console.log(),console.log(chalk.cyan(" ╭─ ")+chalk.bold.white(g)+" "+chalk.cyan("─".repeat(Math.max(0,i()-g.length-4))))}function p(){console.log(chalk.cyan(" ╰"+d())),console.log()}function m(o){console.log(chalk.cyan(" │ ")+chalk.white(o))}function b(){console.log(chalk.cyan(" │"))}const f=/[┌┐└┘├┤┬┴┼─│╔╗╚╝═║╠╣╦╩╬▓▒░█]/;function $(o){return/^(?: {4}|\t)/.test(o)||f.test(o)||/ {3,}/.test(o)}export function wrapMixed(e,n){const t=[];let l="",r=0,a=!1;for(const s of function(e){const n=[],t=e=>{let t="";const l=()=>{t&&(n.push({t:"atom",s:t}),t="")};for(const r of e)" "===r?(l(),n.push({t:"space"})):2===o(r)?(l(),n.push({t:"wide",s:r})):t+=r;l()},l=/`[^`]*`/g;let r,a=0;for(;null!==(r=l.exec(e));)t(e.slice(a,r.index)),n.push({t:"code",s:r[0]}),a=r.index+r[0].length;return t(e.slice(a)),n}(e)){if("space"===s.t){""!==l&&(a=!0);continue}const e=s.s,c=o("code"===s.t?e.replace(/`/g,""):e),g=a?1:0,i="atom"===s.t&&e.length<=2&&/^[.,;:!?)\]}…'"]+$/.test(e);""!==l&&!i&&r+g+c>n?(t.push(l),l=e,r=c,a=!1):(a&&(l+=" ",r+=1,a=!1),l+=e,r+=c)}return l&&t.push(l),t}const w=/\b(?:Common bug|Common mistake|Common error|Pitfall|Gotcha|Footgun)\b[::]?|\b(?:Warning|Caution)\b[::]|(?:常见错误|常见陷阱|常见误区|陷阱|注意|警告|误区)[::]/gi;function k(o,e){const n=e?chalk.bold.white:chalk.white;let t,l="",r=0;for(w.lastIndex=0;null!==(t=w.exec(o));)t.index>r&&(l+=n(o.slice(r,t.index))),l+=chalk.bold.yellow(t[0]),r=t.index+t[0].length;return r<o.length&&(l+=n(o.slice(r))),l}function _(o,e=!1){let n="",t=0;const l=/`([^`]*)`/g;let r;for(;null!==(r=l.exec(o));)r.index>t&&(n+=k(o.slice(t,r.index),e)),n+=chalk.cyan(r[1]),t=r.index+r[0].length;return t<o.length&&(n+=k(o.slice(t),e)),n}function x(o,e=!1){let n=!1;for(const t of o.split("\n"))if(/^\s*```/.test(t))n=!n;else if(n)console.log(chalk.cyan(" │ ")+chalk.cyan(` ${t}`));else if(""!==t)if($(t))console.log(chalk.cyan(" │ ")+chalk.white(t));else for(const o of wrapMixed(t,i()-4))console.log(chalk.cyan(" │ ")+` ${_(o,e)}`);else b()}function C(o,e){const n=[];for(const t of o.split("\n")){if(""===t){n.push("");continue}if($(t)){n.push(t);continue}let o="";for(const l of t.split(" "))(o+" "+l).trim().length>e?(n.push(o.trim()),o=l):o=(o+" "+l).trim();o&&n.push(o)}return n}let v=!1;export function setLearnAiEnabled(o){v=o}let M=new Set;export function setLearnPqsViewCards(o){M=o}let W=new Map;export function setLearnHonorRanks(o){W=o}const z=["①","②","③","④","⑤","⑥","⑦","⑧","⑨","⑩"];let j=[];export function setLearnReplies(o){j=o}let q=[];export function setLearnFaqs(o){q=o}let A=null;export function setLearnHometask(o){A=o}let S="full";export function setLearnFooterProfile(o){S=o}let P=null;export function setLearnPracticeChips(o){P=o}let L=null;export function setLearnPracticeCount(o){L=o}export function renderHonorWall(o){const e=c().startsWith("zh"),{totals:n,chapters:t}=o,l=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===l){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 a=t.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(" "+a);const s=t.flatMap(o=>o.cards.map(e=>({...e,chapter:o.chapter})));s.sort((o,e)=>o.rank-e.rank||o.chapter-e.chapter||o.card-e.card);for(const o of s.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}`):""))}s.length>24&&console.log(chalk.gray(e?` …还有 ${s.length-24} 枚`:` …and ${s.length-24} more`)),console.log(chalk.gray(" ─────────────────────────────────────────"))}export function renderKnowledgeCard(t,l){const d=c(),y=e(t,d);h(y,l),b();const u=wrapMixed(y.title,i()-4);for(const o of u)m(chalk.bold.yellow(o));m(chalk.gray("─".repeat(Math.min(Math.max(...u.map(e=>o(e))),i()))));const f=!!(t.chart_ids&&t.chart_ids.length>0);if(f&&r(t.chart_ids.slice(0,1),l.charts,a()),y.key_point&&""!==y.key_point.trim()?(f||b(),function(o,e,n){const t=c().startsWith("zh"),l=a().cols,r="-".repeat(Math.min(i(),Math.max(24,l-6))),s=t?"要点":"KEY POINT",g=t?`第 ${e} / ${n} 张`:`Card ${e} / ${n}`;console.log(chalk.cyan(" │ ")+chalk.green(r)),console.log(chalk.cyan(" │ ")+chalk.green(`${s} · ${g}`));for(const e of wrapMixed(o,i()-4))console.log(chalk.cyan(" │ ")+chalk.bold.green(" "+e));console.log(chalk.cyan(" │ ")+chalk.green(r)),b()}(y.key_point,y.number,l.totalCards)):f||b(),y.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){x(n[1],!0);const o=n[2]?.trimStart();o&&x(o,!1)}else x(o,!1);b()}),y.icoaConnection){m(chalk.magenta(" "+n("icoa_connection",d))),m(chalk.gray(" "+"─".repeat(20)));for(const o of wrapMixed(y.icoaConnection,i()-4))m(" "+chalk.magenta(o));b()}if(t.check){const o=d.startsWith("zh"),e=t._zh?.checkStatement??t._zh?.check?.statement,n=o?e??t.check.statement:t.check.statement;m(chalk.cyan(" 🤔 "+(o?"请回答 (输 y 或 n):":"Quick comprehension check (type y or n):"))),m(chalk.gray(" "+"─".repeat(20)));for(const o of wrapMixed(n,i()-4))m(" "+chalk.white(o));b()}p(),function(e,n){const t=c().startsWith("zh"),l=a().cols,r="-".repeat(Math.min(i(),Math.max(24,l-6))),d=chalk.gray(" ");if("minimal"===S){const o=P?P(e.number):null;console.log(chalk.gray(" "+r)),console.log(chalk.gray(" "+(t?`第 ${e.number} / ${n.totalCards} 张`:`Card ${e.number} / ${n.totalCards}`))+(o?chalk.gray(t?" · 本卡练习 ":" · practice ")+o:""));const l=[];if(e.check?(l.push(chalk.bold.green("[y]")+chalk.gray(t?" 是":" yes")),l.push(chalk.bold.green("[n]")+chalk.gray(t?" 否":" no"))):l.push(chalk.bold.green("[ok]")+chalk.gray(t?" 下一张":" next")),o){const e=Math.max(1,(o.match(/\[\d\]/g)??[]).length),n=Array.from({length:e},(o,e)=>`[${e+1}]`).join("");l.push(chalk.bold.cyan(n)+chalk.gray(t?" 练习":" practice"))}return l.push(chalk.bold.cyan("[b]")+chalk.gray(t?" 上一张":" back")),l.push(chalk.bold.yellow("[e]")+chalk.gray(t?" 求助":" help")),l.push(chalk.gray("[quit]")+(t?chalk.gray(" 退出"):"")),console.log(" "+l.join(d)),void console.log()}const y=[];e.check?(y.push(chalk.bold.green("[y]")+chalk.gray(t?" 是":" yes")),y.push(chalk.bold.green("[n]")+chalk.gray(t?" 否":" no"))):y.push(chalk.bold.green("[ok]")+chalk.gray(t?" 继续":" next")),v&&(y.push(chalk.bold.magenta("[/ai]")+chalk.gray(t?" 🐼 熊猫导师":" 🐼 Panda tutor")),y.push(chalk.bold.magenta("[/submit]")+chalk.gray(t?" 提问评分":" prompt score")));const u=W.get(e.number);var h;y.push(chalk.bold.magenta(u?`[/honors ${h=u,z[h-1]??`#${h}`}]`:"[/honors]")+chalk.gray(t?" 排行":" board")),"normal"===g?y.push(chalk.bold.cyan("[wide]")+chalk.gray(t?" 宽屏":" widen")):(y.push("wide"===g?chalk.bold.cyan("[wider]")+chalk.gray(t?" 更宽":" wider"):chalk.bold.cyan("[wide]")+chalk.gray(t?" 回宽屏":" back to wide")),y.push(chalk.bold.cyan("[normal]")+chalk.gray(t?" 64列":" 64-col")));const p=s(j,q,e.number,t),m=p.startsWith("💬");if(y.push((m?chalk.bold.yellow("[r]"):chalk.gray("[r]"))+(m?chalk.bold.white(` ${p}`):chalk.gray(` ${p}`))),y.push(chalk.bold.yellow("[e]")+chalk.gray(t?" 提问或报告":" ask or report")),A){const o=A,e=`${o.done}/${o.total}`,n=o.completedAt?chalk.bold.green:chalk.bold.cyan;y.push(n("[task]")+chalk.gray(t?` 本周 ${e}`:` week ${e}`))}y.push(chalk.bold.cyan("[b]")+chalk.gray(t?" 上一张":" back")),y.push(chalk.bold.cyan("[card N]")+chalk.gray(t?" 跳转":" jump")),y.push(chalk.gray("[reload]")+(t?chalk.gray(" 刷新"):"")),y.push(chalk.gray("[quit]")+(t?chalk.gray(" 退出"):""));const b=o(r);console.log(chalk.gray(" "+r)),console.log(chalk.gray(" "+(t?`第 ${e.number} / ${n.totalCards} 张`:`Card ${e.number} / ${n.totalCards}`)));let f="",$=0;for(const e of y){const n=o(e);f&&$+2+n>b?(console.log(" "+f),f=e,$=n):(f=f?f+d+e:e,$=f===e?n:$+2+n)}f&&console.log(" "+f),console.log(" "+chalk.magenta("✍ ")+chalk.magenta("[/py]")+chalk.gray(t?" Python 草稿本":" Python scratchpad")+d+chalk.magenta("[/pylist]")+chalk.gray(t?" 我的草稿":" my files")),console.log(chalk.gray(" "+r));const w=[t?"e = 就这张卡提问,或报告问题":"e = ask about this card, or report a problem",t?"更多: bookmark / unbookmark · status · lang zh/en · link":"more: bookmark / unbookmark · status · lang zh/en · link"];for(const o of w)for(const e of wrapMixed(o,b))console.log(chalk.gray(" "+e));console.log()}(t,l)}export function renderCheckFeedback(o,e,n,t=!1){const l=c().startsWith("zh");if(console.log(),n)console.log(chalk.green(" ✓ ")+chalk.white(l?"答对了。":"Correct."));else if(console.log(chalk.yellow(" ✗ ")+chalk.white(l?"记下了,继续。":"Noted — moving on.")),t){const e="y"===o.check?.answer?l?"是":"Yes":l?"否":"No";console.log(chalk.gray(" "+(l?"正确答案: ":"Answer: ")+e+". "+(l?"回看上面那段。":"Re-read the paragraph above.")))}const r=l?o._zh?.solution??o.solution:o.solution;if(r&&r.length>0){console.log(),console.log(chalk.cyan(" 📝 "+(l?"解答:":"Worked solution:")));for(const o of r){for(const e of wrapMixed(o,i()-4))console.log(chalk.white(" "+e));console.log()}}console.log()}function R(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,t,r,a){const s=c().startsWith("zh"),[g,d]=n.cardRange,y=e.checkResults||{};let u=0,h=0;for(let o=g;o<=d;o++){const e=y[String(o)];e&&(u++,e.correct&&h++)}const p="-".repeat(48);console.log();for(const o of l(" "))console.log(o);console.log(),console.log(chalk.bold.yellow(" "+p)),console.log(chalk.bold.yellow(" "+(s?`阶段 ${n.number} 完成`:`Phase ${n.number} complete`))+chalk.gray(" · ")+chalk.white(n.name));const m=u>0&&h===u?chalk.bold.green:chalk.bold.white;console.log(chalk.gray(" "+(s?"理解检查得分: ":"comprehension score: "))+m(`${h}/${u}`)+chalk.gray(0===u?s?" (本阶段未作答 y/n)":" (no y/n answered)":""));const b=String(d).length;let f=!1,$=0,w=0,k=0,_=0;for(let e=g;e<=d;e++){const n=o.cards.find(o=>o.number===e);if(!n)continue;const t=n,l=t.title??t.badge??"",r=(s?t._zh?.title??t._zh?.badge??l:l)||"",a=null!=n.check,c=y[String(e)];let g;g=c?c.correct?chalk.green("✓"):chalk.red("✗"):a?chalk.gray("○"):chalk.gray("·");const d=L?L(e):null;d&&(f=!0);const u=d?(d.solved>=d.total?chalk.green:d.solved>0?chalk.yellow:chalk.gray)(` ⚔${d.solved}/${d.total}`):"";d&&($++,d.solved>=d.total&&w++,k+=d.solved,_+=d.total);const h=wrapMixed(r,i()-10-b-(d?6:0)),p=h[0]+(h.length>1?"…":"");console.log(chalk.gray(` ${String(e).padStart(b)} `)+g+u+chalk.white(" "+p))}if(console.log(chalk.gray(s?" ✓ 答对 · ✗ 答错 · ○ 未作答 · 无 y/n 的卡不计分":" ✓ correct · ✗ wrong · ○ unanswered · cards without a y/n are unscored")),f&&console.log(chalk.bold.cyan(" ⚔ "+(s?"卡片练习: ":"practice: "))+(w>=$?chalk.bold.green:chalk.bold.white)(`${w}/${$}`)+chalk.gray(s?` 卡全完成 · ${k}/${_} 题 AC · 行内 ⚔已过/共(绿 完成 · 黄 部分 · 灰 未碰)`:` cards complete · ${k}/${_} problems AC · ⚔solved/total per row (green done · yellow partial · gray untouched)`)),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(" ⭐ "+(s?"提问得分 (best): ":"Prompt scores (best): "))+(0===r.length?chalk.gray(s?"本阶段尚未提问 — 用 /ai 研究,再 /submit 评分":"none this phase — research with /ai, then /submit"):r.map(e=>chalk.gray((s?"卡":"#")+e.card+" ")+o(e.tier)(String(e.score))).join(chalk.gray(" · "))))}if(a&&!a.configured){const o=d-g+1>12;console.log(chalk.gray(o?s?" 🔒 本阶段通关标准制作中 — 很快更新(360 层第二阶段标准设计中)":" 🔒 Pass criteria for this phase are in production — coming soon (360-tier second-stage standard in design)":s?" 🔒 本阶段通关标准制作中 — 很快更新(标准范式:3 场练习赛 + 3 卡研究)":" 🔒 Pass criteria for this phase are in production — coming soon (standard: 3 arenas + 3 research cards)"))}if(a?.configured&&a.contest){const o=a.contest;if(o.slots&&o.slots.length>0)for(const e of o.slots){const o=chalk.bold.cyan(` ⚔ ${s?"练习赛":"arena"} ${n.number}-${e.slot}: `);e.passed?console.log(o+chalk.bold.green(`${e.best_ac}/${e.n} AC ✅`)):0===e.attempts?console.log(o+chalk.white(s?`${e.n} 题 · 过 ${e.need} 题 — `:`${e.n} problems · AC ${e.need} — `)+chalk.bold.green(`contest ${e.slot}`)):console.log(o+chalk.yellow(s?`最好 ${e.best_ac}/${e.n}(需 ${e.need})· 已试 ${e.attempts} 次 — `:`best ${e.best_ac}/${e.n} (need ${e.need}) · ${e.attempts} attempt(s) — `)+chalk.bold.green(`contest ${e.slot}`))}else{const e=chalk.bold.cyan(" ⚖ "+(s?"判分赛: ":"Contest: "));o.passed?console.log(e+chalk.bold.green(`${o.best_ac}/${o.n} AC ✅ `+(s?"已过关":"passed"))):0===o.attempts?console.log(e+chalk.white(s?`${o.n} 题 · 过 ${o.need} 题解锁 — 输入 `:`${o.n} problems · AC ${o.need} to pass — type `)+chalk.bold.green("contest")+chalk.white(s?" 开始":" to start")):console.log(e+chalk.yellow(s?`最好 ${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(s?" 再战":" to retry"))}}t&&console.log(chalk.gray(" "+(s?"下一阶段: ":"next: "))+chalk.bold.cyan(s?`阶段 ${t.number}`:`Phase ${t.number}`)+chalk.gray(" · ")+chalk.white(t.name)),console.log(chalk.bold.yellow(" "+p));for(const o of R(s))console.log(o)}export function renderCurriculumComplete(o,e){const n=c().startsWith("zh"),t=e.checkResults||{},r="-".repeat(48);let a=0,s=0;console.log();for(const o of l(" "))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,l]=e.cardRange;let r=0,c=0;for(let e=o;e<=l;e++){const o=t[String(e)];o&&(r++,o.correct&&c++)}a+=r,s+=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(`${s}/${a}`)),console.log(chalk.bold.green(" "+r));for(const o of R(n))console.log(o);console.log()}export function renderMCQCard(o,t){const l=c(),r=e(o,l);h(r,t),b(),m(chalk.bold.yellow("🎯 "+r.title)),m(chalk.gray("─".repeat(Math.min(r.title.length+4,i())))),b();for(const o of C(r.question,i()-4))m(" "+o);b();for(const o of["A","B","C","D"])m(chalk.cyan(` ${o}.`)+" "+chalk.white(r.options[o]));b(),p(),console.log(chalk.gray(" "+(l.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",l))),console.log(chalk.gray(" ")+chalk.bold.cyan("back")+chalk.gray(l.startsWith("zh")?" 上一张 · ":" previous card · ")+chalk.bold.cyan("card N")+chalk.gray(l.startsWith("zh")?" 跳转":" jump")),console.log()}export function renderMCQFeedback(o,t,l,r){const a=c(),s=e(o,a);console.log(),l?console.log(" "+chalk.bold.green(n("correct",a)+" ")+chalk.gray(`${n("one_point",a)} · ${t} = ${s.options[t]}`)):console.log(" "+chalk.bold.red(n("not_quite",a)+" ")+chalk.gray(`${n("you_chose",a)} ${t}; ${n("answer_is",a)} ${s.answer}.`)),console.log(),console.log(chalk.gray(" "+n("explanation",a)));for(const o of C(s.explanation,i()-4))console.log(chalk.gray(" "+o));const g=Object.values(r.mcqResults),d=g.filter(o=>o.correct).length;console.log(),console.log(chalk.gray(" "+n("mcq_accuracy_so_far",a)+" ")+chalk.white(`${d}/${g.length}`)),console.log(),l||(console.log(chalk.gray(" "+n("press_ok_continue",a)+" ")+chalk.bold.green("ok")+chalk.gray(" "+n("to_continue",a))),console.log())}export function renderPracticalCard(o,t){const l=c(),r=e(o,l);h(r,t),b(),m(chalk.bold.yellow("🛠 "+r.title)),m(chalk.gray("─".repeat(Math.min(r.title.length+4,i())))),b();for(const o of C(r.task,i()-4))m(" "+o);if(r.starterCode){b(),m(chalk.gray(" "+n("starter_code",l))),b();for(const o of r.starterCode.split("\n"))m(" "+chalk.cyan(o))}b(),p(),console.log(chalk.gray(" "+n("try_in_sandbox",l)+" ")+chalk.bold.cyan("!python3")+chalk.gray(" "+n("drops_into_python",l))),console.log(chalk.gray(" "+n("when_done",l))),console.log(chalk.gray(" ")+chalk.bold.green("done")+chalk.gray(" "+n("done_desc",l))),console.log(chalk.gray(" ")+chalk.bold.yellow("skip")+chalk.gray(" "+n("skip_desc",l))),console.log(chalk.gray(" back previous card")),console.log(chalk.gray(" card N jump to a card by number")),console.log()}export function renderPracticalSuccess(o){const t=c(),l=e(o,t);console.log(),console.log(" "+chalk.bold.green(n("practical_recorded",t))),console.log(),console.log(chalk.gray(" "+n("reference_answer",t)));for(const o of C(l.successHint,i()-4))console.log(chalk.gray(" "+o));console.log(),console.log(chalk.gray(" "+n("press_ok_continue",t)+" ")+chalk.bold.green("ok")+chalk.gray(" "+n("to_continue",t))),console.log()}export function renderSimDemoCard(o,t){const l=c(),r=e(o,l);h(r,t),b(),m(chalk.bold.yellow("🎬 "+r.title)),b();for(const o of C(r.description,i()-4))m(" "+o);b(),m(chalk.gray(" "+n("sim_requires",l))),b(),p(),console.log(chalk.gray(" ")+chalk.bold.cyan("sim")+chalk.gray(" "+n("sim_launch_desc",l))),console.log(chalk.gray(" ")+chalk.bold.green("ok")+chalk.gray(" / ")+chalk.bold.green("next")+chalk.gray(" "+n("continue_to_next",l))),console.log(chalk.gray(" bookmark "+n("bookmark_desc",l))),console.log(chalk.gray(" back "+n("back_desc",l))),console.log(chalk.gray(" back previous card")),console.log(chalk.gray(" card N jump to a card by number")),console.log(chalk.gray(" quit "+n("quit_desc",l))),console.log()}export function renderArenaTaskCard(o,n){const t=c(),l=t.startsWith("zh"),r=e(o,t);h(r,n),b(),m(chalk.bold.cyan("🏟 "+r.title)),m(chalk.gray(" "+r.origin+" · "+r.domain+" · "+r.metric)),b();for(const o of r.statement){for(const e of C(o,i()-4))m(" "+e);b()}m(chalk.bold.green(l?" 核心要点 — baseline → 解法的关键变化":" CORE DELTA — baseline → solution"));for(const o of r.core_delta)for(const e of C("• "+o,i()-6))m(" "+chalk.white(e));b(),m(chalk.gray((l?" 提交格式: ":" Submission: ")+r.submission_format)),r.hints&&m(chalk.gray(l?" 本题有 5 级提示 (a–e),进入 arena 后逐级解锁。":" 5 progressive hints (a–e) unlock inside the arena.")),b(),p(),console.log(chalk.gray(" ")+chalk.bold.cyan("arena "+r.task_id)+chalk.gray(l?" 动手做这道题(下载 / 提示 / 提交 / 榜单)":" open the arena (download / hints / submit / board)")),console.log(chalk.gray(" ")+chalk.bold.green("ok")+chalk.gray(" / ")+chalk.bold.green("next")+chalk.gray(l?" 继续下一张":" continue to next")),console.log(chalk.gray(" back "+(l?"上一张":"previous"))),console.log(chalk.gray(" back previous card")),console.log(chalk.gray(" card N jump to a card by number")),console.log(chalk.gray(" quit "+(l?"退出":"exit"))),console.log()}export function renderComingSoonCard(o,n){const t=c(),l=t.startsWith("zh"),r=e(o,t);h(r,n),b(),m(chalk.bold.yellow("🔜 "+r.title)),b();const a=r.note||(l?"这道竞技场题目正在编写中,敬请期待。":"This arena task is coming soon.");for(const o of C(a,i()-4))m(" "+chalk.gray(o));b(),p(),console.log(chalk.gray(" ")+chalk.bold.green("ok")+chalk.gray(" / ")+chalk.bold.green("next")+chalk.gray(l?" 继续":" continue")),console.log(chalk.gray(" back previous card")),console.log(chalk.gray(" card N jump to a card by number")),console.log(chalk.gray(" quit "+(l?"退出":"exit"))),console.log()}export function renderMilestone(o,t){const l=c(),r=e(o,l),a=o.number===t.totalCards;console.log(),console.log(chalk.bold.yellow(" ╭"+d("═"))),console.log(chalk.bold.yellow(" ║")),console.log(chalk.bold.yellow(" ║ ")+chalk.white(" "+n("milestone_header",l))),console.log(chalk.bold.yellow(" ║"));const s=` ${r.badge} ${r.emoji}`;console.log(chalk.bold.yellow(" ║ ")+chalk.bold.green(s)),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",l)));for(const o of C(r.realWorldLevel,i()-4))console.log(chalk.bold.yellow(" ║ ")+" "+chalk.white(o));console.log(chalk.bold.yellow(" ║")),console.log(chalk.bold.yellow(" ║ ")+" "+chalk.gray(n("whats_next",l)));for(const o of C(r.unlockedNext,i()-4))console.log(chalk.bold.yellow(" ║ ")+" "+chalk.white(o));console.log(chalk.bold.yellow(" ║")),console.log(chalk.bold.yellow(" ╰"+d("═"))),console.log(),a?(console.log(chalk.gray(" "+n("demo_complete",l))),console.log(),console.log(chalk.gray(" "+n("unlock_full",l)+" ")+chalk.white(n("curriculum_name",l))+chalk.gray(",")),console.log(chalk.gray(" "+n("contact_team_leader",l)+" ")+chalk.bold.yellow("EA")+chalk.gray(" "+n("learn_token",l))),console.log(chalk.gray(" "+n("or_email",l)+" ")+chalk.cyan("australia@icoa2026.au")+chalk.gray(" "+n("for_partnership",l))),console.log(),console.log(chalk.gray(" "+n("type_quit",l)+" ")+chalk.bold.green("quit")+chalk.gray(" "+n("to_exit",l)+" ")+chalk.bold.green("status")+chalk.gray(" "+n("for_dashboard",l)))):(console.log(chalk.gray(" ")+chalk.bold.green("ok")+chalk.gray(" / ")+chalk.bold.green("next")+chalk.gray(" "+n("continue_to_next",l))),console.log(chalk.gray(" status "+n("status_full",l))),console.log(chalk.gray(" back previous card")),console.log(chalk.gray(" card N jump to a card by number")),console.log(chalk.gray(" quit "+n("quit_desc",l)))),console.log()}export function renderStatus(o,e){const t=c(),l=e.cardsCompleted.length,r=o.totalCards,a=Object.values(e.mcqResults),s=a.filter(o=>o.correct).length;console.log(),console.log(chalk.cyan(" ╭─ ")+chalk.bold.white(n("status_title",t))+" "+chalk.cyan("─".repeat(25))),b(),m(" "+chalk.gray(n("total_progress",t)+" ")+y(l,r)+chalk.gray(` (${l}/${r})`)),m(" "+chalk.gray(n("streak",t)+" ")+u(e.streakDays)+chalk.gray(` (${n("longest",t)} ${e.longestStreak})`)),m(" "+chalk.gray(n("mcq_accuracy",t)+" ")+chalk.white(`${s}/${a.length}`)),m(" "+chalk.gray(n("practicals_done",t)+" ")+chalk.white(`${e.practicalsCompleted.length}`)),m(" "+chalk.gray(n("bookmarked",t)+" ")+chalk.white(`${e.bookmarks.length}`)),b();const g=n("module",t).replace(":","").trim();for(const n of o.modules){const[o,t]=n.cardRange,l=e.cardsCompleted.filter(e=>e>=o&&e<=t).length,r=t-o+1;m(" "+(l===r?chalk.green("✓"):l>0?chalk.yellow("▶"):chalk.gray("□"))+" "+chalk.gray(`${g} ${n.number}: `)+chalk.white(`${l}/${r}`)+chalk.gray(" "+n.name))}if(b(),e.achievements.length>0){m(" "+chalk.gray(n("achievements",t)));for(const o of e.achievements)m(" "+chalk.bold.yellow("★ ")+chalk.white(o))}else m(" "+chalk.gray(n("achievements_none",t)));b(),p()}
|
|
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 t}from"./config.js";import{icoaBannerLines as l}from"./banner.js";import{renderCharts as r,detectCaps as a}from"./render-card.js";import{replyChipLabel as s}from"./learn-replies.js";function c(){return(t().language||"en").toLowerCase()}let g="normal";export function resolveLearnWidth(o){return"normal"===o.learnWidth||"wide"===o.learnWidth||"wider"===o.learnWidth?o.learnWidth:o.learnWide?"wide":"normal"}export function setLearnWidth(o){g=o}export function learnWidthCap(o=g){return"wider"===o?120:"wide"===o?96:64}export function learnW(){if("normal"===g)return 64;const o=process.stdout.columns||64;return Math.max(64,Math.min(o-6,learnWidthCap()))}const i=learnW;function d(o="─"){return o.repeat(i()+2)}function y(o,e,n=20){const t=e>0?o/e:0,l=Math.floor(t*n),r=n-l;return`${chalk.green("█".repeat(l))+chalk.gray("░".repeat(r))} ${(100*t).toFixed(1)}%`}function u(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,t){const l=c(),r=e.cardsCompleted.length,a=o.totalCards,s=a-r,g=o.modules.find(o=>e.currentCard>=o.cardRange[0]&&e.currentCard<=o.cardRange[1]),i=/demo/i.test(o.id);if(console.log(),console.log(chalk.cyan(" ╭"+d("═"))),console.log(chalk.cyan(" ║")),console.log(chalk.cyan(" ║ ")+chalk.bold.white(" "+(o.name?.trim()||n("academy_title",l)))),console.log(chalk.cyan(" ║")),t)console.log(chalk.cyan(" ║ ")+chalk.white(" "+n(i?"welcome_new_demo":"welcome_new",l)));else{const o=new Date(e.lastSeenAt),t=Math.floor((Date.now()-o.getTime())/36e5),r=t<1?n("just_now",l):t<24?`${t}${n("ago_hours",l)}`:`${Math.floor(t/24)}${n("ago_days",l)}`;console.log(chalk.cyan(" ║ ")+chalk.white(` ${n("welcome_back",l)} ${r}`))}console.log(chalk.cyan(" ║"));const h=g?`${g.number}. ${g.name}`:"—";console.log(chalk.cyan(" ║ ")+" "+chalk.gray(n("module",l).padEnd(11))+chalk.white(h)),console.log(chalk.cyan(" ║ ")+" "+chalk.gray(n("progress",l).padEnd(11))+y(r,a)+chalk.gray(` (${r}/${a})`)),console.log(chalk.cyan(" ║ ")+" "+chalk.gray(n("streak",l).padEnd(11))+u(e.streakDays)),s>0&&e.currentCard<=a&&console.log(chalk.cyan(" ║ ")+" "+chalk.gray(n("next_card",l).padEnd(11))+chalk.white(`#${e.currentCard} ${n("of",l)} ${a}`)),console.log(chalk.cyan(" ║")),console.log(chalk.cyan(" ╰"+d("═"))),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────"));const p=0===r?n("continue_start",l):`${n("continue_resume",l)} ${e.currentCard}`;console.log(chalk.bold.green(" continue")+chalk.gray(" "+p)),console.log(chalk.yellow(" status")+chalk.gray(" "+n("status_full",l))),e.bookmarks.length>0&&console.log(chalk.yellow(" bookmarks")+chalk.gray(` ${e.bookmarks.length} ${n("bookmarks_desc",l)}`)),console.log(chalk.gray(" quit")+chalk.gray(" "+n("quit_desc",l))),console.log(chalk.gray(" ─────────────────────────────────────────────")),console.log()}function h(o,e){const t=c(),l=o.module,r=e.modules.find(o=>o.number===l),a=r?r.name:n("unknown_module",t),s=n("module",t).replace(":","").trim(),g=`${n("card",t)} ${o.number} / ${e.totalCards} · ${s} ${l} · ${a}`;console.log(),console.log(chalk.cyan(" ╭─ ")+chalk.bold.white(g)+" "+chalk.cyan("─".repeat(Math.max(0,i()-g.length-4))))}function p(){console.log(chalk.cyan(" ╰"+d())),console.log()}function m(o){console.log(chalk.cyan(" │ ")+chalk.white(o))}function b(){console.log(chalk.cyan(" │"))}const f=/[┌┐└┘├┤┬┴┼─│╔╗╚╝═║╠╣╦╩╬▓▒░█]/;function $(o){return/^(?: {4}|\t)/.test(o)||f.test(o)||/ {3,}/.test(o)}export function wrapMixed(e,n){const t=[];let l="",r=0,a=!1;for(const s of function(e){const n=[],t=e=>{let t="";const l=()=>{t&&(n.push({t:"atom",s:t}),t="")};for(const r of e)" "===r?(l(),n.push({t:"space"})):2===o(r)?(l(),n.push({t:"wide",s:r})):t+=r;l()},l=/`[^`]*`/g;let r,a=0;for(;null!==(r=l.exec(e));)t(e.slice(a,r.index)),n.push({t:"code",s:r[0]}),a=r.index+r[0].length;return t(e.slice(a)),n}(e)){if("space"===s.t){""!==l&&(a=!0);continue}const e=s.s,c=o("code"===s.t?e.replace(/`/g,""):e),g=a?1:0,i="atom"===s.t&&e.length<=2&&/^[.,;:!?)\]}…'"]+$/.test(e);""!==l&&!i&&r+g+c>n?(t.push(l),l=e,r=c,a=!1):(a&&(l+=" ",r+=1,a=!1),l+=e,r+=c)}return l&&t.push(l),t}const w=/\b(?:Common bug|Common mistake|Common error|Pitfall|Gotcha|Footgun)\b[::]?|\b(?:Warning|Caution)\b[::]|(?:常见错误|常见陷阱|常见误区|陷阱|注意|警告|误区)[::]/gi;function k(o,e){const n=e?chalk.bold.white:chalk.white;let t,l="",r=0;for(w.lastIndex=0;null!==(t=w.exec(o));)t.index>r&&(l+=n(o.slice(r,t.index))),l+=chalk.bold.yellow(t[0]),r=t.index+t[0].length;return r<o.length&&(l+=n(o.slice(r))),l}function _(o,e=!1){let n="",t=0;const l=/`([^`]*)`/g;let r;for(;null!==(r=l.exec(o));)r.index>t&&(n+=k(o.slice(t,r.index),e)),n+=chalk.cyan(r[1]),t=r.index+r[0].length;return t<o.length&&(n+=k(o.slice(t),e)),n}function x(o,e=!1){let n=!1;for(const t of o.split("\n"))if(/^\s*```/.test(t))n=!n;else if(n)console.log(chalk.cyan(" │ ")+chalk.cyan(` ${t}`));else if(""!==t)if($(t))console.log(chalk.cyan(" │ ")+chalk.white(t));else for(const o of wrapMixed(t,i()-4))console.log(chalk.cyan(" │ ")+` ${_(o,e)}`);else b()}function C(o,e){const n=[];for(const t of o.split("\n")){if(""===t){n.push("");continue}if($(t)){n.push(t);continue}let o="";for(const l of t.split(" "))(o+" "+l).trim().length>e?(n.push(o.trim()),o=l):o=(o+" "+l).trim();o&&n.push(o)}return n}let v=!1;export function setLearnAiEnabled(o){v=o}let M=new Set;export function setLearnPqsViewCards(o){M=o}let W=new Map;export function setLearnHonorRanks(o){W=o}const z=["①","②","③","④","⑤","⑥","⑦","⑧","⑨","⑩"];let j=[];export function setLearnReplies(o){j=o}let q=[];export function setLearnFaqs(o){q=o}let A=null;export function setLearnHometask(o){A=o}let S="full";export function setLearnFooterProfile(o){S=o}let P=null;export function setLearnFooterExtra(o){P=o}function L(){const o=P?.();o&&console.log(chalk.gray(" "+o))}let E=null;export function setLearnPracticeChips(o){E=o}let R=null;export function setLearnPracticeCount(o){R=o}export function renderHonorWall(o){const e=c().startsWith("zh"),{totals:n,chapters:t}=o,l=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===l){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 a=t.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(" "+a);const s=t.flatMap(o=>o.cards.map(e=>({...e,chapter:o.chapter})));s.sort((o,e)=>o.rank-e.rank||o.chapter-e.chapter||o.card-e.card);for(const o of s.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}`):""))}s.length>24&&console.log(chalk.gray(e?` …还有 ${s.length-24} 枚`:` …and ${s.length-24} more`)),console.log(chalk.gray(" ─────────────────────────────────────────"))}export function renderKnowledgeCard(t,l){const d=c(),y=e(t,d);h(y,l),b();const u=wrapMixed(y.title,i()-4);for(const o of u)m(chalk.bold.yellow(o));m(chalk.gray("─".repeat(Math.min(Math.max(...u.map(e=>o(e))),i()))));const f=!!(t.chart_ids&&t.chart_ids.length>0);if(f&&r(t.chart_ids.slice(0,1),l.charts,a()),y.key_point&&""!==y.key_point.trim()?(f||b(),function(o,e,n){const t=c().startsWith("zh"),l=a().cols,r="-".repeat(Math.min(i(),Math.max(24,l-6))),s=t?"要点":"KEY POINT",g=t?`第 ${e} / ${n} 张`:`Card ${e} / ${n}`;console.log(chalk.cyan(" │ ")+chalk.green(r)),console.log(chalk.cyan(" │ ")+chalk.green(`${s} · ${g}`));for(const e of wrapMixed(o,i()-4))console.log(chalk.cyan(" │ ")+chalk.bold.green(" "+e));console.log(chalk.cyan(" │ ")+chalk.green(r)),b()}(y.key_point,y.number,l.totalCards)):f||b(),y.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){x(n[1],!0);const o=n[2]?.trimStart();o&&x(o,!1)}else x(o,!1);b()}),y.icoaConnection){m(chalk.magenta(" "+n("icoa_connection",d))),m(chalk.gray(" "+"─".repeat(20)));for(const o of wrapMixed(y.icoaConnection,i()-4))m(" "+chalk.magenta(o));b()}if(t.check){const o=d.startsWith("zh"),e=t._zh?.checkStatement??t._zh?.check?.statement,n=o?e??t.check.statement:t.check.statement;m(chalk.cyan(" 🤔 "+(o?"请回答 (输 y 或 n):":"Quick comprehension check (type y or n):"))),m(chalk.gray(" "+"─".repeat(20)));for(const o of wrapMixed(n,i()-4))m(" "+chalk.white(o));b()}p(),function(e,n){const t=c().startsWith("zh"),l=a().cols,r="-".repeat(Math.min(i(),Math.max(24,l-6))),d=chalk.gray(" ");if("minimal"===S){const o=E?E(e.number):null;console.log(chalk.gray(" "+r)),console.log(chalk.gray(" "+(t?`第 ${e.number} / ${n.totalCards} 张`:`Card ${e.number} / ${n.totalCards}`))+(o?chalk.gray(t?" · 本卡练习 ":" · practice ")+o:""));const l=[];if(e.check?(l.push(chalk.bold.green("[y]")+chalk.gray(t?" 是":" yes")),l.push(chalk.bold.green("[n]")+chalk.gray(t?" 否":" no"))):l.push(chalk.bold.green("[ok]")+chalk.gray(t?" 下一张":" next")),o){const e=Math.max(1,(o.match(/\[\d\]/g)??[]).length),n=Array.from({length:e},(o,e)=>`[${e+1}]`).join("");l.push(chalk.bold.cyan(n)+chalk.gray(t?" 练习":" practice"))}return l.push(chalk.bold.cyan("[b]")+chalk.gray(t?" 上一张":" back")),l.push(chalk.bold.yellow("[e]")+chalk.gray(t?" 求助":" help")),l.push(chalk.gray("[quit]")+(t?chalk.gray(" 退出"):"")),console.log(" "+l.join(d)),L(),void console.log()}const y=[];e.check?(y.push(chalk.bold.green("[y]")+chalk.gray(t?" 是":" yes")),y.push(chalk.bold.green("[n]")+chalk.gray(t?" 否":" no"))):y.push(chalk.bold.green("[ok]")+chalk.gray(t?" 继续":" next")),v&&(y.push(chalk.bold.magenta("[/ai]")+chalk.gray(t?" 🐼 熊猫导师":" 🐼 Panda tutor")),y.push(chalk.bold.magenta("[/submit]")+chalk.gray(t?" 提问评分":" prompt score")));const u=W.get(e.number);var h;y.push(chalk.bold.magenta(u?`[/honors ${h=u,z[h-1]??`#${h}`}]`:"[/honors]")+chalk.gray(t?" 排行":" board")),"normal"===g?y.push(chalk.bold.cyan("[wide]")+chalk.gray(t?" 宽屏":" widen")):(y.push("wide"===g?chalk.bold.cyan("[wider]")+chalk.gray(t?" 更宽":" wider"):chalk.bold.cyan("[wide]")+chalk.gray(t?" 回宽屏":" back to wide")),y.push(chalk.bold.cyan("[normal]")+chalk.gray(t?" 64列":" 64-col")));const p=s(j,q,e.number,t),m=p.startsWith("💬");if(y.push((m?chalk.bold.yellow("[r]"):chalk.gray("[r]"))+(m?chalk.bold.white(` ${p}`):chalk.gray(` ${p}`))),y.push(chalk.bold.yellow("[e]")+chalk.gray(t?" 提问或报告":" ask or report")),A){const o=A,e=`${o.done}/${o.total}`,n=o.completedAt?chalk.bold.green:chalk.bold.cyan;y.push(n("[task]")+chalk.gray(t?` 本周 ${e}`:` week ${e}`))}y.push(chalk.bold.cyan("[b]")+chalk.gray(t?" 上一张":" back")),y.push(chalk.bold.cyan("[card N]")+chalk.gray(t?" 跳转":" jump")),y.push(chalk.gray("[reload]")+(t?chalk.gray(" 刷新"):"")),y.push(chalk.gray("[quit]")+(t?chalk.gray(" 退出"):""));const b=o(r);console.log(chalk.gray(" "+r)),console.log(chalk.gray(" "+(t?`第 ${e.number} / ${n.totalCards} 张`:`Card ${e.number} / ${n.totalCards}`)));let f="",$=0;for(const e of y){const n=o(e);f&&$+2+n>b?(console.log(" "+f),f=e,$=n):(f=f?f+d+e:e,$=f===e?n:$+2+n)}f&&console.log(" "+f),console.log(" "+chalk.magenta("✍ ")+chalk.magenta("[/py]")+chalk.gray(t?" Python 草稿本":" Python scratchpad")+d+chalk.magenta("[/pylist]")+chalk.gray(t?" 我的草稿":" my files")),console.log(chalk.gray(" "+r));const w=[t?"e = 就这张卡提问,或报告问题":"e = ask about this card, or report a problem",t?"更多: bookmark / unbookmark · status · lang zh/en · link":"more: bookmark / unbookmark · status · lang zh/en · link"];for(const o of w)for(const e of wrapMixed(o,b))console.log(chalk.gray(" "+e));L(),console.log()}(t,l)}export function renderCheckFeedback(o,e,n,t=!1){const l=c().startsWith("zh");if(console.log(),n)console.log(chalk.green(" ✓ ")+chalk.white(l?"答对了。":"Correct."));else if(console.log(chalk.yellow(" ✗ ")+chalk.white(l?"记下了,继续。":"Noted — moving on.")),t){const e="y"===o.check?.answer?l?"是":"Yes":l?"否":"No";console.log(chalk.gray(" "+(l?"正确答案: ":"Answer: ")+e+". "+(l?"回看上面那段。":"Re-read the paragraph above.")))}const r=l?o._zh?.solution??o.solution:o.solution;if(r&&r.length>0){console.log(),console.log(chalk.cyan(" 📝 "+(l?"解答:":"Worked solution:")));for(const o of r){for(const e of wrapMixed(o,i()-4))console.log(chalk.white(" "+e));console.log()}}console.log()}function N(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,t,r,a){const s=c().startsWith("zh"),[g,d]=n.cardRange,y=e.checkResults||{};let u=0,h=0;for(let o=g;o<=d;o++){const e=y[String(o)];e&&(u++,e.correct&&h++)}const p="-".repeat(48);console.log();for(const o of l(" "))console.log(o);console.log(),console.log(chalk.bold.yellow(" "+p)),console.log(chalk.bold.yellow(" "+(s?`阶段 ${n.number} 完成`:`Phase ${n.number} complete`))+chalk.gray(" · ")+chalk.white(n.name));const m=u>0&&h===u?chalk.bold.green:chalk.bold.white;console.log(chalk.gray(" "+(s?"理解检查得分: ":"comprehension score: "))+m(`${h}/${u}`)+chalk.gray(0===u?s?" (本阶段未作答 y/n)":" (no y/n answered)":""));const b=String(d).length;let f=!1,$=0,w=0,k=0,_=0;for(let e=g;e<=d;e++){const n=o.cards.find(o=>o.number===e);if(!n)continue;const t=n,l=t.title??t.badge??"",r=(s?t._zh?.title??t._zh?.badge??l:l)||"",a=null!=n.check,c=y[String(e)];let g;g=c?c.correct?chalk.green("✓"):chalk.red("✗"):a?chalk.gray("○"):chalk.gray("·");const d=R?R(e):null;d&&(f=!0);const u=d?(d.solved>=d.total?chalk.green:d.solved>0?chalk.yellow:chalk.gray)(` ⚔${d.solved}/${d.total}`):"";d&&($++,d.solved>=d.total&&w++,k+=d.solved,_+=d.total);const h=wrapMixed(r,i()-10-b-(d?6:0)),p=h[0]+(h.length>1?"…":"");console.log(chalk.gray(` ${String(e).padStart(b)} `)+g+u+chalk.white(" "+p))}if(console.log(chalk.gray(s?" ✓ 答对 · ✗ 答错 · ○ 未作答 · 无 y/n 的卡不计分":" ✓ correct · ✗ wrong · ○ unanswered · cards without a y/n are unscored")),f&&console.log(chalk.bold.cyan(" ⚔ "+(s?"卡片练习: ":"practice: "))+(w>=$?chalk.bold.green:chalk.bold.white)(`${w}/${$}`)+chalk.gray(s?` 卡全完成 · ${k}/${_} 题 AC · 行内 ⚔已过/共(绿 完成 · 黄 部分 · 灰 未碰)`:` cards complete · ${k}/${_} problems AC · ⚔solved/total per row (green done · yellow partial · gray untouched)`)),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(" ⭐ "+(s?"提问得分 (best): ":"Prompt scores (best): "))+(0===r.length?chalk.gray(s?"本阶段尚未提问 — 用 /ai 研究,再 /submit 评分":"none this phase — research with /ai, then /submit"):r.map(e=>chalk.gray((s?"卡":"#")+e.card+" ")+o(e.tier)(String(e.score))).join(chalk.gray(" · "))))}if(a&&!a.configured){const o=d-g+1>12;console.log(chalk.gray(o?s?" 🔒 本阶段通关标准制作中 — 很快更新(360 层第二阶段标准设计中)":" 🔒 Pass criteria for this phase are in production — coming soon (360-tier second-stage standard in design)":s?" 🔒 本阶段通关标准制作中 — 很快更新(标准范式:3 场练习赛 + 3 卡研究)":" 🔒 Pass criteria for this phase are in production — coming soon (standard: 3 arenas + 3 research cards)"))}if(a?.configured&&a.contest){const o=a.contest;if(o.slots&&o.slots.length>0)for(const e of o.slots){const o=chalk.bold.cyan(` ⚔ ${s?"练习赛":"arena"} ${n.number}-${e.slot}: `);e.passed?console.log(o+chalk.bold.green(`${e.best_ac}/${e.n} AC ✅`)):0===e.attempts?console.log(o+chalk.white(s?`${e.n} 题 · 过 ${e.need} 题 — `:`${e.n} problems · AC ${e.need} — `)+chalk.bold.green(`contest ${e.slot}`)):console.log(o+chalk.yellow(s?`最好 ${e.best_ac}/${e.n}(需 ${e.need})· 已试 ${e.attempts} 次 — `:`best ${e.best_ac}/${e.n} (need ${e.need}) · ${e.attempts} attempt(s) — `)+chalk.bold.green(`contest ${e.slot}`))}else{const e=chalk.bold.cyan(" ⚖ "+(s?"判分赛: ":"Contest: "));o.passed?console.log(e+chalk.bold.green(`${o.best_ac}/${o.n} AC ✅ `+(s?"已过关":"passed"))):0===o.attempts?console.log(e+chalk.white(s?`${o.n} 题 · 过 ${o.need} 题解锁 — 输入 `:`${o.n} problems · AC ${o.need} to pass — type `)+chalk.bold.green("contest")+chalk.white(s?" 开始":" to start")):console.log(e+chalk.yellow(s?`最好 ${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(s?" 再战":" to retry"))}}t&&console.log(chalk.gray(" "+(s?"下一阶段: ":"next: "))+chalk.bold.cyan(s?`阶段 ${t.number}`:`Phase ${t.number}`)+chalk.gray(" · ")+chalk.white(t.name)),console.log(chalk.bold.yellow(" "+p));for(const o of N(s))console.log(o)}export function renderCurriculumComplete(o,e){const n=c().startsWith("zh"),t=e.checkResults||{},r="-".repeat(48);let a=0,s=0;console.log();for(const o of l(" "))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,l]=e.cardRange;let r=0,c=0;for(let e=o;e<=l;e++){const o=t[String(e)];o&&(r++,o.correct&&c++)}a+=r,s+=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(`${s}/${a}`)),console.log(chalk.bold.green(" "+r));for(const o of N(n))console.log(o);console.log()}export function renderMCQCard(o,t){const l=c(),r=e(o,l);h(r,t),b(),m(chalk.bold.yellow("🎯 "+r.title)),m(chalk.gray("─".repeat(Math.min(r.title.length+4,i())))),b();for(const o of C(r.question,i()-4))m(" "+o);b();for(const o of["A","B","C","D"])m(chalk.cyan(` ${o}.`)+" "+chalk.white(r.options[o]));b(),p(),console.log(chalk.gray(" "+(l.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",l))),console.log(chalk.gray(" ")+chalk.bold.cyan("back")+chalk.gray(l.startsWith("zh")?" 上一张 · ":" previous card · ")+chalk.bold.cyan("card N")+chalk.gray(l.startsWith("zh")?" 跳转":" jump")),console.log()}export function renderMCQFeedback(o,t,l,r){const a=c(),s=e(o,a);console.log(),l?console.log(" "+chalk.bold.green(n("correct",a)+" ")+chalk.gray(`${n("one_point",a)} · ${t} = ${s.options[t]}`)):console.log(" "+chalk.bold.red(n("not_quite",a)+" ")+chalk.gray(`${n("you_chose",a)} ${t}; ${n("answer_is",a)} ${s.answer}.`)),console.log(),console.log(chalk.gray(" "+n("explanation",a)));for(const o of C(s.explanation,i()-4))console.log(chalk.gray(" "+o));const g=Object.values(r.mcqResults),d=g.filter(o=>o.correct).length;console.log(),console.log(chalk.gray(" "+n("mcq_accuracy_so_far",a)+" ")+chalk.white(`${d}/${g.length}`)),console.log(),l||(console.log(chalk.gray(" "+n("press_ok_continue",a)+" ")+chalk.bold.green("ok")+chalk.gray(" "+n("to_continue",a))),console.log())}export function renderPracticalCard(o,t){const l=c(),r=e(o,l);h(r,t),b(),m(chalk.bold.yellow("🛠 "+r.title)),m(chalk.gray("─".repeat(Math.min(r.title.length+4,i())))),b();for(const o of C(r.task,i()-4))m(" "+o);if(r.starterCode){b(),m(chalk.gray(" "+n("starter_code",l))),b();for(const o of r.starterCode.split("\n"))m(" "+chalk.cyan(o))}b(),p(),console.log(chalk.gray(" "+n("try_in_sandbox",l)+" ")+chalk.bold.cyan("!python3")+chalk.gray(" "+n("drops_into_python",l))),console.log(chalk.gray(" "+n("when_done",l))),console.log(chalk.gray(" ")+chalk.bold.green("done")+chalk.gray(" "+n("done_desc",l))),console.log(chalk.gray(" ")+chalk.bold.yellow("skip")+chalk.gray(" "+n("skip_desc",l))),console.log(chalk.gray(" back previous card")),console.log(chalk.gray(" card N jump to a card by number")),console.log()}export function renderPracticalSuccess(o){const t=c(),l=e(o,t);console.log(),console.log(" "+chalk.bold.green(n("practical_recorded",t))),console.log(),console.log(chalk.gray(" "+n("reference_answer",t)));for(const o of C(l.successHint,i()-4))console.log(chalk.gray(" "+o));console.log(),console.log(chalk.gray(" "+n("press_ok_continue",t)+" ")+chalk.bold.green("ok")+chalk.gray(" "+n("to_continue",t))),console.log()}export function renderSimDemoCard(o,t){const l=c(),r=e(o,l);h(r,t),b(),m(chalk.bold.yellow("🎬 "+r.title)),b();for(const o of C(r.description,i()-4))m(" "+o);b(),m(chalk.gray(" "+n("sim_requires",l))),b(),p(),console.log(chalk.gray(" ")+chalk.bold.cyan("sim")+chalk.gray(" "+n("sim_launch_desc",l))),console.log(chalk.gray(" ")+chalk.bold.green("ok")+chalk.gray(" / ")+chalk.bold.green("next")+chalk.gray(" "+n("continue_to_next",l))),console.log(chalk.gray(" bookmark "+n("bookmark_desc",l))),console.log(chalk.gray(" back "+n("back_desc",l))),console.log(chalk.gray(" back previous card")),console.log(chalk.gray(" card N jump to a card by number")),console.log(chalk.gray(" quit "+n("quit_desc",l))),console.log()}export function renderArenaTaskCard(o,n){const t=c(),l=t.startsWith("zh"),r=e(o,t);h(r,n),b(),m(chalk.bold.cyan("🏟 "+r.title)),m(chalk.gray(" "+r.origin+" · "+r.domain+" · "+r.metric)),b();for(const o of r.statement){for(const e of C(o,i()-4))m(" "+e);b()}m(chalk.bold.green(l?" 核心要点 — baseline → 解法的关键变化":" CORE DELTA — baseline → solution"));for(const o of r.core_delta)for(const e of C("• "+o,i()-6))m(" "+chalk.white(e));b(),m(chalk.gray((l?" 提交格式: ":" Submission: ")+r.submission_format)),r.hints&&m(chalk.gray(l?" 本题有 5 级提示 (a–e),进入 arena 后逐级解锁。":" 5 progressive hints (a–e) unlock inside the arena.")),b(),p(),console.log(chalk.gray(" ")+chalk.bold.cyan("arena "+r.task_id)+chalk.gray(l?" 动手做这道题(下载 / 提示 / 提交 / 榜单)":" open the arena (download / hints / submit / board)")),console.log(chalk.gray(" ")+chalk.bold.green("ok")+chalk.gray(" / ")+chalk.bold.green("next")+chalk.gray(l?" 继续下一张":" continue to next")),console.log(chalk.gray(" back "+(l?"上一张":"previous"))),console.log(chalk.gray(" back previous card")),console.log(chalk.gray(" card N jump to a card by number")),console.log(chalk.gray(" quit "+(l?"退出":"exit"))),console.log()}export function renderComingSoonCard(o,n){const t=c(),l=t.startsWith("zh"),r=e(o,t);h(r,n),b(),m(chalk.bold.yellow("🔜 "+r.title)),b();const a=r.note||(l?"这道竞技场题目正在编写中,敬请期待。":"This arena task is coming soon.");for(const o of C(a,i()-4))m(" "+chalk.gray(o));b(),p(),console.log(chalk.gray(" ")+chalk.bold.green("ok")+chalk.gray(" / ")+chalk.bold.green("next")+chalk.gray(l?" 继续":" continue")),console.log(chalk.gray(" back previous card")),console.log(chalk.gray(" card N jump to a card by number")),console.log(chalk.gray(" quit "+(l?"退出":"exit"))),console.log()}export function renderMilestone(o,t){const l=c(),r=e(o,l),a=o.number===t.totalCards;console.log(),console.log(chalk.bold.yellow(" ╭"+d("═"))),console.log(chalk.bold.yellow(" ║")),console.log(chalk.bold.yellow(" ║ ")+chalk.white(" "+n("milestone_header",l))),console.log(chalk.bold.yellow(" ║"));const s=` ${r.badge} ${r.emoji}`;console.log(chalk.bold.yellow(" ║ ")+chalk.bold.green(s)),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",l)));for(const o of C(r.realWorldLevel,i()-4))console.log(chalk.bold.yellow(" ║ ")+" "+chalk.white(o));console.log(chalk.bold.yellow(" ║")),console.log(chalk.bold.yellow(" ║ ")+" "+chalk.gray(n("whats_next",l)));for(const o of C(r.unlockedNext,i()-4))console.log(chalk.bold.yellow(" ║ ")+" "+chalk.white(o));console.log(chalk.bold.yellow(" ║")),console.log(chalk.bold.yellow(" ╰"+d("═"))),console.log(),a?(console.log(chalk.gray(" "+n("demo_complete",l))),console.log(),console.log(chalk.gray(" "+n("unlock_full",l)+" ")+chalk.white(n("curriculum_name",l))+chalk.gray(",")),console.log(chalk.gray(" "+n("contact_team_leader",l)+" ")+chalk.bold.yellow("EA")+chalk.gray(" "+n("learn_token",l))),console.log(chalk.gray(" "+n("or_email",l)+" ")+chalk.cyan("australia@icoa2026.au")+chalk.gray(" "+n("for_partnership",l))),console.log(),console.log(chalk.gray(" "+n("type_quit",l)+" ")+chalk.bold.green("quit")+chalk.gray(" "+n("to_exit",l)+" ")+chalk.bold.green("status")+chalk.gray(" "+n("for_dashboard",l)))):(console.log(chalk.gray(" ")+chalk.bold.green("ok")+chalk.gray(" / ")+chalk.bold.green("next")+chalk.gray(" "+n("continue_to_next",l))),console.log(chalk.gray(" status "+n("status_full",l))),console.log(chalk.gray(" back previous card")),console.log(chalk.gray(" card N jump to a card by number")),console.log(chalk.gray(" quit "+n("quit_desc",l)))),console.log()}export function renderStatus(o,e){const t=c(),l=e.cardsCompleted.length,r=o.totalCards,a=Object.values(e.mcqResults),s=a.filter(o=>o.correct).length;console.log(),console.log(chalk.cyan(" ╭─ ")+chalk.bold.white(n("status_title",t))+" "+chalk.cyan("─".repeat(25))),b(),m(" "+chalk.gray(n("total_progress",t)+" ")+y(l,r)+chalk.gray(` (${l}/${r})`)),m(" "+chalk.gray(n("streak",t)+" ")+u(e.streakDays)+chalk.gray(` (${n("longest",t)} ${e.longestStreak})`)),m(" "+chalk.gray(n("mcq_accuracy",t)+" ")+chalk.white(`${s}/${a.length}`)),m(" "+chalk.gray(n("practicals_done",t)+" ")+chalk.white(`${e.practicalsCompleted.length}`)),m(" "+chalk.gray(n("bookmarked",t)+" ")+chalk.white(`${e.bookmarks.length}`)),b();const g=n("module",t).replace(":","").trim();for(const n of o.modules){const[o,t]=n.cardRange,l=e.cardsCompleted.filter(e=>e>=o&&e<=t).length,r=t-o+1;m(" "+(l===r?chalk.green("✓"):l>0?chalk.yellow("▶"):chalk.gray("□"))+" "+chalk.gray(`${g} ${n.number}: `)+chalk.white(`${l}/${r}`)+chalk.gray(" "+n.name))}if(b(),e.achievements.length>0){m(" "+chalk.gray(n("achievements",t)));for(const o of e.achievements)m(" "+chalk.bold.yellow("★ ")+chalk.white(o))}else m(" "+chalk.gray(n("achievements_none",t)));b(),p()}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{existsSync as t,readFileSync as e,unlinkSync as s,writeFileSync as n}from"node:fs";import{join as i}from"node:path";import{randomUUID as r}from"node:crypto";import chalk from"chalk";import{getConfig as o,getIcoaDir as a,getInstallSalt as c}from"./config.js";import{deriveFileKey as h,readSignedFile as d,writeSignedFile as l}from"./secure-store.js";import{getCliVersion as u}from"./version.js";import{postFocusSession as p}from"./learn-curricula.js";export const FOCUS_MIN=15;export const FOCUS_MAX=60;export const FOCUS_STEP=5;const g=Math.max(200,Number(process.env.ICOA_FOCUS_MINUTE_MS)||6e4),m=()=>i(a(),"focus-state.json"),k=()=>i(a(),"focus-spool.json"),f=()=>h(c(),"focus-state");export function parseFocusMinutes(t){const e=t.trim().match(/^(\d{1,3})\s*(m|min|mins|minutes?|分钟|分)?$/i);if(!e)return null;const s=Number(e[1]);if(s<15)return{min:15,note:"min"};if(s>60)return{min:60,note:"max"};const n=5*Math.round(s/5);return{min:n,note:n===s?"ok":"snapped"}}export function pingSchedule(t){const e=[];for(let s=10;s<t;s+=10)t-s>5&&e.push(s);let s=new Set(e);return e.length>3&&(s=new Set([e[0],e[Math.floor(e.length/2)],e[e.length-1]])),e.map(t=>({at:t,rated:s.has(t)}))}const $=t=>{const e=new Date(t);return`${String(e.getHours()).padStart(2,"0")}:${String(e.getMinutes()).padStart(2,"0")}`},_=t=>{if(null===t)return"—";const e=Math.round(t/1e3);return e>=60?`${Math.floor(e/60)}m${String(e%60).padStart(2,"0")}s`:`${e}s`};function w(t){const{_token:e,...s}=t;return s}export class StudyTimer{session=null;hooks=null;attachedToken="";timers=[];deferred=null;pendingRating=null;lastInputAt=0;lastPingAt=0;constructor(){this.loadFromDisk()}get active(){return null!==this.session&&!this.session.pending}remainingMin(){return this.active?Math.max(0,Math.ceil((this.session.endAt-Date.now())/g)):0}get seg(){if(!this.session)return null;const t=this.session.segments;return t.length&&null!==t[t.length-1].enteredAt?t[t.length-1]:null}isZh(){return this.hooks?this.hooks.isZh():(o().language||"en").toLowerCase().startsWith("zh")}footerLine(){if(!this.active)return null;const t=this.remainingMin(),e=new Set(this.session.segments.map(t=>t.curriculumId)).size,s=e>1?this.isZh()?` · ${e} 条轨`:` · ${e} tracks`:"";return this.isZh()?`⏱ 专注 还剩 ${t} 分钟${s} · focus off 提前结束`:`⏱ focus ${t} min left${s} · focus off to end early`}start(t){const e=Date.now();this.clearTimers();const s=this.hooks?{token:this.attachedToken,cid:this.attachedCid,card:this.hooks.currentCard()}:null;this.session={id:r(),startedAt:e,endAt:e+t*g,plannedMin:t,segments:[],ratings:[],pingsFired:0,idleRanges:[],pending:null},this.lastInputAt=e,this.lastPingAt=e,s?.token&&s.cid&&this.openSegment(s.token,s.cid,s.card),this.persist(),this.arm()}attachedCid="";attach(t,e,s){return this.hooks=s,this.attachedToken=t.toUpperCase(),this.attachedCid=e,replayFocusSpool(this.attachedToken),this.session?this.session.pending?this.showPending()?"reported":null:this.session.endAt<=Date.now()?(this.finish("done"),"reported"):(this.openSegment(this.attachedToken,e,s.currentCard()),this.lastInputAt=Date.now(),this.lastPingAt=Math.max(this.lastPingAt,this.session.startedAt),this.persist(),0===this.timers.length&&this.arm(),"resumed"):null}detach(){this.closeSegment(),this.pendingRating&&(clearTimeout(this.pendingRating.expire),this.session?.ratings.push({at_min:this.pendingRating.at,rating:null,latency_ms:null}),this.pendingRating=null),this.persist(),this.hooks=null,this.attachedToken="",this.attachedCid="",this.deferred=null}stop(){return!!this.active&&(this.finish("early"),!0)}showPending(){if(!this.session?.pending)return!1;const t=this.session.pending;return this.session=null,this.persist(),this.printReport(t.reports,t.totals,t.kind,{plannedMin:t.plannedMin,startedAt:t.startedAt,endedAt:t.endedAt}),!0}openSegment(t,e,s){if(!this.session)return;const n=this.session.segments[this.session.segments.length-1];n&&n.token===t&&n.curriculumId===e&&null===n.enteredAt?n.enteredAt=Date.now():this.session.segments.push({token:t,curriculumId:e,cardFrom:s,cardTo:s,ms:0,enteredAt:Date.now(),cardsSeen:[s],cardsReread:0,cardTimes:[],under3s:0,checksAnswered:0,checksCorrect:0,practiceAc:0,practiceSkipped:0,aiQuestions:0,aiDeep:0})}closeSegment(){const t=this.seg;if(!t||!this.session)return;const e=Math.min(Date.now(),this.session.endAt);t.ms+=Math.max(0,e-t.enteredAt),t.enteredAt=null,this.hooks&&(t.cardTo=this.hooks.currentCard())}noteInput(){this.lastInputAt=Date.now()}noteCardShown(t,e){const s=this.seg;s&&((s.cardsSeen.includes(t)||e)&&(s.cardsReread+=1),s.cardsSeen.includes(t)||s.cardsSeen.push(t),s.cardTo=t,this.persist())}noteCheck(t,e){const s=this.seg;s&&(s.checksAnswered+=1,t&&(s.checksCorrect+=1),s.cardTimes.push(e),e<3e3&&(s.under3s+=1),this.persist())}notePracticeAc(){const t=this.seg;t&&(t.practiceAc+=1,this.persist())}notePracticeSkipped(){const t=this.seg;t&&(t.practiceSkipped+=1,this.persist())}noteAi(t){const e=this.seg;e&&(e.aiQuestions+=1,t&&(e.aiDeep+=1),this.persist())}onPromptIdle(){if(!this.deferred||!this.hooks||!this.hooks.canInterrupt())return;const t=this.deferred;this.deferred=null,this.hooks.clearLine(),this.printPing(t,!0)}consumeRating(t){if(!this.pendingRating||!this.session)return!1;const e=this.pendingRating;if(clearTimeout(e.expire),this.pendingRating=null,/^[012]$/.test(t)){const s=Number(t);this.session.ratings.push({at_min:e.at,rating:s,latency_ms:Date.now()-e.askedAt});const n=["bad","ok","good"][s];return console.log(chalk.gray(this.isZh()?` ✓ 记下了(${s} = ${n})`:` ✓ noted (${s} = ${n})`)),this.persist(),!0}return this.session.ratings.push({at_min:e.at,rating:null,latency_ms:null}),!1}arm(){if(!this.session||this.session.pending)return;const t=Date.now();for(const e of pingSchedule(this.session.plannedMin)){const s=this.session.startedAt+e.at*g;s<=t||this.timers.push(setTimeout(()=>this.tick(e),s-t))}this.timers.push(setTimeout(()=>{this.finish("done")},Math.max(0,this.session.endAt-t)))}tick(t){this.active&&(this.hooks?.canInterrupt()&&this.hooks.lineEmpty()?(this.hooks.clearLine(),this.printPing(t,!0)):this.deferred=t)}printPing(t,e){if(!this.session||!this.hooks)return;const s=this.isZh(),n=Date.now(),i=this.lastInputAt<this.lastPingAt;i&&this.session.idleRanges.push([new Date(this.lastPingAt).toISOString(),new Date(n).toISOString()]),this.lastPingAt=n,this.session.pingsFired+=1;const r=Math.round((n-this.session.startedAt)/g),o=this.totals(),a=`${o.checksCorrect}/${o.checksAnswered} ✓`;if(console.log(),console.log(chalk.yellow(" ⏱ ")+chalk.white(`${r} / ${this.session.plannedMin} min`)+chalk.gray(s?` · ${o.cards} 张卡 · ${a}`:` · ${o.cards} cards · ${a}`)),i)console.log(chalk.gray(s?" 👋 还在吗?按 Enter 继续":" 👋 still there? press Enter"));else if(t.rated){console.log(chalk.gray(s?" 这段学得怎么样? ":" how's it going? ")+chalk.bold.green("0")+chalk.gray(" bad · ")+chalk.bold.green("1")+chalk.gray(" ok · ")+chalk.bold.green("2")+chalk.gray(s?" good (其它键 = 跳过)":" good (anything else = skip)"));const e=n,i=setTimeout(()=>{this.pendingRating&&this.pendingRating.askedAt===e&&(this.pendingRating=null,this.session?.ratings.push({at_min:t.at,rating:null,latency_ms:null}))},6e4);this.pendingRating={at:t.at,askedAt:e,expire:i}}console.log(),this.persist(),e&&this.hooks.prompt()}totals(){const t=this.session?.segments??[],e=t.flatMap(t=>t.cardTimes).sort((t,e)=>t-e),s=e=>t.reduce((t,s)=>t+e(s),0);return{tracks:new Set(t.map(t=>t.curriculumId)).size,cards:s(t=>t.cardsSeen.length),reread:s(t=>t.cardsReread),under3s:s(t=>t.under3s),checksAnswered:s(t=>t.checksAnswered),checksCorrect:s(t=>t.checksCorrect),practiceAc:s(t=>t.practiceAc),practiceSkipped:s(t=>t.practiceSkipped),aiQuestions:s(t=>t.aiQuestions),aiDeep:s(t=>t.aiDeep),medianCardMs:e.length?e[Math.floor(e.length/2)]:null}}buildReports(t){const e=this.session,s=Math.min(Date.now(),e.endAt);return e.segments.map(n=>{const i=[...n.cardTimes].sort((t,e)=>t-e);return{client_session_id:e.id,curriculum_id:n.curriculumId,started_at:new Date(e.startedAt).toISOString(),ended_at:new Date(s).toISOString(),planned_min:e.plannedMin,actual_ms:Math.round(6e4*n.ms/g),early:"early"===t?1:0,aborted:0,card_from:n.cardFrom,card_to:n.cardTo,cards_viewed:n.cardsSeen.length,cards_reread:n.cardsReread,cards_under_3s:n.under3s,checks_answered:n.checksAnswered,checks_correct:n.checksCorrect,median_card_ms:i.length?i[Math.floor(i.length/2)]:null,practice_ac:n.practiceAc,practice_skipped:n.practiceSkipped,ai_questions:n.aiQuestions,ai_deep:n.aiDeep,pings:e.pingsFired,ratings:e.ratings,idle_windows:e.idleRanges.length,idle_ranges:e.idleRanges,client_version:u(),_token:n.token}})}printReport(t,e,s,n){const i=this.isZh(),r=t[0],o=r?.planned_min??n?.plannedMin??this.session?.plannedMin??0,a=r?.started_at??new Date(n?.startedAt??this.session?.startedAt??Date.now()).toISOString(),c=r?.ended_at??new Date(n?.endedAt??Date.now()).toISOString(),h=Math.round((new Date(c).getTime()-new Date(a).getTime())/g),d=r?.idle_windows??0,l=e.checksAnswered>0&&e.checksCorrect===e.checksAnswered&&0===d,u="early"===s?i?`(提前结束于 ${h} 分钟)`:`(ended early at ${h} min)`:"",p=(r?.ratings??[]).filter(t=>null!==t.rating),m=p.map(t=>["bad","ok","good"][t.rating]).join(", "),k=(t,e)=>console.log(chalk.gray(` ${t.padEnd(12)}`)+chalk.white(e));this.hooks?.canInterrupt()&&this.hooks.lineEmpty()&&this.hooks.clearLine(),console.log(),console.log((l?chalk.yellow(" 🎉 "):chalk.yellow(" ⏱ "))+chalk.bold.white(i?`专注 ${o} 分钟`:`Focus session · ${o} min`)+chalk.gray(` · ${$(new Date(a).getTime())}–${$(new Date(c).getTime())}`)+chalk.gray(e.tracks>1?i?` · ${e.tracks} 条轨 `:` · ${e.tracks} tracks `:` · ${r?.curriculum_id??""} `)+chalk.gray(u));const f=Math.max(8,...t.map(t=>t.curriculum_id.length))+2;for(const e of t){const t=Math.round(e.actual_ms/6e4),s=[i?`${t} 分钟`:`${t} min`,i?`${e.cards_viewed} 卡(${e.card_from}→${e.card_to})`:`${e.cards_viewed} cards (${e.card_from}→${e.card_to})`,`${e.checks_correct}/${e.checks_answered} ✓`];e.practice_ac+e.practice_skipped>0&&s.push(`${e.practice_ac} AC${e.practice_skipped?` · ${e.practice_skipped} skipped`:""}`),e.ai_questions>0&&s.push(`/ai ${e.ai_questions}`),console.log(chalk.gray(" ")+chalk.white(e.curriculum_id.padEnd(f))+chalk.gray(s.join(" · ")))}0===t.length&&console.log(chalk.gray(i?" (这段时间没有打开课程)":" (no course was open during this session)")),k(i?"合计":"total",i?`${e.cards} 张卡 · 重读 ${e.reread} · ${e.checksCorrect}/${e.checksAnswered} ✓ · 中位每卡 ${_(e.medianCardMs)} · 3 秒内翻过 ${e.under3s} 张`:`${e.cards} cards · ${e.reread} re-read · ${e.checksCorrect}/${e.checksAnswered} ✓ · median ${_(e.medianCardMs)} per card · ${e.under3s} under 3s`),k(i?"在线":"presence",i?`${r?.pings??0} 次提醒 · 回应 ${p.length}${m?`(${m})`:""} · 空闲窗口 ${d}`:`${r?.pings??0} pings · answered ${p.length}${m?` (${m})`:""} · ${d} idle window${1===d?"":"s"}`),console.log(chalk.gray(" 📋 share: ")+chalk.white(`${h} min · ${e.tracks>1?`${e.tracks} tracks · `:""}${e.cards} cards · ${e.checksCorrect}/${e.checksAnswered} ok · icoa-cli`)),console.log(chalk.green(" ▸ ")+chalk.bold.green("focus <min>")+chalk.gray(i?" 随时再来一场 · ":" any time · ")+chalk.bold.green("me")+chalk.gray(i?" 看档案":" for your profile")),console.log()}async finish(t){if(!this.session||this.session.pending)return;this.pendingRating&&(clearTimeout(this.pendingRating.expire),this.session.ratings.push({at_min:this.pendingRating.at,rating:null,latency_ms:null}),this.pendingRating=null),this.closeSegment();const e=this.buildReports(t),s=this.totals();this.clearTimers(),this.deferred=null;for(const t of e)D({token:t._token,report:w(t)});const n=null!==this.hooks;n?(this.session=null,this.persist(),this.printReport(e,s,t)):(this.session.pending={kind:t,reports:e,totals:s,plannedMin:this.session.plannedMin,startedAt:this.session.startedAt,endedAt:Math.min(Date.now(),this.session.endAt)},this.persist());let i=!0;for(const t of e)await p(t._token,w(t))?T(t.client_session_id,t._token):i=!1;n&&this.hooks&&(i||(console.log(chalk.gray(this.isZh()?" (已保存在本机 · 下次联网时上传)":" (saved locally · will sync next time)")),console.log()),this.hooks.prompt())}clearTimers(){for(const t of this.timers)clearTimeout(t);this.timers=[]}persist(){try{if(!this.session)return void(t(m())&&s(m()));l(m(),this.session,f())}catch{}}loadFromDisk(){let t=null;try{t=d(m(),f()).data}catch{t=null}if(t&&"number"==typeof t.endAt&&Array.isArray(t.segments)){for(const e of t.segments)null!==e.enteredAt&&(e.enteredAt=null);this.session=t,this.lastInputAt=Date.now(),this.lastPingAt=Date.now(),t.pending||(t.endAt<=Date.now()?this.finish("done"):this.arm())}}}let A=null;export function getStudyTimer(){return A||(A=new StudyTimer),A}function y(){try{if(!t(k()))return[];const s=JSON.parse(e(k(),"utf8"));return Array.isArray(s)?s:[]}catch{return[]}}function S(e){try{if(0===e.length)return void(t(k())&&s(k()));n(k(),JSON.stringify(e.slice(-50)))}catch{}}const M=(t,e,s)=>t.report.client_session_id===e&&t.token===s;function D(t){const e=y().filter(e=>!M(e,t.report.client_session_id,t.token));e.push(t),S(e)}function T(t,e){S(y().filter(s=>!M(s,t,e)))}export async function replayFocusSpool(t){const e=y().filter(e=>e.token===t.toUpperCase());for(const t of e)await p(t.token,t.report)&&T(t.report.client_session_id,t.token)}
|