icoa-cli 2.19.459 → 2.19.461
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/commands/ref.js +1 -1
- package/dist/lib/lazy-assets.js +1 -0
- package/dist/lib/learn-curricula.js +1 -1
- package/dist/lib/learn-render.js +1 -1
- package/dist/lib/learn-replies.js +1 -1
- package/dist/lib/py-syntax.js +1 -1
- package/package.json +1 -2
- package/refs/ROPgadget.txt +0 -67
- package/refs/base64.txt +0 -63
- package/refs/bash.txt +0 -79
- package/refs/binwalk.txt +0 -43
- package/refs/bs4.txt +0 -61
- package/refs/checksec.txt +0 -57
- package/refs/curl.txt +0 -73
- package/refs/cyberchef.txt +0 -78
- package/refs/exiftool.txt +0 -50
- package/refs/ffuf.txt +0 -73
- package/refs/gcc.txt +0 -66
- package/refs/gdb.txt +0 -83
- package/refs/hashcat.txt +0 -64
- package/refs/hint.txt +0 -48
- package/refs/icoa.txt +0 -72
- package/refs/john.txt +0 -74
- package/refs/linux.txt +0 -58
- package/refs/nc.txt +0 -64
- package/refs/nmap.txt +0 -57
- package/refs/numpy.txt +0 -59
- package/refs/openssl.txt +0 -75
- package/refs/pillow.txt +0 -67
- package/refs/pwntools.txt +0 -79
- package/refs/pycrypto.txt +0 -77
- package/refs/python.txt +0 -103
- package/refs/r2.txt +0 -85
- package/refs/regex.txt +0 -73
- package/refs/requests.txt +0 -83
- package/refs/rules.txt +0 -28
- package/refs/scapy.txt +0 -80
- package/refs/sqlmap.txt +0 -69
- package/refs/steghide.txt +0 -71
- package/refs/struct.txt +0 -61
- package/refs/sympy.txt +0 -77
- package/refs/tshark.txt +0 -65
- package/refs/vim.txt +0 -74
- package/refs/volatility.txt +0 -41
- package/refs/z3.txt +0 -78
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 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()}
|
|
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 q=[];export function setLearnReplies(o){q=o}let j=[];export function setLearnFaqs(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(q,j,e.number,t),b=m.startsWith("💬");y.push((b?chalk.bold.yellow("[r]"):chalk.gray("[r]"))+(b?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 p=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>p?(console.log(" "+f),f=e,$=n):(f=f?f+d+e:e,$=f===e?n:$+2+n)}f&&console.log(" "+f),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,p))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 S(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 S(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 S(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()}
|
|
@@ -1 +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
|
|
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 t=[];for(const r of e){if(!r||"object"!=typeof r)continue;const e=r;if("number"==typeof e.id&&"number"==typeof e.card_number&&(t.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}),t.length>=50))break}return t}export function parseFaqs(e){if(!Array.isArray(e))return[];const t=[];for(const r of e){if(!r||"object"!=typeof r)continue;const e=r;if("number"==typeof e.card_number&&"string"==typeof e.title&&"string"==typeof e.answer&&(t.push({cardNumber:e.card_number,title:sanitizeReplyText(e.title),answer:sanitizeReplyText(e.answer),repliedBy:"string"==typeof e.replied_by?sanitizeReplyText(e.replied_by):void 0,repliedAt:"string"==typeof e.replied_at?e.replied_at:void 0}),t.length>=50))break}return t}export function faqsForCard(e,t){return e.filter(e=>e.cardNumber===t)}export function shortDay(e){const t=Date.parse(e);if(Number.isNaN(t))return"";const r=new Date(t),n=e=>String(e).padStart(2,"0");return`${n(r.getMonth()+1)}-${n(r.getDate())}`}export function replySignature(e,t){const r=t?.trim()?t.trim():"";if(!r)return"";const n=e?Date.parse(e):Number.NaN;return Number.isNaN(n)?`— ${r} 🐼`:`— ${r} 🐼 · ${new Intl.DateTimeFormat("en-AU",{timeZone:"Australia/Sydney",hour:"2-digit",minute:"2-digit",hour12:!1}).format(new Date(n))} Sydney`}export function repliesForCard(e,t){return e.filter(e=>e.cardNumber===t)}export function parseReplyCommand(e){if("r"!==e&&"reply"!==e&&"replies"!==e&&!/^(r|reply|replies)\s+(\d+|all)$/.test(e))return null;const t=e.replace(/^(reply|replies|r)\s*/,"").trim();return"all"===t?{target:"all"}:""===t?{target:"current"}:{target:Number(t)}}export function unreadReplies(e){return e.filter(e=>!!e.replyText&&!e.seen)}export function replyPushLine(e,t){const r=unreadReplies(e);if(0===r.length)return null;const n=Array.from(new Set(r.map(e=>e.cardNumber))).sort((e,t)=>e-t),s=n[0];if(1===n.length){const e=s;return t?`💬 你有 ${r.length} 条回复 — 第 ${e} 张卡 · 输 card ${e} 去看`:`💬 You have ${r.length} ${r.length>1?"replies":"reply"} — Card ${e} · type card ${e}`}const o=n.slice(0,4),i=n.length-o.length,p=o.join(" / ");if(t){const e=i>0?`,还有 ${i} 张未列出`:"";return`💬 你有 ${r.length} 条回复 — 第 ${p} 张卡${e} · 输 card ${s} 去看`}const u=i>0?` / …+${i} more`:"";return`💬 You have ${r.length} replies — Cards ${p}${u} · type card ${s}`}export function replyChipLabel(e,t,r,n){const s=repliesForCard(e,r),o=unreadReplies(s).length;if(o>0)return n?`💬 ${o} 条回复`:`💬 ${o} ${o>1?"replies":"reply"}`;const i=faqsForCard(t,r).length;return i>0?n?`📌 ${i} 个常见问题`:`📌 ${i} ${i>1?"questions":"question"}`:s.length>0?n?`我的报告 (${s.length})`:`my reports (${s.length})`:n?"常见问题":"questions"}function e(e,t,r){const n=[];for(const s of e.split("\n")){let e="";for(const o of s.split(/\s+/).filter(Boolean))e&&e.length+1+o.length>t?(n.push(r+e),e=o):e=e?`${e} ${o}`:o;n.push(r+e)}return n}function t(t,r,n,s,o){const i=[r?"📌 这张卡的常见问题":"📌 Questions on this card",s];if(t.length>0){for(const r of t)i.push(...e(`Q: ${r.title}`,n,"")),i.push(...e(r.answer,n-3," ")),i.push("");return i}return i.push(r?"这里还没有内容。":"Nothing shared here yet."),i.push(""),i.push(...e(r?"有人问出好问题时,答案会贴在这里给所有人看。":"When someone asks a good question about this card, the answer gets posted here for everyone.",n,"")),o&&(i.push(""),i.push(...e(r?"按 e 可以就这张卡提问,或报告问题。":"Press e to ask about this card, or to report a problem.",n,"")),i.push(r?"不是每条都会有回复,但每条都有人看。":"Not every report gets a reply — but every one is read.")),i}export function renderReplyLines(r,n,s,o,i){const p=Math.max(32,Math.min(i,96)),u="-".repeat(p);if("all"===s){if(0===r.length)return o?["你还没在这门课报过问题。按 e 可以报告本卡的错误或提问。"]:["You have not reported anything on this course yet. Press e to report a problem or ask about this card."];const t=[o?"💬 我的全部报告":"💬 All your reports",u];for(const n of r){const r=o?`第 ${n.cardNumber} 张卡 · `:`Card ${n.cardNumber} · `;if(n.replyText){t.push(o?`${r}已回复 · ${shortDay(n.repliedAt??"")} · ${n.repliedBy??""}`:`${r}replied · ${shortDay(n.repliedAt??"")} · ${n.repliedBy??""}`),t.push(o?` 你写的(${shortDay(n.sentAt)}):`:` you wrote (${shortDay(n.sentAt)}):`),t.push(...e(n.myText,p-6," ")),t.push(...e(`-> ${n.replyText}`,p-4," "));const s=replySignature(n.repliedAt,n.repliedBy);s&&t.push(` ${s}`)}else t.push(o?`${r}待回复 · ${shortDay(n.sentAt)} 已送达`:`${r}waiting · sent ${shortDay(n.sentAt)}`),t.push(o?" 你写的:":" you wrote:"),t.push(...e(n.myText,p-6," "));t.push("")}return t.push(u),t.push(o?"不是每条都会有回复,但每条都有人看。":"Not every report gets a reply — but every one is read."),t.push(o?"e = 在这张卡再报一条":"e = report something else on this card"),t}const a=repliesForCard(r,s),l=faqsForCard(n,s);if(0===a.length)return t(l,o,p,u,!0);const y=[o?`💬 第 ${s} 张卡 — 我的报告`:`💬 Card ${s} — your reports`,u],h=[...a].reverse();for(const t of h){if(t.replyText){y.push(o?`已回复 · ${shortDay(t.repliedAt??"")} · ${t.repliedBy??""}`:`replied · ${shortDay(t.repliedAt??"")} · ${t.repliedBy??""}`),y.push(o?` 你写的(${shortDay(t.sentAt)}):`:` you wrote (${shortDay(t.sentAt)}):`),y.push(...e(t.myText,p-6," ")),y.push(...e(`-> ${t.replyText}`,p-4," "));const r=replySignature(t.repliedAt,t.repliedBy);r&&y.push(` ${r}`)}else y.push(o?`待回复 · ${shortDay(t.sentAt)} 已送达`:`waiting · sent ${shortDay(t.sentAt)}`),y.push(o?" 你写的:":" you wrote:"),y.push(...e(t.myText,p-6," "));y.push("")}return y.push(u),y.push(o?"不是每条都会有回复,但每条都有人看。":"Not every report gets a reply — but every one is read."),y.push(o?"e = 在这张卡再报一条":"e = report something else on this card"),y.push(""),y.push(...t(l,o,p,u,!1)),y}
|
package/dist/lib/py-syntax.js
CHANGED
|
@@ -1 +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:["list","array","lst"],en:"The workhorse sequence: ordered, mutable, mixed types. End operations are O(1), front operations O(n).",zh:"最常用的序列:有序、可变、可混类型。尾部操作 O(1),头部操作 O(n)。",demo:["l = [] l = [1, 2, 3]","l = [0] * n # n zeros","l.append(x) # add at end O(1)","l[0] l[-1] len(l) # first, last, size O(1)","for i, x in enumerate(l): ...","# O(n) traps: l.pop(0) · l.insert(0,x) · x in l"]},{keys:["append","extend","insert","add to list"],en:"append adds ONE item; extend adds each item of an iterable; insert shifts everything after it.",zh:"append 加一个;extend 把可迭代对象逐个加进来;insert 会把后面全部搬移。",demo:["l = [1, 2]","l.append([3, 4]) # → [1, 2, [3, 4]] ONE item (a list!)","l = [1, 2]","l.extend([3, 4]) # → [1, 2, 3, 4] each item","l.insert(0, 9) # → [9, 1, 2, 3, 4] O(n), shifts all"]},{keys:["remove","clear","count","index"],en:"remove deletes the FIRST match by value (ValueError if absent); index finds its position; count tallies.",zh:"remove 按值删第一个匹配(没有会 ValueError);index 找位置;count 数个数。",demo:["l = [5, 7, 5]","l.remove(5) # → [7, 5] first match only","l.index(5) # → 1 ValueError if absent","l.count(5) # → 1","l.clear() # → [] same as del l[:]","if x in l: l.remove(x) # guard against ValueError"]},{keys:["dict","hashmap","map","key value"],en:"Key→value with O(1) lookup. Iterating gives keys; use .items() for pairs.",zh:"键→值,查找 O(1)。直接迭代给的是键,要键值对用 .items()。",demo:['d = {} d = {"a": 1}','d["b"] = 2 # insert or overwrite',"for k in d: ... # keys","for k, v in d.items(): ...",'"a" in d # O(1) — checks KEYS, not values',"len(d) d.keys() d.values()"]},{keys:["tuple","immutable","pair"],en:"Like a list but frozen — so it can be a dict key or go in a set. Parentheses optional.",zh:"像 list 但不可改 —— 所以能当 dict 的键、能进 set。括号可省。",demo:["t = (1, 2) t = 1, 2 # same thing","t[0] # read ok","t[0] = 9 # TypeError — immutable","seen = {(r, c)} # a list here would fail","d[(x, y)] = v # coordinate as a key"]},{keys:["deque","queue","bfs","front queue"],en:"THE fix for l.pop(0): popping from either end is O(1). This is what BFS needs.",zh:"l.pop(0) 的正解:两端弹出都是 O(1)。BFS 要的就是它。",demo:["from collections import deque","q = deque([start])","q.append(x) # push right O(1)","q.popleft() # pop left O(1) ← list is O(n)","q.appendleft(x) q.pop()","while q: node = q.popleft()"]},{keys:["heapq","heap","priority queue","smallest"],en:"A min-heap on a plain list: push/pop are O(log n), smallest is always at [0]. Negate for a max-heap.",zh:"建在普通 list 上的小顶堆:push/pop 都是 O(log n),最小值永远在 [0]。取负数即大顶堆。",demo:["import heapq","h = []","heapq.heappush(h, (cost, node)) # tuples sort by first","cost, node = heapq.heappop(h) # smallest","h[0] # peek, no removal","heapq.heappush(h, -x) # max-heap: negate"]},{keys:["bisect","binary search","sorted insert"],en:"Binary search on an already-SORTED list — O(log n). bisect_left is the insertion point.",zh:"在**已排序**的 list 上二分 —— O(log n)。bisect_left 给的是插入位置。",demo:["import bisect","a = [1, 3, 5, 7]","bisect.bisect_left(a, 5) # → 2 first index >= 5","bisect.bisect_right(a, 5) # → 3 first index > 5","bisect.insort(a, 4) # → [1,3,4,5,7] keeps order"]},{keys:["cache","memo","lru_cache","memoize"],en:"One decorator turns exponential recursion into linear — the biggest single speedup in contest Python.",zh:"一个装饰器把指数级递归变线性 —— 竞赛 Python 里单点提速最大的一招。",demo:["from functools import cache","","@cache","def fib(n):"," return n if n < 2 else fib(n-1) + fib(n-2)","# fib(100) instant; without @cache it never finishes"]},{keys:["math","gcd","sqrt","inf","factorial"],en:"Integer-safe helpers. Use math.isqrt for exact integer square roots and math.inf as a starting bound.",zh:"整数安全的工具。整数开方用 math.isqrt,起始边界用 math.inf。",demo:["import math","math.gcd(12, 18) # → 6 lcm(a,b) = a*b//gcd","math.isqrt(17) # → 4 exact int, no float error","math.inf -math.inf # safe initial min / max","math.ceil(7/2) # → 4 (or -(-7//2))","math.factorial(5) # → 120"]},{keys:["itertools","permutations","combinations","product"],en:"Brute-force generators. Watch the count: permutations of 10 items is 3.6 million.",zh:"暴力枚举生成器。注意规模:10 个元素的全排列是 362 万。",demo:["from itertools import permutations, combinations, product","permutations([1,2,3]) # 6 orderings","combinations([1,2,3], 2) # (1,2) (1,3) (2,3)",'product("ab", repeat=2) # aa ab ba bb',"product(range(3), range(3)) # all grid cells"]},{keys:["any","all","exists"],en:"any = at least one true; all = every one true (and all([]) is True). Both short-circuit.",zh:"any = 至少一个真;all = 全部为真(空表 all([]) 是 True)。两者都短路。",demo:["any(x < 0 for x in l) # is there a negative?","all(x > 0 for x in l) # are they all positive?","all([]) # → True (careful!)","if any(c.isdigit() for c in s): ..."]},{keys:["min","max","sum","total"],en:"Aggregate a whole iterable in one call; key= picks the field, default= saves you from empty input.",zh:"一次聚合整个可迭代对象;key= 指定按什么比,default= 防空输入报错。",demo:["sum(l) min(l) max(l)","max(l, default=0) # empty-safe","min(ps, key=lambda p: p[1]) # by 2nd field","max(range(n), key=lambda i: a[i]) # argmax","sum(x*x for x in l) # no list built"]},{keys:["map","filter","convert list"],en:"map applies a function to every item, filter keeps the truthy ones. Both are lazy — wrap in list() to see them.",zh:"map 对每项应用函数,filter 留下为真的。两者惰性 —— 要看内容得 list() 包一层。",demo:["list(map(int, input().split())) # the standard input line","list(map(str, l)) # ints → strings","list(filter(None, l)) # drop falsy items","# a comprehension is usually clearer:","[int(x) for x in parts]"]},{keys:["join","build string","concatenate"],en:"TRAP: s += x in a loop is O(n squared). Collect in a list, join once.",zh:"陷阱:循环里 s += x 是 O(n²)。先收进 list,最后 join 一次。",demo:["parts = []","for x in l: parts.append(str(x))",'out = " ".join(parts) # ONE pass — correct',"","# slow: rebuilds the whole string every iteration","for x in l: out += str(x)"]},{keys:["reverse","reversed","backwards"],en:"l[::-1] gives a reversed COPY; l.reverse() flips in place and returns None; reversed() is a lazy view.",zh:"l[::-1] 是反转的**拷贝**;l.reverse() 原地反转并返回 None;reversed() 是惰性视图。",demo:["l[::-1] # new reversed list (works on str too)","l.reverse() # in place — returns None!","for x in reversed(l): ... # no copy made","s[::-1] == s # palindrome check"]},{keys:["grid","neighbors","4 directions","flood"],en:"The standard 4-neighbour walk with a bounds guard — the shape almost every grid problem needs.",zh:"标准四邻域遍历 + 越界判断 —— 几乎每道网格题都要这个形状。",demo:["R, C = len(g), len(g[0])","for dr, dc in ((1,0), (-1,0), (0,1), (0,-1)):"," nr, nc = r + dr, c + dc"," if 0 <= nr < R and 0 <= nc < C:"," ... # g[nr][nc]","# 8 dirs: add (1,1) (1,-1) (-1,1) (-1,-1)"]},{keys:["break","continue","while","loop control"],en:"break leaves the loop, continue skips to the next round; both act on the INNERMOST loop only.",zh:"break 跳出循环,continue 跳到下一轮;两者都只作用于**最内层**循环。",demo:["for x in l:"," if x < 0: continue # skip this one"," if x > 99: break # stop entirely","while True:"," if done: break # the usual exit","# leaving 2 loops: use a flag, or return from a function"]},{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:["counter","frequency","most common","tally"],en:'Counts everything in one call and ranks it — no loop, no dict setup. The fastest way to answer "which appears most".',zh:"一行统计全部并排序 —— 不用循环、不用先建 dict。回答「哪个出现最多」最快的办法。",demo:["from collections import Counter",'c = Counter("hello") # {"l":2,"h":1,"e":1,"o":1}',"c = Counter(l) # works on any iterable","c.most_common(3) # top 3 as (item, count) pairs","c.most_common(1)[0][0] # THE most common item",'c["z"] # → 0, never a KeyError']},{keys:["defaultdict","group by","adjacency","graph"],en:'A dict that creates the empty value for you — how you build a graph or group items without any "if key in" checks.',zh:"会自动建空值的 dict —— 建图或分组时不用再写「if key in」。",demo:["from collections import defaultdict","g = defaultdict(list)","g[a].append(b) # no setup needed","cnt = defaultdict(int)","cnt[k] += 1 # starts at 0","groups = defaultdict(list)","for w in words: groups[len(w)].append(w)"]},{keys:["stderr","debug print","debugging"],en:"Print debug lines to stderr — the judge only reads stdout, so your prints cannot cause a wrong answer.",zh:"调试信息打到 stderr —— 判题机只读 stdout,这样你的打印**不会**把答案judge成错。",demo:["import sys",'print("here:", x, file=sys.stderr) # safe to leave in',"print(answer) # stdout = the answer","# a stray print() to stdout is a classic WA cause"]},{keys:["assert","sanity check","test"],en:"Cheap self-test while writing: it raises the moment an assumption breaks, right where it broke.",zh:"写题时最便宜的自测:假设一破就在破的那一行抛出。",demo:["assert len(a) == n",'assert 1 <= x <= 1000, f"x out of range: {x}"',"# sample check:",'assert solve("5") == "24"',"# python -O disables asserts; judges do not use -O"]},{keys:["timing","perf_counter","how slow","tle"],en:"Measure before you optimise. If a local run of the max input is over ~1s, expect TLE.",zh:"先量再优化。最大输入本地跑超过约 1 秒,就要预期 TLE。",demo:["import time","t = time.perf_counter()","...","print(time.perf_counter() - t, file=sys.stderr)","# rule of thumb: CPython ≈ 10^7 simple ops per second"]},{keys:["test cases","t cases","multiple inputs"],en:"The standard shape when the first line is how many cases follow.",zh:"第一行给「有几组数据」时的标准写法。",demo:["import sys","data = sys.stdin.read().split()","i = 0","t = int(data[i]); i += 1","for _ in range(t):"," n = int(data[i]); i += 1"," ... # print one answer per case"]},{keys:["read grid","read matrix","2d input"],en:"Read R rows of a grid, as characters or as numbers.",zh:"读入 R 行网格 —— 字符版和数字版。",demo:["R, C = map(int, input().split())","g = [input() for _ in range(R)] # chars: g[r][c]","g = [list(input()) for _ in range(R)] # mutable chars","g = [list(map(int, input().split()))"," for _ in range(R)] # numbers"]},{keys:["ord","chr","char to int","letter index"],en:'Letters ↔ numbers. ord(c) - ord("a") turns a lowercase letter into 0..25.',zh:'字母↔数字。ord(c) - ord("a") 把小写字母变成 0..25。',demo:['ord("a") # → 97','chr(97) # → "a"','ord(c) - ord("a") # → 0..25 bucket index',"cnt = [0] * 26",'for c in s: cnt[ord(c) - ord("a")] += 1']},{keys:["isdigit","isalpha","char type","validate"],en:"Character/string classification — all return True/False and are true for the WHOLE string.",zh:"字符/字符串分类 —— 返回布尔,且要求**整串**都满足。",demo:['"42".isdigit() # → True','"4a".isdigit() # → False (whole string)',"c.isalpha() c.isalnum() c.isupper() c.islower()","s.lower() s.upper()",'if s.lstrip("-").isdigit(): n = int(s) # signed check']},{keys:["pow","modulo power","mod","big numbers"],en:"pow(a, b, m) is modular exponentiation — fast and never overflows. Python ints have no size limit anyway.",zh:"pow(a, b, m) 是模幂 —— 快且不溢出。Python 整数本身也没有位数上限。",demo:["pow(2, 100) # exact, arbitrary size","pow(2, 1000000, 10**9+7) # instant, modular","MOD = 10**9 + 7","ans = ans * x % MOD # keep it reduced every step","pow(a, MOD-2, MOD) # modular inverse (MOD prime)"]},{keys:["prefix sum","range sum","cumulative"],en:'Build once in O(n), then any range sum is O(1). The standard answer to "sum of a[i..j], many queries".',zh:"O(n) 建一次,之后任意区间和都是 O(1)。「多次查询 a[i..j] 的和」的标准解。",demo:["p = [0] * (n + 1)","for i in range(n): p[i+1] = p[i] + a[i]","p[j+1] - p[i] # sum of a[i..j] O(1)","","from itertools import accumulate","p = list(accumulate(a, initial=0)) # same thing"]},{keys:["dfs","stack","iterative dfs"],en:"DFS with an explicit stack — no recursion, so no RecursionError however deep the graph.",zh:"用显式栈做 DFS —— 不递归,所以图再深也不会 RecursionError。",demo:["seen = set([start])","st = [start]","while st:"," u = st.pop()"," for v in g[u]:"," if v not in seen:"," seen.add(v); st.append(v)"]},{keys:["union find","dsu","connected"],en:'Disjoint-set union: near-O(1) "are these connected?" and "join these two".',zh:"并查集:近 O(1) 回答「这两个连通吗」和「把这两个合并」。",demo:["p = list(range(n))","def find(x):"," while p[x] != x:"," p[x] = p[p[x]]; x = p[x] # path halving"," return x","def union(a, b): p[find(a)] = find(b)","find(a) == find(b) # same component?"]},{keys:["readline","strip","trailing newline"],en:"sys.stdin.readline is faster than input() but KEEPS the newline — strip it or int() will still work but string compares will not.",zh:"sys.stdin.readline 比 input() 快,但**保留换行** —— 要 strip,否则字符串比较会失败(int() 仍能用)。",demo:["import sys","input = sys.stdin.readline # rebind once, then use input()","s = input().strip() # strip for STRINGS",'n = int(input()) # int() tolerates "\\n"','# forgetting .strip() breaks s == "YES" silently']},{keys:["flush","output speed","interactive"],en:"Many prints are slow — build one string and print it once. Interactive problems need flush=True.",zh:"大量 print 很慢 —— 攒成一个字符串一次打。交互题必须 flush=True。",demo:["out = []","for x in answers: out.append(str(x))",'print("\\n".join(out)) # ONE write — much faster',"","print(guess, flush=True) # interactive problems only"]},{keys:["dedup","unique","keep order"],en:"set() dedups but loses order; dict.fromkeys dedups and KEEPS first-seen order.",zh:"set() 去重但丢顺序;dict.fromkeys 去重并**保留首次出现的顺序**。",demo:["list(set(l)) # unique, order LOST","list(dict.fromkeys(l)) # unique, order KEPT","sorted(set(l)) # unique + sorted","len(l) != len(set(l)) # are there duplicates?"]},{keys:["transpose","rotate","matrix ops"],en:"Transpose and 90-degree rotation in one line each — zip(*g) is the trick.",zh:"转置与 90 度旋转各一行 —— 关键是 zip(*g)。",demo:["list(zip(*g)) # transpose (rows of tuples)","[list(r) for r in zip(*g)] # transpose, mutable","[list(r) for r in zip(*g[::-1])] # rotate 90° clockwise","[list(r)[::-1] for r in zip(*g)] # rotate 90° anticlockwise"]},{keys:["scope","global","nonlocal","unboundlocal"],en:"Assigning to a name inside a function makes it LOCAL for the whole function — that is the UnboundLocalError.",zh:"在函数里给某个名字赋值,它在整个函数内就是**局部**的 —— 这就是 UnboundLocalError 的来源。",demo:["total = 0","def f():"," total += 1 # UnboundLocalError!","def g():"," global total"," total += 1 # ok","# reading without assigning never needs global"]},{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]);const t={list:["Types","类型"],dict:["Types","类型"],tuple:["Types","类型"],set:["Types","类型"],"string methods":["Types","类型"],append:["Add / remove","增删改"],remove:["Add / remove","增删改"],pop:["Add / remove","增删改"],del:["Add / remove","增删改"],slice:["Add / remove","增删改"],copy:["Add / remove","增删改"],dedup:["Add / remove","增删改"],counter:["Data structures","数据结构"],defaultdict:["Data structures","数据结构"],deque:["Data structures","数据结构"],heapq:["Data structures","数据结构"],"union find":["Data structures","数据结构"],dfs:["Data structures","数据结构"],comprehension:["Loops","循环与推导"],enumerate:["Loops","循环与推导"],range:["Loops","循环与推导"],break:["Loops","循环与推导"],any:["Loops","循环与推导"],map:["Loops","循环与推导"],ternary:["Loops","循环与推导"],lambda:["Loops","循环与推导"],walrus:["Loops","循环与推导"],sort:["Order / search","排序与查找"],min:["Order / search","排序与查找"],bisect:["Order / search","排序与查找"],reverse:["Order / search","排序与查找"],stdin:["Input / output","输入输出"],print:["Input / output","输入输出"],readline:["Input / output","输入输出"],flush:["Input / output","输入输出"],"read grid":["Input / output","输入输出"],"test cases":["Input / output","输入输出"],fstring:["Input / output","输入输出"],join:["Input / output","输入输出"],divide:["Numbers / math","数学"],float:["Numbers / math","数学"],math:["Numbers / math","数学"],pow:["Numbers / math","数学"],"prefix sum":["Numbers / math","数学"],ord:["Characters","字符"],isdigit:["Characters","字符"],grid:["Grid / matrix","网格与矩阵"],"2d list":["Grid / matrix","网格与矩阵"],transpose:["Grid / matrix","网格与矩阵"],itertools:["Brute force","暴力枚举"],cache:["Speed / traps","性能与陷阱"],recursionerror:["Speed / traps","性能与陷阱"],timing:["Speed / traps","性能与陷阱"],scope:["Speed / traps","性能与陷阱"],truthy:["Speed / traps","性能与陷阱"],"dict get":["Speed / traps","性能与陷阱"],stderr:["Debug / test","调试与自测"],assert:["Debug / test","调试与自测"],try:["Debug / test","调试与自测"],unpack:["Misc","其他"]},s=["Types","Add / remove","Data structures","Loops","Order / search","Input / output","Numbers / math","Characters","Grid / matrix","Brute force","Speed / traps","Debug / test","Misc"];export function renderIndex(e){const n=new Map;for(const o of SYNTAX_TABLE){const[r,i]=t[o.keys[0]]??["Misc","其他"],a=e?i:r,l=`${s.indexOf(r)}\0${a}`;n.has(l)||n.set(l,[]),n.get(l)?.push(o)}const o=[...n.keys()].sort((e,t)=>{const s=Number.parseInt(e.split("\0")[0],10),n=Number.parseInt(t.split("\0")[0],10);return(s<0?99:s)-(n<0?99:n)});console.log(),console.log(chalk.bold.white(e?` /syntax 全部关键词 — ${SYNTAX_TABLE.length} 条`:` /syntax keyword index — ${SYNTAX_TABLE.length} entries`)),console.log(chalk.gray(e?" 左边是主词,右边任一别名也能敲。方法名(如 list.pop)直接查本机文档。":" Type the left word, or any alias on the right. Any name (list.pop) hits your own docs."));for(const e of o){const t=e.split("\0")[1];console.log(),console.log(chalk.bold.cyan(` ── ${t} ──`));for(const t of n.get(e)??[]){const e=t.keys.slice(1).join(" · ");console.log(chalk.bold.green(` ${t.keys[0].padEnd(15)}`)+chalk.gray(e))}}console.log()}function n(e){return o(e).toLowerCase()}function o(e){return e.trim().replace(/^\/+/,"").replace(/[?。?]+$/,"").replace(/\s+/g," ")}export function lookupRanked(e){const t=n(e);if(!t)return[];const s=SYNTAX_TABLE.filter(e=>e.keys.some(e=>e===t)),o=t.split(" ").filter(Boolean),r=[];for(const e of SYNTAX_TABLE){if(s.includes(e))continue;let n=0;for(const s of e.keys)s.includes(" ")?t.includes(s)&&(n+=2*s.length):o.includes(s)&&(n+=s.length);n>0&&r.push({e:e,score:n})}return r.sort((e,t)=>t.score-e.score),[...s,...r.map(e=>e.e)]}export function nearKeys(e,t=6){const s=n(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,t)}export function lookupPydoc(t,s,n=14){const r=o(s);return r.length>60||!/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*){0,2}$/.test(r)?Promise.resolve(null):new Promise(s=>{let o="",i=!1;const a=e=>{i||(i=!0,s(e))};let l;try{l=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",r],{stdio:["ignore","pipe","ignore"]})}catch{return void a(null)}const d=setTimeout(()=>{try{l.kill("SIGKILL")}catch{}a(null)},4e3);l.stdout?.on("data",e=>{o+=String(e)}),l.on("error",()=>{clearTimeout(d),a(null)}),l.on("close",e=>{if(clearTimeout(d),0!==e||!o.trim())return void a(null);const t=o.split("\n"),s=(t[0]??"").replace(/^(OBJ|TOPIC)\s+/,"").trim(),r=/^\s*\|?\s*(Method resolution order|Methods? (defined here|inherited)|Data (descriptors|and other attributes)|Static methods|Class methods|Readonly properties|-{3,}|_{2})/,i=[];for(const e of t.slice(1)){if(/^Python Library Documentation/.test(e))continue;if(r.test(e))break;const t=e.replace(/^\s*\|\s?/," ");(t.trim()||i.length&&i[i.length-1].trim())&&i.push(t)}for(;i.length&&!i[0].trim();)i.shift();for(;i.length&&!i[i.length-1].trim();)i.pop();a({title:s,lines:i.slice(0,n)})})})}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()}
|
|
1
|
+
import{spawn as e}from"node:child_process";import chalk from"chalk";import{lazyJson as t}from"./lazy-assets.js";const n=[{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:["list","array","lst"],en:"The workhorse sequence: ordered, mutable, mixed types. End operations are O(1), front operations O(n).",zh:"最常用的序列:有序、可变、可混类型。尾部操作 O(1),头部操作 O(n)。",demo:["l = [] l = [1, 2, 3]","l = [0] * n # n zeros","l.append(x) # add at end O(1)","l[0] l[-1] len(l) # first, last, size O(1)","for i, x in enumerate(l): ...","# O(n) traps: l.pop(0) · l.insert(0,x) · x in l"]},{keys:["dict","hashmap","map","key value"],en:"Key→value with O(1) lookup. Iterating gives keys; use .items() for pairs.",zh:"键→值,查找 O(1)。直接迭代给的是键,要键值对用 .items()。",demo:['d = {} d = {"a": 1}','d["b"] = 2 # insert or overwrite',"for k in d: ... # keys","for k, v in d.items(): ...",'"a" in d # O(1) — checks KEYS, not values',"len(d) d.keys() d.values()"]},{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:["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"]}];let o=n,s=!1;export async function ensureFullTable(){if(s)return!0;const e=await t("syntax-table.v1.json");if(s=!0,!e||!Array.isArray(e.entries)||!e.entries.length)return!1;const r=new Set(n.map(e=>e.keys[0]));return o=[...n,...e.entries.filter(e=>e?.keys?.length&&!r.has(e.keys[0]))],!0}export function tableSize(){return o.length}export function syntaxIndex(){return o.map(e=>e.keys[0])}const r={list:["Types","类型"],dict:["Types","类型"],tuple:["Types","类型"],set:["Types","类型"],"string methods":["Types","类型"],append:["Add / remove","增删改"],remove:["Add / remove","增删改"],pop:["Add / remove","增删改"],del:["Add / remove","增删改"],slice:["Add / remove","增删改"],copy:["Add / remove","增删改"],dedup:["Add / remove","增删改"],counter:["Data structures","数据结构"],defaultdict:["Data structures","数据结构"],deque:["Data structures","数据结构"],heapq:["Data structures","数据结构"],"union find":["Data structures","数据结构"],dfs:["Data structures","数据结构"],comprehension:["Loops","循环与推导"],enumerate:["Loops","循环与推导"],range:["Loops","循环与推导"],break:["Loops","循环与推导"],any:["Loops","循环与推导"],map:["Loops","循环与推导"],ternary:["Loops","循环与推导"],lambda:["Loops","循环与推导"],walrus:["Loops","循环与推导"],sort:["Order / search","排序与查找"],min:["Order / search","排序与查找"],bisect:["Order / search","排序与查找"],reverse:["Order / search","排序与查找"],stdin:["Input / output","输入输出"],print:["Input / output","输入输出"],readline:["Input / output","输入输出"],flush:["Input / output","输入输出"],"read grid":["Input / output","输入输出"],"test cases":["Input / output","输入输出"],fstring:["Input / output","输入输出"],join:["Input / output","输入输出"],divide:["Numbers / math","数学"],float:["Numbers / math","数学"],math:["Numbers / math","数学"],pow:["Numbers / math","数学"],"prefix sum":["Numbers / math","数学"],ord:["Characters","字符"],isdigit:["Characters","字符"],grid:["Grid / matrix","网格与矩阵"],"2d list":["Grid / matrix","网格与矩阵"],transpose:["Grid / matrix","网格与矩阵"],itertools:["Brute force","暴力枚举"],cache:["Speed / traps","性能与陷阱"],recursionerror:["Speed / traps","性能与陷阱"],timing:["Speed / traps","性能与陷阱"],scope:["Speed / traps","性能与陷阱"],truthy:["Speed / traps","性能与陷阱"],"dict get":["Speed / traps","性能与陷阱"],stderr:["Debug / test","调试与自测"],assert:["Debug / test","调试与自测"],try:["Debug / test","调试与自测"],unpack:["Misc","其他"]},i=["Types","Add / remove","Data structures","Loops","Order / search","Input / output","Numbers / math","Characters","Grid / matrix","Brute force","Speed / traps","Debug / test","Misc"];export function renderIndex(e){const t=new Map;for(const n of o){const[o,s]=r[n.keys[0]]??["Misc","其他"],l=i.indexOf(o),a=l<0?i.length:l,d=t.get(a)??{name:e?s:o,items:[]};d.items.push(n),t.set(a,d)}console.log(),console.log(chalk.bold.white(e?` /syntax 全部关键词 — ${o.length} 条`:` /syntax keyword index — ${o.length} entries`)),console.log(chalk.gray(e?" 左边是主词,右边任一别名也能敲。方法名(如 list.pop)直接查本机文档。":" Type the left word, or any alias on the right. Any name (list.pop) hits your own docs."));for(const e of[...t.keys()].sort((e,t)=>e-t)){const n=t.get(e);if(n){console.log(),console.log(chalk.bold.cyan(` ── ${n.name} ──`));for(const e of n.items)console.log(chalk.bold.green(` ${e.keys[0].padEnd(15)}`)+chalk.gray(e.keys.slice(1).join(" · ")))}}console.log()}function l(e){return a(e).toLowerCase()}function a(e){return e.trim().replace(/^\/+/,"").replace(/[?。?]+$/,"").replace(/\s+/g," ")}export function lookupRanked(e){const t=l(e);if(!t)return[];const n=o.filter(e=>e.keys.some(e=>e===t)),s=t.split(" ").filter(Boolean),r=[];for(const e of o){if(n.includes(e))continue;let o=0;for(const n of e.keys)n.includes(" ")?t.includes(n)&&(o+=2*n.length):s.includes(n)&&(o+=n.length);o>0&&r.push({e:e,score:o})}return r.sort((e,t)=>t.score-e.score),[...n,...r.map(e=>e.e)]}export function nearKeys(e,t=6){const n=l(e).split(" ").filter(e=>e.length>=3),o=syntaxIndex().filter(e=>n.some(t=>e.includes(t)||t.includes(e)));return(o.length?o:syntaxIndex()).slice(0,t)}export function lookupPydoc(t,n,o=14){const s=a(n);return s.length>60||!/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*){0,2}$/.test(s)?Promise.resolve(null):new Promise(n=>{let r="",i=!1;const l=e=>{i||(i=!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",s],{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=>{r+=String(e)}),a.on("error",()=>{clearTimeout(d),l(null)}),a.on("close",e=>{if(clearTimeout(d),0!==e||!r.trim())return void l(null);const t=r.split("\n"),n=(t[0]??"").replace(/^(OBJ|TOPIC)\s+/,"").trim(),s=/^\s*\|?\s*(Method resolution order|Methods? (defined here|inherited)|Data (descriptors|and other attributes)|Static methods|Class methods|Readonly properties|-{3,}|_{2})/,i=[];for(const e of t.slice(1)){if(/^Python Library Documentation/.test(e))continue;if(s.test(e))break;const t=e.replace(/^\s*\|\s?/," ");(t.trim()||i.length&&i[i.length-1].trim())&&i.push(t)}for(;i.length&&!i[0].trim();)i.shift();for(;i.length&&!i[i.length-1].trim();)i.pop();l({title:n,lines:i.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.461",
|
|
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": {
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
|
-
"refs",
|
|
12
11
|
"assets",
|
|
13
12
|
"docker/Dockerfile",
|
|
14
13
|
"docker/constraints.txt"
|
package/refs/ROPgadget.txt
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
ROPgadget Quick Reference
|
|
2
|
-
=========================
|
|
3
|
-
|
|
4
|
-
BASIC USAGE
|
|
5
|
-
ROPgadget --binary binary Find all gadgets
|
|
6
|
-
ROPgadget --binary binary --depth 10 Deeper search
|
|
7
|
-
|
|
8
|
-
FILTERING
|
|
9
|
-
ROPgadget --binary binary --only "pop|ret" Only pop/ret
|
|
10
|
-
ROPgadget --binary binary --only "mov|ret" Only mov/ret
|
|
11
|
-
ROPgadget --binary binary --filter "leave" Exclude leave
|
|
12
|
-
|
|
13
|
-
SEARCHING
|
|
14
|
-
ROPgadget --binary binary --string "/bin/sh" Find string
|
|
15
|
-
ROPgadget --binary binary --opcode "c3" Find by opcode
|
|
16
|
-
ROPgadget --binary binary --re "pop .* ; ret" Regex search
|
|
17
|
-
|
|
18
|
-
AUTO ROP CHAIN
|
|
19
|
-
ROPgadget --binary binary --ropchain Auto-generate chain
|
|
20
|
-
|
|
21
|
-
COMMON GADGETS TO FIND
|
|
22
|
-
# x86-64 function call setup
|
|
23
|
-
pop rdi ; ret # 1st argument
|
|
24
|
-
pop rsi ; ret # 2nd argument
|
|
25
|
-
pop rdx ; ret # 3rd argument
|
|
26
|
-
pop rax ; ret # syscall number
|
|
27
|
-
syscall ; ret # syscall
|
|
28
|
-
|
|
29
|
-
# x86 (32-bit)
|
|
30
|
-
pop eax ; ret
|
|
31
|
-
pop ebx ; ret
|
|
32
|
-
int 0x80 # syscall
|
|
33
|
-
|
|
34
|
-
# Stack pivot
|
|
35
|
-
xchg rax, rsp ; ret
|
|
36
|
-
leave ; ret
|
|
37
|
-
|
|
38
|
-
# Write-what-where
|
|
39
|
-
mov [rdi], rax ; ret
|
|
40
|
-
mov qword ptr [rsi], rdi ; ret
|
|
41
|
-
|
|
42
|
-
ROPPER (alternative tool)
|
|
43
|
-
ropper -f binary Find gadgets
|
|
44
|
-
ropper -f binary --search "pop rdi" Search specific
|
|
45
|
-
ropper -f binary --chain execve Auto chain
|
|
46
|
-
|
|
47
|
-
PWNTOOLS ROP
|
|
48
|
-
from pwn import *
|
|
49
|
-
e = ELF("./binary")
|
|
50
|
-
rop = ROP(e)
|
|
51
|
-
|
|
52
|
-
rop.find_gadget(["pop rdi", "ret"])
|
|
53
|
-
rop.find_gadget(["pop rsi", "pop r15", "ret"])
|
|
54
|
-
rop.find_gadget(["ret"]) # ret gadget for alignment
|
|
55
|
-
|
|
56
|
-
# Build chain
|
|
57
|
-
rop.raw(ret_gadget) # Stack alignment
|
|
58
|
-
rop.call("puts", [got_puts]) # Call puts(GOT[puts])
|
|
59
|
-
rop.call("main") # Return to main
|
|
60
|
-
chain = rop.chain()
|
|
61
|
-
|
|
62
|
-
COMMON CTF ROP PATTERNS
|
|
63
|
-
# ret2libc (x86-64)
|
|
64
|
-
1. Leak libc address (puts GOT via puts PLT)
|
|
65
|
-
2. Calculate libc base
|
|
66
|
-
3. Find system() and "/bin/sh" in libc
|
|
67
|
-
4. pop rdi; ret → "/bin/sh" → system()
|
package/refs/base64.txt
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
Base64 & Encoding Quick Reference
|
|
2
|
-
=================================
|
|
3
|
-
|
|
4
|
-
BASE64
|
|
5
|
-
Alphabet: A-Z a-z 0-9 + / (padding: =)
|
|
6
|
-
Encodes 3 bytes → 4 characters
|
|
7
|
-
Decodes 4 characters → 3 bytes
|
|
8
|
-
|
|
9
|
-
# Command line
|
|
10
|
-
echo -n "text" | base64 Encode
|
|
11
|
-
echo "dGV4dA==" | base64 -d Decode (Linux)
|
|
12
|
-
echo "dGV4dA==" | base64 -D Decode (macOS)
|
|
13
|
-
base64 file > encoded.txt Encode file
|
|
14
|
-
base64 -d encoded.txt > file Decode file
|
|
15
|
-
|
|
16
|
-
# Python
|
|
17
|
-
import base64
|
|
18
|
-
base64.b64encode(b"text") → b"dGV4dA=="
|
|
19
|
-
base64.b64decode(b"dGV4dA==") → b"text"
|
|
20
|
-
|
|
21
|
-
# URL-safe Base64
|
|
22
|
-
base64.urlsafe_b64encode(data) Uses - and _ instead of + and /
|
|
23
|
-
base64.urlsafe_b64decode(data)
|
|
24
|
-
|
|
25
|
-
BASE32
|
|
26
|
-
Alphabet: A-Z 2-7 (padding: =)
|
|
27
|
-
echo -n "text" | base32
|
|
28
|
-
base64.b32encode(b"text")
|
|
29
|
-
base64.b32decode(b"ORSXG5A=")
|
|
30
|
-
|
|
31
|
-
HEX
|
|
32
|
-
echo -n "text" | xxd -p Encode to hex
|
|
33
|
-
echo "74657874" | xxd -r -p Decode from hex
|
|
34
|
-
bytes.fromhex("74657874") Python decode
|
|
35
|
-
b"text".hex() Python encode
|
|
36
|
-
|
|
37
|
-
URL ENCODING
|
|
38
|
-
# Python
|
|
39
|
-
from urllib.parse import quote, unquote
|
|
40
|
-
quote("hello world") → "hello%20world"
|
|
41
|
-
unquote("hello%20world") → "hello world"
|
|
42
|
-
|
|
43
|
-
ROT13
|
|
44
|
-
echo "text" | tr 'a-zA-Z' 'n-za-mN-ZA-M'
|
|
45
|
-
import codecs; codecs.decode("grkg", "rot_13")
|
|
46
|
-
|
|
47
|
-
BINARY
|
|
48
|
-
# Python
|
|
49
|
-
bin(65) → "0b1000001"
|
|
50
|
-
int("1000001", 2) → 65
|
|
51
|
-
''.join(format(b, '08b') for b in data) Bytes to binary
|
|
52
|
-
|
|
53
|
-
ASCII TABLE (key values)
|
|
54
|
-
0x00 NULL 0x20 SPACE 0x30 '0'
|
|
55
|
-
0x09 TAB 0x41 'A' 0x61 'a'
|
|
56
|
-
0x0a LF (\n) 0x5a 'Z' 0x7a 'z'
|
|
57
|
-
0x0d CR (\r) 0x7e '~' 0x7f DEL
|
|
58
|
-
|
|
59
|
-
CTF TIPS
|
|
60
|
-
- Try "Magic" in CyberChef to auto-detect encoding
|
|
61
|
-
- Flags often have multiple layers: Base64(Hex(XOR(flag)))
|
|
62
|
-
- Look for patterns: "==" at end = base64, all hex chars = hex
|
|
63
|
-
- base64 length is always multiple of 4
|
package/refs/bash.txt
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
Bash Scripting Quick Reference
|
|
2
|
-
==============================
|
|
3
|
-
|
|
4
|
-
VARIABLES
|
|
5
|
-
NAME="value" Set variable (no spaces around =)
|
|
6
|
-
echo $NAME Use variable
|
|
7
|
-
echo "${NAME}_suffix" Variable in string
|
|
8
|
-
readonly VAR="val" Constant
|
|
9
|
-
|
|
10
|
-
CONDITIONALS
|
|
11
|
-
if [ condition ]; then
|
|
12
|
-
commands
|
|
13
|
-
elif [ condition ]; then
|
|
14
|
-
commands
|
|
15
|
-
else
|
|
16
|
-
commands
|
|
17
|
-
fi
|
|
18
|
-
|
|
19
|
-
# String comparisons
|
|
20
|
-
[ "$a" = "$b" ] Equal
|
|
21
|
-
[ "$a" != "$b" ] Not equal
|
|
22
|
-
[ -z "$a" ] Empty string
|
|
23
|
-
[ -n "$a" ] Non-empty string
|
|
24
|
-
|
|
25
|
-
# Numeric comparisons
|
|
26
|
-
[ $a -eq $b ] Equal
|
|
27
|
-
[ $a -ne $b ] Not equal
|
|
28
|
-
[ $a -lt $b ] Less than
|
|
29
|
-
[ $a -gt $b ] Greater than
|
|
30
|
-
|
|
31
|
-
# File tests
|
|
32
|
-
[ -f file ] Regular file exists
|
|
33
|
-
[ -d dir ] Directory exists
|
|
34
|
-
[ -r file ] Readable
|
|
35
|
-
[ -x file ] Executable
|
|
36
|
-
|
|
37
|
-
LOOPS
|
|
38
|
-
for i in 1 2 3; do echo $i; done
|
|
39
|
-
for f in *.txt; do cat "$f"; done
|
|
40
|
-
for ((i=0; i<10; i++)); do echo $i; done
|
|
41
|
-
while read line; do echo "$line"; done < file
|
|
42
|
-
while true; do cmd; sleep 1; done
|
|
43
|
-
|
|
44
|
-
FUNCTIONS
|
|
45
|
-
myfunc() {
|
|
46
|
-
echo "Arg1: $1, Arg2: $2"
|
|
47
|
-
return 0
|
|
48
|
-
}
|
|
49
|
-
myfunc "hello" "world"
|
|
50
|
-
|
|
51
|
-
ARRAYS
|
|
52
|
-
arr=(one two three)
|
|
53
|
-
echo ${arr[0]} First element
|
|
54
|
-
echo ${arr[@]} All elements
|
|
55
|
-
echo ${#arr[@]} Length
|
|
56
|
-
|
|
57
|
-
STRING OPERATIONS
|
|
58
|
-
${#var} String length
|
|
59
|
-
${var:0:5} Substring (offset:length)
|
|
60
|
-
${var/old/new} Replace first match
|
|
61
|
-
${var//old/new} Replace all matches
|
|
62
|
-
${var%.ext} Remove suffix
|
|
63
|
-
${var#prefix} Remove prefix
|
|
64
|
-
|
|
65
|
-
SPECIAL VARIABLES
|
|
66
|
-
$0 Script name
|
|
67
|
-
$1, $2, ... Arguments
|
|
68
|
-
$# Number of arguments
|
|
69
|
-
$@ All arguments
|
|
70
|
-
$? Last exit code
|
|
71
|
-
$$ Current PID
|
|
72
|
-
$! Last background PID
|
|
73
|
-
|
|
74
|
-
USEFUL PATTERNS
|
|
75
|
-
cmd || echo "failed" Run on failure
|
|
76
|
-
cmd && echo "ok" Run on success
|
|
77
|
-
$(command) Command substitution
|
|
78
|
-
$((1 + 2)) Arithmetic
|
|
79
|
-
cmd & Background process
|
package/refs/binwalk.txt
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
Binwalk Quick Reference
|
|
2
|
-
=======================
|
|
3
|
-
|
|
4
|
-
BASIC USAGE
|
|
5
|
-
binwalk file Scan for embedded files
|
|
6
|
-
binwalk -e file Extract embedded files
|
|
7
|
-
binwalk -Me file Recursive extraction
|
|
8
|
-
binwalk -D ".*" file Extract all file types
|
|
9
|
-
binwalk -E file Entropy analysis
|
|
10
|
-
binwalk -A file Instruction scan
|
|
11
|
-
binwalk -W file1 file2 Compare files (hexdiff)
|
|
12
|
-
|
|
13
|
-
EXTRACTION OPTIONS
|
|
14
|
-
binwalk -e file Extract to ./_file.extracted/
|
|
15
|
-
binwalk -C /tmp/out -e file Extract to custom directory
|
|
16
|
-
binwalk --dd="png:png" file Extract specific type
|
|
17
|
-
|
|
18
|
-
ENTROPY
|
|
19
|
-
binwalk -E file Show entropy graph
|
|
20
|
-
binwalk -E -J file Save entropy plot as PNG
|
|
21
|
-
|
|
22
|
-
FILTERING
|
|
23
|
-
binwalk -y "jpeg" file Only show JPEG signatures
|
|
24
|
-
binwalk -x "jpeg" file Exclude JPEG signatures
|
|
25
|
-
binwalk -R "\x89PNG" file Raw byte search
|
|
26
|
-
|
|
27
|
-
COMMON CTF PATTERNS
|
|
28
|
-
# Firmware analysis
|
|
29
|
-
binwalk -Me firmware.bin
|
|
30
|
-
|
|
31
|
-
# Find hidden files in image
|
|
32
|
-
binwalk -e image.png
|
|
33
|
-
|
|
34
|
-
# Check for appended data
|
|
35
|
-
binwalk suspicious_file
|
|
36
|
-
|
|
37
|
-
# Extract filesystem from firmware
|
|
38
|
-
binwalk -e -C ./extracted firmware.bin
|
|
39
|
-
|
|
40
|
-
RELATED TOOLS
|
|
41
|
-
foremost file Carve files by header/footer
|
|
42
|
-
foremost -t all -i file Carve all known types
|
|
43
|
-
foremost -o output/ -i file Output directory
|