icoa-cli 2.19.454 → 2.19.456
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/access.js +1 -1
- package/dist/lib/editor-spawn.js +1 -1
- package/dist/lib/input-dynamics.d.ts +20 -0
- package/dist/lib/input-dynamics.js +1 -1
- package/dist/lib/interactive-spawn.js +1 -1
- package/dist/lib/learn-curricula.d.ts +5 -1
- package/dist/lib/learn-curricula.js +1 -1
- package/dist/lib/learn-render.d.ts +2 -0
- package/dist/lib/learn-render.js +1 -1
- package/dist/lib/learn-replies.d.ts +42 -0
- package/dist/lib/learn-replies.js +1 -0
- package/dist/lib/py-syntax.d.ts +27 -0
- package/dist/lib/py-syntax.js +1 -0
- package/package.json +1 -1
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";function s(){return(t().language||"en").toLowerCase()}let c="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){c=o}export function learnWidthCap(o=c){return"wider"===o?120:"wide"===o?96:64}export function learnW(){if("normal"===c)return 64;const o=process.stdout.columns||64;return Math.max(64,Math.min(o-6,learnWidthCap()))}const g=learnW;function i(o="─"){return o.repeat(g()+2)}function d(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 y(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=s(),r=e.cardsCompleted.length,a=o.totalCards,c=a-r,g=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(" ╭"+i("═"))),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(u?"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))+d(r,a)+chalk.gray(` (${r}/${a})`)),console.log(chalk.cyan(" ║ ")+" "+chalk.gray(n("streak",l).padEnd(11))+y(e.streakDays)),c>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(" ╰"+i("═"))),console.log(),console.log(chalk.gray(" ─────────────────────────────────────────────"));const m=0===r?n("continue_start",l):`${n("continue_resume",l)} ${e.currentCard}`;console.log(chalk.bold.green(" continue")+chalk.gray(" "+m)),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 u(o,e){const t=s(),l=o.module,r=e.modules.find(o=>o.number===l),a=r?r.name:n("unknown_module",t),c=n("module",t).replace(":","").trim(),i=`${n("card",t)} ${o.number} / ${e.totalCards} · ${c} ${l} · ${a}`;console.log(),console.log(chalk.cyan(" ╭─ ")+chalk.bold.white(i)+" "+chalk.cyan("─".repeat(Math.max(0,g()-i.length-4))))}function h(){console.log(chalk.cyan(" ╰"+i())),console.log()}function m(o){console.log(chalk.cyan(" │ ")+chalk.white(o))}function b(){console.log(chalk.cyan(" │"))}const p=/[┌┐└┘├┤┬┴┼─│╔╗╚╝═║╠╣╦╩╬▓▒░█]/;function f(o){return/^(?: {4}|\t)/.test(o)||p.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 $=/\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 t,l="",r=0;for($.lastIndex=0;null!==(t=$.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+=w(o.slice(t,r.index),e)),n+=chalk.cyan(r[1]),t=r.index+r[0].length;return t<o.length&&(n+=w(o.slice(t),e)),n}function k(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(f(t))console.log(chalk.cyan(" │ ")+chalk.white(t));else for(const o of wrapMixed(t,g()-4))console.log(chalk.cyan(" │ ")+` ${_(o,e)}`);else b()}function x(o,e){const n=[];for(const t of o.split("\n")){if(""===t){n.push("");continue}if(f(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 C=!1;export function setLearnAiEnabled(o){C=o}let M=new Set;export function setLearnPqsViewCards(o){M=o}let v=new Map;export function setLearnHonorRanks(o){v=o}const W=["①","②","③","④","⑤","⑥","⑦","⑧","⑨","⑩"];export function renderHonorWall(o){const e=s().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 c=t.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(t,l){const i=s(),d=e(t,i);u(d,l),b();const y=wrapMixed(d.title,g()-4);for(const o of y)m(chalk.bold.yellow(o));m(chalk.gray("─".repeat(Math.min(Math.max(...y.map(e=>o(e))),g()))));const p=!!(t.chart_ids&&t.chart_ids.length>0);if(p&&r(t.chart_ids.slice(0,1),l.charts,a()),d.key_point&&""!==d.key_point.trim()?(p||b(),function(o,e,n){const t=s().startsWith("zh"),l=a().cols,r="-".repeat(Math.min(g(),Math.max(24,l-6))),c=t?"要点":"KEY POINT",i=t?`第 ${e} / ${n} 张`:`Card ${e} / ${n}`;console.log(chalk.cyan(" │ ")+chalk.green(r)),console.log(chalk.cyan(" │ ")+chalk.green(`${c} · ${i}`));for(const e of wrapMixed(o,g()-4))console.log(chalk.cyan(" │ ")+chalk.bold.green(" "+e));console.log(chalk.cyan(" │ ")+chalk.green(r)),b()}(d.key_point,d.number,l.totalCards)):p||b(),d.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);b()}),d.icoaConnection){m(chalk.magenta(" "+n("icoa_connection",i))),m(chalk.gray(" "+"─".repeat(20)));for(const o of wrapMixed(d.icoaConnection,g()-4))m(" "+chalk.magenta(o));b()}if(t.check){const o=i.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,g()-4))m(" "+chalk.white(o));b()}h(),function(e,n){const t=s().startsWith("zh"),l=a().cols,r="-".repeat(Math.min(g(),Math.max(24,l-6))),i=chalk.gray(" "),d=[];e.check?(d.push(chalk.bold.green("[y]")+chalk.gray(t?" 是":" yes")),d.push(chalk.bold.green("[n]")+chalk.gray(t?" 否":" no"))):d.push(chalk.bold.green("[ok]")+chalk.gray(t?" 继续":" next")),C&&(d.push(chalk.bold.magenta("[/ai]")+chalk.gray(t?" 🐼 熊猫导师":" 🐼 Panda tutor")),d.push(chalk.bold.magenta("[/submit]")+chalk.gray(t?" 提问评分":" prompt score")));const y=v.get(e.number);var u;d.push(chalk.bold.magenta(y?`[/honors ${u=y,W[u-1]??`#${u}`}]`:"[/honors]")+chalk.gray(t?" 排行":" board")),"normal"===c?d.push(chalk.bold.cyan("[wide]")+chalk.gray(t?" 宽屏":" widen")):(d.push("wide"===c?chalk.bold.cyan("[wider]")+chalk.gray(t?" 更宽":" wider"):chalk.bold.cyan("[wide]")+chalk.gray(t?" 窄一点":" narrower")),d.push(chalk.bold.cyan("[normal]")+chalk.gray(t?" 64列":" 64-col"))),d.push(chalk.bold.yellow("[e]")+chalk.gray(t?" 报告问题":" report")),d.push(chalk.bold.cyan("[b]")+chalk.gray(t?" 上一张":" back")),d.push(chalk.bold.cyan("[card N]")+chalk.gray(t?" 跳转":" jump")),d.push(chalk.gray("[reload]")+(t?chalk.gray(" 刷新"):"")),d.push(chalk.gray("[quit]")+(t?chalk.gray(" 退出"):""));const h=o(r);console.log(chalk.gray(" "+r)),console.log(chalk.gray(" "+(t?`第 ${e.number} / ${n.totalCards} 张`:`Card ${e.number} / ${n.totalCards}`)));let m="",b=0;for(const e of d){const n=o(e);m&&b+2+n>h?(console.log(" "+m),m=e,b=n):(m=m?m+i+e:e,b=m===e?n:b+2+n)}m&&console.log(" "+m),console.log(chalk.gray(" "+r));const p=[t?"e = 报告本卡错误,不算作答;提交后回到本题继续":"e = report a card problem, not an answer — you come back here",t?"更多: bookmark / unbookmark · status · lang zh/en · link":"more: bookmark / unbookmark · status · lang zh/en · link"];for(const o of p)for(const e of wrapMixed(o,h))console.log(chalk.gray(" "+e));console.log()}(t,l)}export function renderCheckFeedback(o,e,n,t=!1){const l=s().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,g()-4))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,t,r,a){const c=s().startsWith("zh"),[i,d]=n.cardRange,y=e.checkResults||{};let u=0,h=0;for(let o=i;o<=d;o++){const e=y[String(o)];e&&(u++,e.correct&&h++)}const m="-".repeat(48);console.log();for(const o of l(" "))console.log(o);console.log(),console.log(chalk.bold.yellow(" "+m)),console.log(chalk.bold.yellow(" "+(c?`阶段 ${n.number} 完成`:`Phase ${n.number} complete`))+chalk.gray(" · ")+chalk.white(n.name));const b=u>0&&h===u?chalk.bold.green:chalk.bold.white;console.log(chalk.gray(" "+(c?"理解检查得分: ":"comprehension score: "))+b(`${h}/${u}`)+chalk.gray(0===u?c?" (本阶段未作答 y/n)":" (no y/n answered)":""));const p=String(d).length;for(let e=i;e<=d;e++){const n=o.cards.find(o=>o.number===e);if(!n)continue;const t=n,l=t.title??t.badge??"",r=(c?t._zh?.title??t._zh?.badge??l:l)||"",a=null!=n.check,s=y[String(e)];let i;i=s?s.correct?chalk.green("✓"):chalk.red("✗"):a?chalk.gray("○"):chalk.gray("·");const d=wrapMixed(r,g()-10-p),u=d[0]+(d.length>1?"…":"");console.log(chalk.gray(` ${String(e).padStart(p)} `)+i+chalk.white(" "+u))}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(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(` ⚔ ${c?"练习赛":"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(c?`${e.n} 题 · 过 ${e.need} 题 — `:`${e.n} problems · AC ${e.need} — `)+chalk.bold.green(`contest ${e.slot}`)):console.log(o+chalk.yellow(c?`最好 ${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(" ⚖ "+(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"))}}t&&console.log(chalk.gray(" "+(c?"下一阶段: ":"next: "))+chalk.bold.cyan(c?`阶段 ${t.number}`:`Phase ${t.number}`)+chalk.gray(" · ")+chalk.white(t.name)),console.log(chalk.bold.yellow(" "+m));for(const o of z(c))console.log(o)}export function renderCurriculumComplete(o,e){const n=s().startsWith("zh"),t=e.checkResults||{},r="-".repeat(48);let a=0,c=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,s=0;for(let e=o;e<=l;e++){const o=t[String(e)];o&&(r++,o.correct&&s++)}a+=r,c+=s;const g=r>0&&s===r?chalk.green:chalk.white;console.log(chalk.gray(" "+(n?`阶段 ${e.number}`:`Phase ${e.number}`).padEnd(8))+g(`${s}/${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}/${a}`)),console.log(chalk.bold.green(" "+r));for(const o of z(n))console.log(o);console.log()}export function renderMCQCard(o,t){const l=s(),r=e(o,l);u(r,t),b(),m(chalk.bold.yellow("🎯 "+r.title)),m(chalk.gray("─".repeat(Math.min(r.title.length+4,g())))),b();for(const o of x(r.question,g()-4))m(" "+o);b();for(const o of["A","B","C","D"])m(chalk.cyan(` ${o}.`)+" "+chalk.white(r.options[o]));b(),h(),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=s(),c=e(o,a);console.log(),l?console.log(" "+chalk.bold.green(n("correct",a)+" ")+chalk.gray(`${n("one_point",a)} · ${t} = ${c.options[t]}`)):console.log(" "+chalk.bold.red(n("not_quite",a)+" ")+chalk.gray(`${n("you_chose",a)} ${t}; ${n("answer_is",a)} ${c.answer}.`)),console.log(),console.log(chalk.gray(" "+n("explanation",a)));for(const o of x(c.explanation,g()-4))console.log(chalk.gray(" "+o));const i=Object.values(r.mcqResults),d=i.filter(o=>o.correct).length;console.log(),console.log(chalk.gray(" "+n("mcq_accuracy_so_far",a)+" ")+chalk.white(`${d}/${i.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=s(),r=e(o,l);u(r,t),b(),m(chalk.bold.yellow("🛠 "+r.title)),m(chalk.gray("─".repeat(Math.min(r.title.length+4,g())))),b();for(const o of x(r.task,g()-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(),h(),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=s(),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 x(l.successHint,g()-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=s(),r=e(o,l);u(r,t),b(),m(chalk.bold.yellow("🎬 "+r.title)),b();for(const o of x(r.description,g()-4))m(" "+o);b(),m(chalk.gray(" "+n("sim_requires",l))),b(),h(),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=s(),l=t.startsWith("zh"),r=e(o,t);u(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 x(o,g()-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 x("• "+o,g()-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(),h(),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=s(),l=t.startsWith("zh"),r=e(o,t);u(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 x(a,g()-4))m(" "+chalk.gray(o));b(),h(),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=s(),r=e(o,l),a=o.number===t.totalCards;console.log(),console.log(chalk.bold.yellow(" ╭"+i("═"))),console.log(chalk.bold.yellow(" ║")),console.log(chalk.bold.yellow(" ║ ")+chalk.white(" "+n("milestone_header",l))),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",l)));for(const o of x(r.realWorldLevel,g()-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 x(r.unlockedNext,g()-4))console.log(chalk.bold.yellow(" ║ ")+" "+chalk.white(o));console.log(chalk.bold.yellow(" ║")),console.log(chalk.bold.yellow(" ╰"+i("═"))),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=s(),l=e.cardsCompleted.length,r=o.totalCards,a=Object.values(e.mcqResults),c=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)+" ")+d(l,r)+chalk.gray(` (${l}/${r})`)),m(" "+chalk.gray(n("streak",t)+" ")+y(e.streakDays)+chalk.gray(` (${n("longest",t)} ${e.longestStreak})`)),m(" "+chalk.gray(n("mcq_accuracy",t)+" ")+chalk.white(`${c}/${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(),h()}
|
|
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 m=0===r?n("continue_start",l):`${n("continue_resume",l)} ${e.currentCard}`;console.log(chalk.bold.green(" continue")+chalk.gray(" "+m)),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 m(){console.log(chalk.cyan(" ╰"+d())),console.log()}function b(o){console.log(chalk.cyan(" │ ")+chalk.white(o))}function p(){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 _(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 k(o,e=!1){let n="",t=0;const l=/`([^`]*)`/g;let r;for(;null!==(r=l.exec(o));)r.index>t&&(n+=_(o.slice(t,r.index),e)),n+=chalk.cyan(r[1]),t=r.index+r[0].length;return t<o.length&&(n+=_(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(" │ ")+` ${k(o,e)}`);else p()}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 M=!1;export function setLearnAiEnabled(o){M=o}let W=new Set;export function setLearnPqsViewCards(o){W=o}let v=new Map;export function setLearnHonorRanks(o){v=o}const z=["①","②","③","④","⑤","⑥","⑦","⑧","⑨","⑩"];let j=[];export function setLearnReplies(o){j=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),p();const u=wrapMixed(y.title,i()-4);for(const o of u)b(chalk.bold.yellow(o));b(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||p(),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)),p()}(y.key_point,y.number,l.totalCards)):f||p(),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);p()}),y.icoaConnection){b(chalk.magenta(" "+n("icoa_connection",d))),b(chalk.gray(" "+"─".repeat(20)));for(const o of wrapMixed(y.icoaConnection,i()-4))b(" "+chalk.magenta(o));p()}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;b(chalk.cyan(" 🤔 "+(o?"请回答 (输 y 或 n):":"Quick comprehension check (type y or n):"))),b(chalk.gray(" "+"─".repeat(20)));for(const o of wrapMixed(n,i()-4))b(" "+chalk.white(o));p()}m(),function(e,n){const t=c().startsWith("zh"),l=a().cols,r="-".repeat(Math.min(i(),Math.max(24,l-6))),d=chalk.gray(" "),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")),M&&(y.push(chalk.bold.magenta("[/ai]")+chalk.gray(t?" 🐼 熊猫导师":" 🐼 Panda tutor")),y.push(chalk.bold.magenta("[/submit]")+chalk.gray(t?" 提问评分":" prompt score")));const u=v.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?" 窄一点":" narrower")),y.push(chalk.bold.cyan("[normal]")+chalk.gray(t?" 64列":" 64-col")));const m=s(j,e.number,t);if(m){const o=m.startsWith("💬");y.push((o?chalk.bold.yellow("[r]"):chalk.gray("[r]"))+(o?chalk.bold.white(` ${m}`):chalk.gray(` ${m}`)))}y.push(chalk.bold.yellow("[e]")+chalk.gray(t?" 报告问题":" report")),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 p="",f=0;for(const e of y){const n=o(e);p&&f+2+n>b?(console.log(" "+p),p=e,f=n):(p=p?p+d+e:e,f=p===e?n:f+2+n)}p&&console.log(" "+p),console.log(chalk.gray(" "+r));const $=[t?"e = 报告本卡错误,不算作答;提交后回到本题继续":"e = report a card problem, not an answer — you come back here",t?"更多: bookmark / unbookmark · status · lang zh/en · link":"more: bookmark / unbookmark · status · lang zh/en · link"];for(const o of $)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 q(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 m="-".repeat(48);console.log();for(const o of l(" "))console.log(o);console.log(),console.log(chalk.bold.yellow(" "+m)),console.log(chalk.bold.yellow(" "+(s?`阶段 ${n.number} 完成`:`Phase ${n.number} complete`))+chalk.gray(" · ")+chalk.white(n.name));const b=u>0&&h===u?chalk.bold.green:chalk.bold.white;console.log(chalk.gray(" "+(s?"理解检查得分: ":"comprehension score: "))+b(`${h}/${u}`)+chalk.gray(0===u?s?" (本阶段未作答 y/n)":" (no y/n answered)":""));const p=String(d).length;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=wrapMixed(r,i()-10-p),u=d[0]+(d.length>1?"…":"");console.log(chalk.gray(` ${String(e).padStart(p)} `)+g+chalk.white(" "+u))}if(console.log(chalk.gray(s?" ✓ 答对 · ✗ 答错 · ○ 未作答 · 无 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(" ⭐ "+(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?.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(" "+m));for(const o of q(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 q(n))console.log(o);console.log()}export function renderMCQCard(o,t){const l=c(),r=e(o,l);h(r,t),p(),b(chalk.bold.yellow("🎯 "+r.title)),b(chalk.gray("─".repeat(Math.min(r.title.length+4,i())))),p();for(const o of C(r.question,i()-4))b(" "+o);p();for(const o of["A","B","C","D"])b(chalk.cyan(` ${o}.`)+" "+chalk.white(r.options[o]));p(),m(),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),p(),b(chalk.bold.yellow("🛠 "+r.title)),b(chalk.gray("─".repeat(Math.min(r.title.length+4,i())))),p();for(const o of C(r.task,i()-4))b(" "+o);if(r.starterCode){p(),b(chalk.gray(" "+n("starter_code",l))),p();for(const o of r.starterCode.split("\n"))b(" "+chalk.cyan(o))}p(),m(),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),p(),b(chalk.bold.yellow("🎬 "+r.title)),p();for(const o of C(r.description,i()-4))b(" "+o);p(),b(chalk.gray(" "+n("sim_requires",l))),p(),m(),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),p(),b(chalk.bold.cyan("🏟 "+r.title)),b(chalk.gray(" "+r.origin+" · "+r.domain+" · "+r.metric)),p();for(const o of r.statement){for(const e of C(o,i()-4))b(" "+e);p()}b(chalk.bold.green(l?" 核心要点 — baseline → 解法的关键变化":" CORE DELTA — baseline → solution"));for(const o of r.core_delta)for(const e of C("• "+o,i()-6))b(" "+chalk.white(e));p(),b(chalk.gray((l?" 提交格式: ":" Submission: ")+r.submission_format)),r.hints&&b(chalk.gray(l?" 本题有 5 级提示 (a–e),进入 arena 后逐级解锁。":" 5 progressive hints (a–e) unlock inside the arena.")),p(),m(),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),p(),b(chalk.bold.yellow("🔜 "+r.title)),p();const a=r.note||(l?"这道竞技场题目正在编写中,敬请期待。":"This arena task is coming soon.");for(const o of C(a,i()-4))b(" "+chalk.gray(o));p(),m(),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))),p(),b(" "+chalk.gray(n("total_progress",t)+" ")+y(l,r)+chalk.gray(` (${l}/${r})`)),b(" "+chalk.gray(n("streak",t)+" ")+u(e.streakDays)+chalk.gray(` (${n("longest",t)} ${e.longestStreak})`)),b(" "+chalk.gray(n("mcq_accuracy",t)+" ")+chalk.white(`${s}/${a.length}`)),b(" "+chalk.gray(n("practicals_done",t)+" ")+chalk.white(`${e.practicalsCompleted.length}`)),b(" "+chalk.gray(n("bookmarked",t)+" ")+chalk.white(`${e.bookmarks.length}`)),p();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;b(" "+(l===r?chalk.green("✓"):l>0?chalk.yellow("▶"):chalk.gray("□"))+" "+chalk.gray(`${g} ${n.number}: `)+chalk.white(`${l}/${r}`)+chalk.gray(" "+n.name))}if(p(),e.achievements.length>0){b(" "+chalk.gray(n("achievements",t)));for(const o of e.achievements)b(" "+chalk.bold.yellow("★ ")+chalk.white(o))}else b(" "+chalk.gray(n("achievements_none",t)));p(),m()}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* learn-replies — pure functions for the card human-reply channel (student
|
|
3
|
+
* presses `e` to report a card problem; a human writes one reply; the
|
|
4
|
+
* student reads it with `r` under that card). No chalk, no I/O — mirrors
|
|
5
|
+
* the board-render.ts pure-module pattern. Every user-facing string ships
|
|
6
|
+
* both EN and ZH; this channel never promises a reply timeline.
|
|
7
|
+
*/
|
|
8
|
+
export type CardReply = {
|
|
9
|
+
id: number;
|
|
10
|
+
curriculumId: string;
|
|
11
|
+
cardNumber: number;
|
|
12
|
+
myText: string;
|
|
13
|
+
sentAt: string;
|
|
14
|
+
replyText?: string;
|
|
15
|
+
repliedBy?: string;
|
|
16
|
+
repliedAt?: string;
|
|
17
|
+
seen: boolean;
|
|
18
|
+
};
|
|
19
|
+
export declare function sanitizeReplyText(s: string): string;
|
|
20
|
+
export declare function parseReplies(raw: unknown): CardReply[];
|
|
21
|
+
/** Local calendar day `MM-DD` — never a "3 days ago" delta (learn-continue lesson). */
|
|
22
|
+
export declare function shortDay(iso: string): string;
|
|
23
|
+
export declare function repliesForCard(all: CardReply[], card: number): CardReply[];
|
|
24
|
+
/**
|
|
25
|
+
* Recognizes an `r` / `reply` / `replies` command line — bare (current card)
|
|
26
|
+
* or with an argument (`r 17` / `reply all` / ...) — and extracts the
|
|
27
|
+
* target. Pure so it is directly unit-testable instead of only probeable by
|
|
28
|
+
* grepping the regex literal out of `learn.ts`'s source text (review finding
|
|
29
|
+
* I3, 2026-07-30: that grep went stale during an earlier fix round and the
|
|
30
|
+
* test silently degraded to vacuous). `learn.ts`'s dispatch calls this exact
|
|
31
|
+
* function — it is the real guard, not a copy of it.
|
|
32
|
+
*
|
|
33
|
+
* Returns `null` for anything that is not a reply command, including
|
|
34
|
+
* look-alikes (`read 3`, `rally`, `r x`).
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseReplyCommand(input: string): {
|
|
37
|
+
target: number | 'all' | 'current';
|
|
38
|
+
} | null;
|
|
39
|
+
export declare function unreadReplies(all: CardReply[]): CardReply[];
|
|
40
|
+
export declare function replyPushLine(all: CardReply[], zh: boolean): string | null;
|
|
41
|
+
export declare function replyChipLabel(all: CardReply[], card: number, zh: boolean): string | null;
|
|
42
|
+
export declare function renderReplyLines(all: CardReply[], card: number | 'all', zh: boolean, width: number): string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function sanitizeReplyText(e){return String(e??"").replace(/\u001b\[[0-9;]*[A-Za-z]/g,"").replace(/[\u0000-\u0009\u000b-\u001f\u007f]/g,"")}export function parseReplies(e){if(!Array.isArray(e))return[];const r=[];for(const t of e){if(!t||"object"!=typeof t)continue;const e=t;if("number"==typeof e.id&&"number"==typeof e.card_number&&(r.push({id:e.id,curriculumId:"string"==typeof e.curriculum_id?e.curriculum_id:"",cardNumber:e.card_number,myText:sanitizeReplyText("string"==typeof e.my_text?e.my_text:""),sentAt:"string"==typeof e.sent_at?e.sent_at:"",replyText:"string"==typeof e.reply_text?sanitizeReplyText(e.reply_text):void 0,repliedBy:"string"==typeof e.replied_by?sanitizeReplyText(e.replied_by):void 0,repliedAt:"string"==typeof e.replied_at?e.replied_at:void 0,seen:1===e.seen||!0===e.seen}),r.length>=50))break}return r}export function shortDay(e){const r=Date.parse(e);if(Number.isNaN(r))return"";const t=new Date(r),n=e=>String(e).padStart(2,"0");return`${n(t.getMonth()+1)}-${n(t.getDate())}`}export function repliesForCard(e,r){return e.filter(e=>e.cardNumber===r)}export function parseReplyCommand(e){if("r"!==e&&"reply"!==e&&"replies"!==e&&!/^(r|reply|replies)\s+(\d+|all)$/.test(e))return null;const r=e.replace(/^(reply|replies|r)\s*/,"").trim();return"all"===r?{target:"all"}:""===r?{target:"current"}:{target:Number(r)}}export function unreadReplies(e){return e.filter(e=>!!e.replyText&&!e.seen)}export function replyPushLine(e,r){const t=unreadReplies(e);if(0===t.length)return null;if(1===t.length){const e=t[0].cardNumber;return r?`💬 你有 1 条回复 — 第 ${e} 张卡 · 输 r ${e} 看`:`💬 You have 1 reply — Card ${e} · type r ${e}`}return r?`💬 你有 ${t.length} 条回复 · 输 r all 看全部`:`💬 You have ${t.length} replies · type r all`}export function replyChipLabel(e,r,t){const n=repliesForCard(e,r);if(0===n.length)return null;const o=unreadReplies(n).length;return o>0?t?`💬 ${o} 条回复`:`💬 ${o} ${o>1?"replies":"reply"}`:t?`我的报告 (${n.length})`:`my reports (${n.length})`}function e(e,r,t){const n=[];for(const o of e.split("\n")){let e="";for(const s of o.split(/\s+/).filter(Boolean))e&&e.length+1+s.length>r?(n.push(t+e),e=s):e=e?`${e} ${s}`:s;n.push(t+e)}return n}export function renderReplyLines(r,t,n,o){const s=Math.max(32,Math.min(o,96)),l="-".repeat(s),p="all"===t?r:repliesForCard(r,t);if(0===p.length){const e=unreadReplies(r);if(e.length>0){const r=e[0].cardNumber;return n?[`本卡没有报告。你在第 ${r} 张卡有 ${e.length} 条未读回复 · 输 r ${r}`]:[`No reports on this card. You have ${e.length} unread on Card ${r} · type r ${r}`]}if(r.length>0){const e=r[0].cardNumber;return n?[`本卡没有报告。你报过的最近一张是第 ${e} 张 · 输 r ${e}`]:[`No reports on this card. Your most recent was Card ${e} · type r ${e}`]}return n?["你还没在这门课报过问题。按 e 可以报告本卡的错误或提问。"]:["You have not reported anything on this course yet. Press e to report a problem or ask about this card."]}const i=["all"===t?n?"💬 我的全部报告":"💬 All your reports":n?`💬 第 ${t} 张卡 — 我的报告`:`💬 Card ${t} — your reports`,l],u="all"===t?p:[...p].reverse();for(const r of u){const o="all"===t?n?`第 ${r.cardNumber} 张卡 · `:`Card ${r.cardNumber} · `:"";r.replyText?(i.push(n?`${o}已回复 · ${shortDay(r.repliedAt??"")} · ${r.repliedBy??""}`:`${o}replied · ${shortDay(r.repliedAt??"")} · ${r.repliedBy??""}`),i.push(n?` 你写的(${shortDay(r.sentAt)}):`:` you wrote (${shortDay(r.sentAt)}):`),i.push(...e(r.myText,s-6," ")),i.push(...e(`-> ${r.replyText}`,s-4," "))):(i.push(n?`${o}待回复 · ${shortDay(r.sentAt)} 已送达`:`${o}waiting · sent ${shortDay(r.sentAt)}`),i.push(n?" 你写的:":" you wrote:"),i.push(...e(r.myText,s-6," "))),i.push("")}return i.push(l),i.push(n?"不是每条都会有回复,但每条都有人看。":"Not every report gets a reply — but every one is read."),i.push(n?"e = 在这张卡再报一条":"e = report something else on this card"),i}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface SyntaxEntry {
|
|
2
|
+
/** lookup keys — first one is the display title */
|
|
3
|
+
keys: string[];
|
|
4
|
+
en: string;
|
|
5
|
+
zh: string;
|
|
6
|
+
/** runnable demo; language-neutral, `# →` marks the result */
|
|
7
|
+
demo: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare const SYNTAX_TABLE: SyntaxEntry[];
|
|
10
|
+
/** Flat index of primary keys — what `/syntax` with no argument shows. */
|
|
11
|
+
export declare const SYNTAX_INDEX: string[];
|
|
12
|
+
/** Exact-then-substring match against the curated table. */
|
|
13
|
+
export declare function lookupLocal(query: string): SyntaxEntry | null;
|
|
14
|
+
/** Nearest primary keys for a miss — suggestions, never a model. */
|
|
15
|
+
export declare function nearKeys(query: string, limit?: number): string[];
|
|
16
|
+
export interface PydocHit {
|
|
17
|
+
title: string;
|
|
18
|
+
lines: string[];
|
|
19
|
+
}
|
|
20
|
+
/** Ask the student's OWN interpreter. Returns null on any miss/error. */
|
|
21
|
+
export declare function lookupPydoc(py: string, query: string, maxLines?: number): Promise<PydocHit | null>;
|
|
22
|
+
/**
|
|
23
|
+
* Render one lookup. All output is chalked (black-background rule). `zh`
|
|
24
|
+
* selects the gloss language; demo code is never translated.
|
|
25
|
+
*/
|
|
26
|
+
export declare function renderSyntax(hit: SyntaxEntry, zh: boolean): void;
|
|
27
|
+
export declare function renderPydoc(hit: PydocHit, zh: boolean): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{spawn as e}from"node:child_process";import chalk from"chalk";export const SYNTAX_TABLE=[{keys:["del","delete","remove index"],en:"Statement, not a method: deletes without returning. Only `del` can delete a slice.",zh:"语句(不是方法):删掉但不返回。只有 del 能删切片。",demo:["l = [0, 1, 2, 3, 4, 5]","del l[1] # → [0, 2, 3, 4, 5]","del l[1:3] # → [0, 4, 5] slice: pop cannot","del l[:] # → [] same as l.clear()","del l[9] # IndexError; del l[9:99] is silently fine"]},{keys:["pop","list.pop"],en:"Method: removes AND returns. O(1) at the end, O(n) anywhere else.",zh:"方法:删掉并返回。尾部 O(1),其他位置 O(n)。",demo:["l = [10, 20, 30]","l.pop() # → 30 l is [10, 20]","l.pop(0) # → 10 O(n): shifts everything left","[].pop() # IndexError: pop from empty list","d.pop(k, None) # dict: default avoids KeyError"]},{keys:["slice","slicing","sublist"],en:"l[start:stop:step] — stop is EXCLUSIVE, out-of-range is silently clamped.",zh:"l[起:止:步] — 止是开区间,越界静默截断不报错。",demo:["l = [0, 1, 2, 3, 4]","l[1:3] # → [1, 2] stop excluded","l[:2] l[2:] # → [0,1] [2,3,4]","l[-2:] # → [3, 4] last two","l[::-1] # → [4,3,2,1,0] reversed copy","l[9:99] # → [] no IndexError"]},{keys:["fstring","f-string","format","interpolate"],en:'f"..." embeds expressions; :.2f rounds, := width pads, {x=} prints name+value.',zh:'f"..." 内嵌表达式;:.2f 保留小数,:>5 补宽,{x=} 连名字一起打。',demo:["n, x = 7, 3.14159",'f"{n} items" # → "7 items"','f"{x:.2f}" # → "3.14"','f"{n:>5}" # → " 7" right-pad to 5','f"{n=}" # → "n=7" debug form']},{keys:["comprehension","listcomp","dictcomp","setcomp"],en:"Build a list/dict/set in one expression; the `if` filters, and it is faster than append in a loop.",zh:"一行构造 list/dict/set;if 做过滤,比循环里 append 快。",demo:["[x * x for x in range(5)] # → [0,1,4,9,16]","[x for x in l if x % 2 == 0] # filter","{k: len(k) for k in words} # dict","{x % 3 for x in l} # set (dedups)","[(i, c) for i, c in enumerate(s)] # pairs"]},{keys:["sort","sorted","key","sort by"],en:"sorted() returns a new list; l.sort() edits in place and returns None. key= is the sort field.",zh:"sorted() 返回新表;l.sort() 原地改并返回 None。key= 指定按什么排。",demo:["sorted(l) # new list, ascending","sorted(l, reverse=True) # descending","sorted(ps, key=lambda p: p[1]) # by 2nd field","sorted(ps, key=lambda p: (-p[1], p[0])) # desc, then asc tiebreak","l.sort() # in place — returns None!"]},{keys:["stdin","input","read input","fast io"],en:"input() per line; for big input read all of stdin at once — it is far faster.",zh:"input() 一行一行;数据量大时一次读完整个 stdin,快得多。",demo:["n = int(input()) # one number","a = list(map(int, input().split())) # a line of numbers","import sys","data = sys.stdin.read().split() # everything, fastest","for line in sys.stdin: # stream line by line"]},{keys:["print","output","sep","end"],en:"print joins args with a space and adds a newline; sep=/end= change both.",zh:"print 用空格连接参数并换行;sep=/end= 可改。",demo:['print(a, b) # "1 2\\n"',"print(*l) # unpack: each item, space-separated",'print(" ".join(map(str, l))) # explicit, no trailing space','print(x, end="") # no newline','print(a, b, sep=",") # "1,2"']},{keys:["2d list","grid","matrix","2d array"],en:"TRAP: [[0]*m]*n shares ONE row — every row changes together. Build rows in a comprehension.",zh:"陷阱:[[0]*m]*n 是同一行的 n 个引用,改一行全变。用推导式逐行造。",demo:["g = [[0] * 3 for _ in range(2)] # CORRECT","g[0][0] = 9 # → [[9,0,0], [0,0,0]]","","bad = [[0] * 3] * 2 # WRONG — aliased","bad[0][0] = 9 # → [[9,0,0], [9,0,0]] both rows!"]},{keys:["copy","deepcopy","alias","reference"],en:"b = a does NOT copy a list — both names point at one object. Slice or list() for a shallow copy.",zh:"b = a 不是拷贝,两个名字指同一个对象。浅拷贝用切片或 list()。",demo:["b = a # same object — a changes too","b = a[:] # shallow copy (or list(a))","import copy","b = copy.deepcopy(a) # nested lists too"]},{keys:["recursionerror","recursion limit","setrecursionlimit"],en:"CPython caps nesting near 1000 calls. Raise the limit, or rewrite as a loop (usually the right answer).",zh:"CPython 嵌套上限约 1000 层。可以抬上限,但改成循环通常才是对的。",demo:["import sys","sys.setrecursionlimit(300000) # buys depth, not speed","# depth grows with n → prefer iteration:","total = 0","for i in range(1, n + 1): total += i"]},{keys:["dict get","keyerror","default","setdefault"],en:"d[k] raises KeyError; d.get(k, default) does not. Counting? use a Counter.",zh:"d[k] 缺键报 KeyError;d.get(k, 默认) 不报。计数用 Counter。",demo:["d.get(k) # → None if missing","d.get(k, 0) + 1 # count safely","d.setdefault(k, []).append(v) # group into lists","from collections import Counter, defaultdict","Counter(l).most_common(3) # top 3 by frequency"]},{keys:["divide","division","modulo","floor","divmod"],en:"/ is always float; // floors (toward -inf, watch negatives); % follows the divisor sign.",zh:"/ 一定是浮点;// 向下取整(负数会咬人);% 的符号跟除数。",demo:["7 / 2 # → 3.5 float, even for ints","7 // 2 # → 3 floor","-7 // 2 # → -4 NOT -3","-7 % 2 # → 1 sign follows divisor","divmod(7, 2) # → (3, 1)"]},{keys:["try","except","finally","raise"],en:"Catch the narrowest type you expect; finally always runs; bare except hides real bugs.",zh:"只抓你预期的最窄类型;finally 一定执行;裸 except 会藏真 bug。",demo:["try:"," x = int(s)","except ValueError:"," x = 0","finally:"," ... # always runs"]},{keys:["unpack","star","args","kwargs","swap"],en:"* spreads an iterable, ** spreads a dict; a, *rest = l splits head from tail.",zh:"* 展开可迭代,** 展开字典;a, *rest = l 拆头尾。",demo:["a, b = b, a # swap, no temp","a, *rest = [1, 2, 3] # a=1 rest=[2,3]","f(*l) # positional spread","f(**d) # keyword spread","x, y = map(int, input().split())"]},{keys:["enumerate","zip","index in loop"],en:"enumerate gives (index, item); zip walks lists in parallel and stops at the shortest.",zh:"enumerate 给 (下标, 元素);zip 并行遍历,以最短的为准。",demo:["for i, c in enumerate(s): ...","for i, c in enumerate(s, 1): ... # start at 1","for a, b in zip(xs, ys): ...","list(zip(*rows)) # transpose a grid"]},{keys:["string methods","split","join","strip","replace"],en:"Strings are immutable — every method returns a NEW string.",zh:"字符串不可变 —— 每个方法都返回新字符串。",demo:["s.split() # on any whitespace → list",'s.split(",") # on a comma','"-".join(parts) # list → string (parts must be str)',"s.strip() # trim both ends","s.replace(a, b) # returns new; s unchanged"]},{keys:["ternary","inline if","conditional expression"],en:"Value-position if: `a if cond else b` — one expression, always needs the else.",zh:"值位置的 if:a if 条件 else b —— 是表达式,else 不能省。",demo:["x = 1 if ok else -1",'print("yes" if n > 0 else "no")',"l = [f(x) if x else 0 for x in xs]"]},{keys:["set","dedup","union","intersection","in"],en:"set() dedups and makes `in` O(1) instead of O(n) — the single biggest easy speedup.",zh:"set() 去重,并让 in 从 O(n) 变 O(1) —— 最容易拿到的一次加速。",demo:["seen = set(); seen.add(x)","if x in seen: ... # O(1); on a list this is O(n)","a | b a & b a - b # union / intersect / difference","len(set(l)) # distinct count"]},{keys:["walrus",":=","assignment expression"],en:"Assign inside an expression — mostly for while-read loops.",zh:"在表达式里赋值 —— 主要用在 while 读输入。",demo:["while (line := sys.stdin.readline()):"," ...","if (m := len(l)) > 3: print(m)"]},{keys:["lambda","anonymous function"],en:"One-expression function, no return keyword. Mostly for key= / sort.",zh:"单表达式函数,不写 return。主要给 key= / 排序用。",demo:["key=lambda p: p[1]","key=lambda p: (-p[1], p[0]) # desc then asc","f = lambda x: x * 2"]},{keys:["range","loop n times","countdown"],en:"range(stop) / range(start, stop[, step]) — stop is EXCLUSIVE.",zh:"range(止) / range(起, 止[, 步]) —— 止是开区间。",demo:["range(5) # 0 1 2 3 4","range(1, 6) # 1..5","range(10, 0, -1) # 10 down to 1","range(0, 10, 2) # 0 2 4 6 8"]},{keys:["float","rounding","precision","isclose"],en:"Binary floats are inexact: 0.1+0.2 != 0.3. Compare with a tolerance, never ==.",zh:"二进制浮点不精确:0.1+0.2 != 0.3。比较要给容差,别用 ==。",demo:["0.1 + 0.2 == 0.3 # → False","abs(a - b) < 1e-9 # compare like this","round(2.675, 2) # → 2.67 (not 2.68)",'f"{x:.6f}" # print fixed decimals']},{keys:["truthy","falsy","empty check","none"],en:'Empty containers, 0 and "" are falsy. Test None with `is None`, not ==.',zh:'空容器、0、"" 都是假。判 None 用 is None,不用 ==。',demo:["if not l: ... # empty list/str/dict","if x is None: ... # None specifically","if l: ... # non-empty","# careful: 0 and [] are both falsy but not equal"]}];export const SYNTAX_INDEX=SYNTAX_TABLE.map(e=>e.keys[0]);function t(e){return n(e).toLowerCase()}function n(e){return e.trim().replace(/^\/+/,"").replace(/[?。?]+$/,"").replace(/\s+/g," ")}export function lookupLocal(e){const n=t(e);if(!n)return null;for(const e of SYNTAX_TABLE)if(e.keys.some(e=>e===n))return e;const s=n.split(" ").filter(e=>e.length>=2);let o=null;for(const e of SYNTAX_TABLE){let t=0;for(const o of e.keys)n.includes(o)?t+=2*o.length:s.includes(o)&&(t+=o.length);t>0&&(!o||t>o.score)&&(o={e:e,score:t})}return o?o.e:null}export function nearKeys(e,n=6){const s=t(e).split(" ").filter(e=>e.length>=3),o=SYNTAX_INDEX.filter(e=>s.some(t=>e.includes(t)||t.includes(e)));return(o.length?o:SYNTAX_INDEX).slice(0,n)}export function lookupPydoc(t,s,o=14){const i=n(s);return i.length>60||!/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*){0,2}$/.test(i)?Promise.resolve(null):new Promise(n=>{let s="",r=!1;const l=e=>{r||(r=!0,n(e))};let a;try{a=e(t,["-c","\nimport io, sys, contextlib, pydoc\nq = sys.argv[1] if len(sys.argv) > 1 else ''\ncands = [q] + ([] if '.' in q else ['list.' + q, 'str.' + q, 'dict.' + q, 'set.' + q])\nfor c in cands:\n try:\n txt = pydoc.render_doc(c, renderer=pydoc.plaintext)\n except Exception:\n continue\n print('OBJ ' + c); print(txt); sys.exit(0)\nbuf = io.StringIO()\ntry:\n with contextlib.redirect_stdout(buf):\n pydoc.Helper(output=buf).help(q.upper())\n out = buf.getvalue()\nexcept Exception:\n out = ''\nif out.strip() and 'no python documentation' not in out.lower():\n print('TOPIC ' + q.upper()); print(out); sys.exit(0)\nsys.exit(1)\n",i],{stdio:["ignore","pipe","ignore"]})}catch{return void l(null)}const d=setTimeout(()=>{try{a.kill("SIGKILL")}catch{}l(null)},4e3);a.stdout?.on("data",e=>{s+=String(e)}),a.on("error",()=>{clearTimeout(d),l(null)}),a.on("close",e=>{if(clearTimeout(d),0!==e||!s.trim())return void l(null);const t=s.split("\n"),n=(t[0]??"").replace(/^(OBJ|TOPIC)\s+/,"").trim(),i=/^\s*\|?\s*(Method resolution order|Methods? (defined here|inherited)|Data (descriptors|and other attributes)|Static methods|Class methods|Readonly properties|-{3,}|_{2})/,r=[];for(const e of t.slice(1)){if(/^Python Library Documentation/.test(e))continue;if(i.test(e))break;const t=e.replace(/^\s*\|\s?/," ");(t.trim()||r.length&&r[r.length-1].trim())&&r.push(t)}for(;r.length&&!r[0].trim();)r.shift();for(;r.length&&!r[r.length-1].trim();)r.pop();l({title:n,lines:r.slice(0,o)})})})}export function renderSyntax(e,t){console.log(),console.log(chalk.bold.white(` ${e.keys[0]}`)+chalk.gray(t?" (语法速查)":" (syntax lookup)")),console.log(chalk.gray(` ${t?e.zh:e.en}`)),console.log();for(const t of e.demo)console.log(t.trim()?chalk.cyan(` ${t}`):"");console.log()}export function renderPydoc(e,t){console.log(),console.log(chalk.bold.white(` ${e.title}`)+chalk.gray(t?" (你本机 Python 的官方文档)":" (your own Python's official doc)")),console.log();for(const t of e.lines)console.log(t.trim()?chalk.white(` ${t}`):"");console.log()}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "icoa-cli",
|
|
3
|
-
"version": "2.19.
|
|
3
|
+
"version": "2.19.456",
|
|
4
4
|
"description": "ICOA CLI — The world's first CLI-native cyber & AI security olympiad terminal: AI4CTF (Day 1), CTF4AI (Day 2), VLA4CTF (Pioneer Round — embodied AI)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|