icoa-cli 2.19.611 → 2.19.613
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/README.md +2 -1
- package/dist/commands/ai4ctf.js +1 -1
- package/dist/commands/arena-eai.js +1 -1
- package/dist/commands/battle.js +1 -1
- package/dist/commands/ctf4ai-demo.js +1 -1
- package/dist/commands/ctf4vla.js +1 -1
- package/dist/commands/duck.js +1 -1
- package/dist/commands/exam.js +1 -1
- package/dist/commands/learn.js +1 -1
- package/dist/lib/learn-replies.js +1 -1
- package/dist/lib/learn-state.js +1 -1
- package/dist/lib/translations-fetcher.js +1 -1
- package/package.json +1 -1
|
@@ -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 r=[];for(const t of e){if(!t||"object"!=typeof t)continue;const e=t;if("number"==typeof e.id&&"number"==typeof e.card_number&&(r.push({id:e.id,curriculumId:"string"==typeof e.curriculum_id?e.curriculum_id:"",cardNumber:e.card_number,myText:sanitizeReplyText("string"==typeof e.my_text?e.my_text:""),sentAt:"string"==typeof e.sent_at?e.sent_at:"",replyText:"string"==typeof e.reply_text?sanitizeReplyText(e.reply_text):void 0,repliedBy:"string"==typeof e.replied_by?sanitizeReplyText(e.replied_by):void 0,repliedAt:"string"==typeof e.replied_at?e.replied_at:void 0,seen:1===e.seen||!0===e.seen}),r.length>=50))break}return r}export function parseFaqs(e){if(!Array.isArray(e))return[];const r=[];for(const t of e){if(!t||"object"!=typeof t)continue;const e=t;if("number"==typeof e.card_number&&"string"==typeof e.title&&"string"==typeof e.answer&&(r.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}),r.length>=50))break}return r}export function faqsForCard(e,r){return e.filter(e=>e.cardNumber===r)}export function shortDay(e){const r=Date.parse(e);if(Number.isNaN(r))return"";const t=new Date(r),n=e=>String(e).padStart(2,"0");return`${n(t.getMonth()+1)}-${n(t.getDate())}`}export function replySignature(e,r){const t=r?.trim()?r.trim():"";if(!t)return"";const n=e?Date.parse(e):Number.NaN;return Number.isNaN(n)?`— ${t} 🐼`:`— ${t} 🐼 · ${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,r){return e.filter(e=>e.cardNumber===r)}export function parseReplyCommand(e){if("r"!==e&&"reply"!==e&&"replies"!==e&&!/^(r|reply|replies)\s+(\d+|all)$/.test(e))return null;const r=e.replace(/^(reply|replies|r)\s*/,"").trim();return"all"===r?{target:"all"}:""===r?{target:"current"}:{target:Number(r)}}export function unreadReplies(e){return e.filter(e=>!!e.replyText&&!e.seen)}export function replyPushLine(e,r){const t=unreadReplies(e);if(0===t.length)return null;const n=Array.from(new Set(t.map(e=>e.cardNumber))).sort((e,r)=>e-r),s=n[0];if(1===n.length){const e=s;return r?`💬 你有 ${t.length} 条回复 — 第 ${e} 张卡 · 输 card ${e} 去看`:`💬 You have ${t.length} ${t.length>1?"replies":"reply"} — Card ${e} · type card ${e}`}const o=n.slice(0,4),i=n.length-o.length,p=o.join(" / ");if(r){const e=i>0?`,还有 ${i} 张未列出`:"";return`💬 你有 ${t.length} 条回复 — 第 ${p} 张卡${e} · 输 card ${s} 去看`}const a=i>0?` / …+${i} more`:"";return`💬 You have ${t.length} replies — Cards ${p}${a} · type card ${s}`}export function replyChipLabel(e,r,t,n){const s=repliesForCard(e,t),o=unreadReplies(s).length;if(o>0)return n?`💬 ${o} 条回复`:`💬 ${o} ${o>1?"replies":"reply"}`;const i=faqsForCard(r,t).length;return i>0?n?`📌 ${i} 个常见问题`:`📌 ${i} ${i>1?"questions":"question"}`:s.length>0?n?`我的报告 (${s.length})`:`my reports (${s.length})`:n?"常见问题":"questions"}export function wrapPlain(e,r,t){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>r?(n.push(t+e),e=o):e=e?`${e} ${o}`:o;n.push(t+e)}return n}function e(e,r,t,n,s){const o=[r?"📌 这张卡的常见问题":"📌 Questions on this card",n];if(e.length>0){for(const r of e)o.push(...wrapPlain(`Q: ${r.title}`,t,"")),o.push(...wrapPlain(r.answer,t-3," ")),o.push("");return o}return o.push(r?"这里还没有内容。":"Nothing shared here yet."),o.push(""),o.push(...wrapPlain(r?"有人问出好问题时,答案会贴在这里给所有人看。":"When someone asks a good question about this card, the answer gets posted here for everyone.",t,"")),s&&(o.push(""),o.push(...wrapPlain(r?"按 e 可以就这张卡提问,或报告问题。":"Press e to ask about this card, or to report a problem.",t,"")),o.push(r?"不是每条都会有回复,但每条都有人看。":"Not every report gets a reply — but every one is read.")),o}export function renderReplyLines(r,t,n,s,o){const i=Math.max(32,Math.min(o,96)),p="-".repeat(i);if("all"===n){if(0===r.length)return s?["你还没在这门课报过问题。按 e 可以报告本卡的错误或提问。"]:["You have not reported anything on this course yet. Press e to report a problem or ask about this card."];const e=[s?"💬 我的全部报告":"💬 All your reports",p];for(const t of r){const r=s?`第 ${t.cardNumber} 张卡 · `:`Card ${t.cardNumber} · `;if(t.replyText){e.push(s?`${r}已回复 · ${shortDay(t.repliedAt??"")} · ${t.repliedBy??""}`:`${r}replied · ${shortDay(t.repliedAt??"")} · ${t.repliedBy??""}`),e.push(s?` 你写的(${shortDay(t.sentAt)}):`:` you wrote (${shortDay(t.sentAt)}):`),e.push(...wrapPlain(t.myText,i-6," ")),e.push(...wrapPlain(`-> ${t.replyText}`,i-4," "));const n=replySignature(t.repliedAt,t.repliedBy);n&&e.push(` ${n}`)}else e.push(s?`${r}待回复 · ${shortDay(t.sentAt)} 已送达`:`${r}waiting · sent ${shortDay(t.sentAt)}`),e.push(s?" 你写的:":" you wrote:"),e.push(...wrapPlain(t.myText,i-6," "));e.push("")}return e.push(p),e.push(s?"不是每条都会有回复,但每条都有人看。":"Not every report gets a reply — but every one is read."),e.push(s?"e = 在这张卡再提问或报告一条":"e = ask or report something else on this card"),e}const a=repliesForCard(r,n),u=faqsForCard(t,n);if(0===a.length)return e(u,s,i,p,!0);const l=[s?`💬 第 ${n} 张卡 — 我的报告`:`💬 Card ${n} — your reports`,p],y=[...a].reverse();for(const e of y){if(e.replyText){l.push(s?`已回复 · ${shortDay(e.repliedAt??"")} · ${e.repliedBy??""}`:`replied · ${shortDay(e.repliedAt??"")} · ${e.repliedBy??""}`),l.push(s?` 你写的(${shortDay(e.sentAt)}):`:` you wrote (${shortDay(e.sentAt)}):`),l.push(...wrapPlain(e.myText,i-6," ")),l.push(...wrapPlain(`-> ${e.replyText}`,i-4," "));const r=replySignature(e.repliedAt,e.repliedBy);r&&l.push(` ${r}`)}else l.push(s?`待回复 · ${shortDay(e.sentAt)} 已送达`:`waiting · sent ${shortDay(e.sentAt)}`),l.push(s?" 你写的:":" you wrote:"),l.push(...wrapPlain(e.myText,i-6," "));l.push("")}return l.push(p),l.push(s?"不是每条都会有回复,但每条都有人看。":"Not every report gets a reply — but every one is read."),l.push(s?"e = 在这张卡再提问或报告一条":"e = ask or report something else on this card"),l.push(""),l.push(...e(u,s,i,p,!1)),l}
|
package/dist/lib/learn-state.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{join as e}from"node:path";import{getIcoaDir as t,getInstallSalt as
|
|
1
|
+
import{join as e}from"node:path";import{getIcoaDir as t,getInstallSalt as r}from"./config.js";import{deriveFileKey as n,readSignedFile as s,writeSignedFile as c,recordVerdict as o}from"./secure-store.js";const a="learn-state";function u(){return e(t(),"learn-state.json")}function i(){return n(r(),a)}const m="learn-sessions";function d(e){return(e.cardsCompleted?.length??0)>0||(e.currentCard??1)>1}export const CONTINUE_LIST_MAX=5;function l(){return e(t(),"learn-sessions.json")}function p(){return n(r(),m)}function k(){const{data:e,verdict:t}=s(l(),p());if(o(m,t),!e||"object"!=typeof e)return{};const r={};for(const[t,n]of Object.entries(e))n&&"object"==typeof n&&"string"==typeof n.token&&"string"==typeof n.curriculumId&&(r[t]=n);return r}export function listLearnSessions(){const e=loadLearnState(),t=[];e?.token&&e.curriculumId&&t.push(e);for(const[r,n]of Object.entries(k()))r!==e?.token&&t.push(n);return t.sort((e,t)=>(t.lastSeenAt??"").localeCompare(e.lastSeenAt??""))}export function activateSession(e){const t=loadLearnState(),r=k();t?.token&&t.curriculumId&&t.token!==e&&(r[t.token]=t);const n=r[e]??null;return function(e){const t=Object.keys(e);if(t.length>200){const r=t.sort((t,r)=>{const n=d(e[t])?1:0,s=d(e[r])?1:0;return n!==s?n-s:(e[t].lastSeenAt??"").localeCompare(e[r].lastSeenAt??"")}).slice(0,t.length-200);for(const t of r)delete e[t]}c(l(),e,p())}(r),n}export function loadLearnState(){const{data:e,verdict:t}=s(u(),i());return o(a,t),e}export function saveLearnState(e){e.lastSeenAt=(new Date).toISOString(),c(u(),e,i())}export function newLearnState(e,t,r){const n=(new Date).toISOString();return{token:e,curriculumId:t,currentCard:1,totalCards:r,startedAt:n,lastSeenAt:n,streakDays:1,longestStreak:1,cardsCompleted:[],mcqResults:{},checkResults:{},practicalsCompleted:[],bookmarks:[],achievements:[],totalSecondsActive:0}}export function stateFromServerEvents(e,t,r,n){const s=newLearnState(e,t,r),c=new Set;let o="";for(const e of n){const t=Number(e.card_number);!Number.isInteger(t)||t<1||t>r||("check_answered"!==e.event_type||"y"!==e.check_answer&&"n"!==e.check_answer?"mcq_answered"===e.event_type&&e.mcq_answer&&/^[ABCD]$/.test(e.mcq_answer)?(s.mcqResults[String(t)]={answer:e.mcq_answer,correct:!!e.mcq_correct,submittedAt:e.timestamp},c.add(t)):"viewed"===e.event_type?c.add(t):"practical_done"===e.event_type?(s.practicalsCompleted.push(t),c.add(t)):"bookmarked"===e.event_type&&s.bookmarks.push(t):(s.checkResults[String(t)]={answer:e.check_answer,correct:!!e.check_correct,submittedAt:e.timestamp},c.add(t)),e.timestamp&&(!o||e.timestamp<o)&&(o=e.timestamp))}return 0===c.size?null:(s.cardsCompleted=[...c].sort((e,t)=>e-t),s.currentCard=Math.min(r,Math.max(...c)+1),o&&(s.startedAt=o),s)}export function mergeServerProgress(e,t,r){if(!r||0===r.length)return;const n=new Set(e.cardsCompleted??[]);let s=0;for(const c of r){const r=Number(c.card_number);if(!Number.isInteger(r)||r<1||r>t)continue;const o=String(r);"check_answered"!==c.event_type||"y"!==c.check_answer&&"n"!==c.check_answer?"mcq_answered"===c.event_type&&c.mcq_answer&&/^[ABCD]$/.test(c.mcq_answer)?(e.mcqResults[o]||(e.mcqResults[o]={answer:c.mcq_answer,correct:!!c.mcq_correct,submittedAt:c.timestamp}),n.add(r)):"viewed"===c.event_type?n.add(r):"practical_done"===c.event_type?(e.practicalsCompleted.includes(r)||e.practicalsCompleted.push(r),n.add(r)):"bookmarked"===c.event_type&&(e.bookmarks.includes(r)||e.bookmarks.push(r)):(e.checkResults||(e.checkResults={}),e.checkResults[o]||(e.checkResults[o]={answer:c.check_answer,correct:!!c.check_correct,submittedAt:c.timestamp}),n.add(r)),n.has(r)&&(s=Math.max(s,r))}if(0===n.size)return;e.cardsCompleted=[...n].sort((e,t)=>e-t);const c=Math.min(t,s+1);c>(e.currentCard??1)&&(e.currentCard=c)}export function updateStreak(e){const t=new Date(e.lastSeenAt),r=new Date,n=Math.floor(t.getTime()/864e5),s=Math.floor(r.getTime()/864e5)-n;0!==s&&(1===s?(e.streakDays+=1,e.streakDays>e.longestStreak&&(e.longestStreak=e.streakDays)):e.streakDays=1)}export function markCardComplete(e,t){e.cardsCompleted.includes(t)||e.cardsCompleted.push(t)}export function recordMCQ(e,t,r){e.mcqResults[String(t)]=r}export function recordCheck(e,t,r){e.checkResults||(e.checkResults={}),e.checkResults[String(t)]=r}export function markPracticalComplete(e,t){e.practicalsCompleted.includes(t)||e.practicalsCompleted.push(t)}export function addAchievement(e,t){e.achievements.includes(t)||e.achievements.push(t)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import chalk from"chalk";import{spawn as t}from"node:child_process";import{existsSync as
|
|
1
|
+
import chalk from"chalk";import{spawn as t}from"node:child_process";import{existsSync as r,mkdirSync as n,writeFileSync as o}from"node:fs";import{join as a}from"node:path";import{getIcoaDir as e}from"./config.js";import{httpFetch as s}from"./http.js";const i=["https://practice.icoa2026.au/assets/translations"];export function getLangCacheDir(t){return a(e(),"translations",t)}export function isLangCached(t){if("en"===t)return!0;const n=getLangCacheDir(t);return r(a(n,".cached"))}export async function ensureLangCache(r,e={}){if("en"===r)return!0;if(isLangCached(r))return!0;const c=getLangCacheDir(r);n(c,{recursive:!0}),e.silent||console.log(chalk.gray(` Fetching ${r} translation pack ...`));let l=null;for(const n of i)try{const i=`${n}/${r}.tar.gz`,f=await s(i);if(!f.ok){l=`HTTP ${f.status}`;continue}const g=a(c,`${r}.tar.gz`);return o(g,Buffer.from(await f.arrayBuffer())),await new Promise((r,n)=>{const o=t("tar",["-xzf",g,"-C",c],{stdio:"ignore"});o.on("close",t=>0===t?r():n(new Error(`tar exit ${t}`))),o.on("error",n)}),o(a(c,".cached"),(new Date).toISOString()),e.silent||console.log(chalk.green(` ✓ ${r} translations cached at ~/.icoa/translations/${r}/`)),!0}catch(t){l=t instanceof Error?t.message:String(t)}return e.silent||console.log(chalk.yellow(` Translation pack not available (${l??"all mirrors unreachable"}). The interface stays in English; per-question translations fall back to the on-the-fly translator.`)),!1}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "icoa-cli",
|
|
3
|
-
"version": "2.19.
|
|
3
|
+
"version": "2.19.613",
|
|
4
4
|
"description": "ICOA CLI — AI-native competition runtime of the International Cyber Olympiad in AI: learn, practise, and compete in AI & cybersecurity from the terminal (AI4CTF · CTF4AI · CTF4EAI)",
|
|
5
5
|
"author": "Charlie Zhu",
|
|
6
6
|
"type": "module",
|