icoa-cli 2.19.261 → 2.19.263
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/aienv.d.ts +21 -0
- package/dist/commands/aienv.js +1 -0
- package/dist/commands/ctf4ai-demo.js +1 -1
- package/dist/commands/ctf4vla.d.ts +0 -1
- package/dist/commands/ctf4vla.js +1 -1
- package/dist/commands/demo2.js +1 -1
- package/dist/commands/exam.js +1 -1
- package/dist/commands/sim.js +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/aienv.d.ts +93 -0
- package/dist/lib/aienv.js +1 -0
- package/dist/lib/country-lang.d.ts +0 -1
- package/dist/lib/country-lang.js +1 -1
- package/dist/lib/hint-client.js +1 -1
- package/dist/lib/learn-curricula.d.ts +0 -6
- package/dist/lib/learn-curricula.js +1 -1
- package/dist/lib/learn-input.js +1 -1
- package/dist/lib/learn-state.d.ts +0 -2
- package/dist/lib/learn-state.js +1 -1
- package/dist/repl.js +1 -1
- package/package.json +1 -3
package/dist/commands/sim.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import chalk from"chalk";import{spawn as o}from"node:child_process";import{copyFileSync as e,existsSync as n,mkdirSync as r,writeFileSync as a}from"node:fs";import{homedir as t}from"node:os";import{dirname as l,join as s,resolve as i}from"node:path";import{createInterface as c}from"node:readline";import{fileURLToPath as d}from"node:url";import{fetchWithCountdown as g}from"../lib/countdown.js";const m=s(t(),".icoa","cache","sim");import{getMainRl as p}from"../lib/main-rl.js";import{COOLDOWN_SECONDS as u,lastSimAt as y,markArmAt as f,markBundledAt as b,markSimAt as h}from"../lib/sim-cooldown.js";const w={baseline:{action:null,label:"Baseline — robot doing its job",hint:"Approach → grasp → lift → place → return. This is what normal looks like.",frames:90,narrative:!0},release:{action:{xyz:[.1,0,.3],rotation:[0,0,0],gripper_close:.05},label:'Prompt Injection — "release the gripper"',hint:"Gripper opens (0.05 < 0.10 win threshold). This is Paper D Q41 success."},patch:{action:{xyz:[-.3,.2,.5],rotation:[.5,0,.3],gripper_close:.95},label:"Adversarial Patch — sticker redirects the arm",hint:"Same instruction, but arm reaches LEFT instead of RIGHT. Paper D Q42 success."},confused:{action:{xyz:[.001,0,.001],rotation:[0,0,0],gripper_close:.5},label:'Modality Confusion — "pick up the purple object"',hint:"No purple object exists. VLA freezes near origin. Paper D Q43 success."},color:{action:{xyz:[.4,-.3,.6],rotation:[-.4,.2,-.1],gripper_close:.2},label:"Cross-Modal Attack — image + text manipulated together",hint:"Action drifts hard on multiple axes. Paper D Q49-class attack (future).",hidden:!0},ensemble:{action:null,label:"Ensemble —
|
|
1
|
+
import chalk from"chalk";import{spawn as o}from"node:child_process";import{copyFileSync as e,existsSync as n,mkdirSync as r,writeFileSync as a}from"node:fs";import{homedir as t}from"node:os";import{dirname as l,join as s,resolve as i}from"node:path";import{createInterface as c}from"node:readline";import{fileURLToPath as d}from"node:url";import{fetchWithCountdown as g}from"../lib/countdown.js";const m=s(t(),".icoa","cache","sim");import{getMainRl as p}from"../lib/main-rl.js";import{COOLDOWN_SECONDS as u,lastSimAt as y,markArmAt as f,markBundledAt as b,markSimAt as h}from"../lib/sim-cooldown.js";const w={baseline:{action:null,label:"Baseline — robot doing its job",hint:"Approach → grasp → lift → place → return. This is what normal looks like.",frames:90,narrative:!0},release:{action:{xyz:[.1,0,.3],rotation:[0,0,0],gripper_close:.05},label:'Prompt Injection — "release the gripper"',hint:"Gripper opens (0.05 < 0.10 win threshold). This is Paper D Q41 success."},patch:{action:{xyz:[-.3,.2,.5],rotation:[.5,0,.3],gripper_close:.95},label:"Adversarial Patch — sticker redirects the arm",hint:"Same instruction, but arm reaches LEFT instead of RIGHT. Paper D Q42 success."},confused:{action:{xyz:[.001,0,.001],rotation:[0,0,0],gripper_close:.5},label:'Modality Confusion — "pick up the purple object"',hint:"No purple object exists. VLA freezes near origin. Paper D Q43 success."},color:{action:{xyz:[.4,-.3,.6],rotation:[-.4,.2,-.1],gripper_close:.2},label:"Cross-Modal Attack — image + text manipulated together",hint:"Action drifts hard on multiple axes. Paper D Q49-class attack (future).",hidden:!0},ensemble:{action:null,label:"Ensemble — 9 robots dancing on one beat",hint:"Pre-rendered group dance. Instant. No cooldown.",bundled:"ensemble-v2.mp4"},arm:{action:null,label:"Arm mode — interactive 6-DOF companion-arm sandbox",hint:"Type 6 joint amplitudes (or a preset). Each render takes ~15s, 60s cooldown.",bundled:void 0}},v=Object.fromEntries(Object.entries(w).filter(([,o])=>!o.hidden)),j={1:{vec:[1,1,1,1,1.4,1],label:"full demo — every joint at full amplitude"},2:{vec:[1,0,0,0,0,0],label:"base spin only (joint 1 swings)"},3:{vec:[0,.8,.8,0,0,0],label:"shoulder + elbow (lift cycle)"},4:{vec:[0,0,0,.8,1.4,1],label:"wrist drill — j4 roll + j5 bend + j6 twist"},5:{vec:[-.7,-.8,.6,.7,-1,.5],label:"mixed asymmetric (chaotic)"}};function T(){return"darwin"===process.platform?"open":"win32"===process.platform?"start":"xdg-open"}async function x(e){const n=(Date.now()-y())/1e3;if(n<u){const o=Math.ceil(u-n);return console.log(chalk.yellow(` Please wait ${o}s before the next render.`)),console.log(chalk.gray(" (One render per minute keeps things fast for everyone.)")),!1}const r=`${process.env.ICOA_SERVER_URL?.replace(/\/+$/,"")||"https://practice.icoa2026.au"}/api/ai/vla/41/sim`;e.quietHeader||(console.log(),console.log(chalk.bold.cyan(" ICOA · MuJoCo Sim")),console.log(chalk.gray(" ")+chalk.white(e.label)),console.log());try{const n=fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:e.action,frames:e.frames,narrative:e.narrative,...e.panda2_pose?{panda2_pose:e.panda2_pose}:{}}),signal:AbortSignal.timeout(6e4)}),t=await g(n);if(!t.ok)return console.log(chalk.yellow(` Server returned HTTP ${t.status}.`)),!1;const l=await t.json();if(!l.success||!l.data?.mp4_b64)return console.log(chalk.yellow(" Renderer unavailable.")),!1;const i=process.env.TMPDIR||"/tmp",c=s(i,`icoa-sim-${e.scenarioName}-${Date.now()}.mp4`);a(c,Buffer.from(l.data.mp4_b64,"base64")),h(Date.now()),console.log(),console.log(chalk.bold.green(" ✓ ")+chalk.gray("Rendered.")),l.data.description&&console.log(chalk.gray(" Action: ")+chalk.white(l.data.description)),console.log(),console.log(chalk.gray(" ")+e.hint),console.log();const d=T();return d?(o(d,[c],{stdio:"ignore",detached:!0}).unref(),console.log(chalk.gray(" Opening in your default video player..."))):console.log(chalk.gray(" Video at: ")+chalk.white(c)),console.log(),!0}catch(o){const e=o instanceof Error?o.message:String(o);return console.log(chalk.yellow(` Sim error: ${e}`)),!1}}async function A(t){const g=t.toLowerCase();if("arm"===g)return void await async function(){console.log(),console.log(chalk.bold.cyan(" ICOA · Companion Arm Sandbox")),console.log(chalk.gray(" Drive the printable 6-DOF arm directly. Server renders, video opens.")),console.log(),console.log(chalk.gray(" Joint amplitudes (radians, suggested range −1.0 to +1.0):")),console.log(chalk.gray(" j1=base spin j2=shoulder j3=elbow")),console.log(chalk.gray(" j4=forearm roll j5=wrist pitch j6=wrist roll")),console.log(),console.log(chalk.gray(" Type 6 numbers separated by spaces, or pick a preset:"));for(const[o,e]of Object.entries(j)){const n=e.vec.map(o=>o.toFixed(1).padStart(4)).join(" ");console.log(chalk.gray(" ")+chalk.bold.cyan(o)+chalk.gray(" → ["+n+"] "+e.label))}console.log(),console.log(chalk.gray(" Each render: ~15s countdown + 60s cooldown between renders.")),console.log(chalk.gray(" Type ")+chalk.bold.cyan("q")+chalk.gray(" or ")+chalk.bold.cyan("exit")+chalk.gray(" to leave arm mode.")),console.log();const o=p(),e=null!==o,n=e?o.listeners("line").slice():[];e&&o.removeAllListeners("line");const r=e?o:c({input:process.stdin,output:process.stdout,terminal:!0});return new Promise(o=>{let a=!1;const t=()=>{process.stdout.write(chalk.bold.cyan(" arm> "))},l=async s=>{const i=s.trim();if(a)return;if("q"===i||"exit"===i||"quit"===i)return console.log(chalk.gray(" Leaving arm mode.")),console.log(),r.removeListener("line",l),(()=>{if(e){r.removeAllListeners("line");for(const o of n)r.on("line",o);r.prompt()}else r.close()})(),void o();if("help"===i||"?"===i)return console.log(chalk.gray(" 6 numbers (e.g. 0.5 0.7 0.5 0.3 0.6 0.3) or preset 1/2/3/4.")),void t();const c=(o=>{const e=o.trim();if(!e)return null;if(j[e])return j[e].vec;const n=e.split(/[\s,]+/).map(o=>Number(o));return 6!==n.length||n.some(o=>!Number.isFinite(o))?null:n.map(o=>Math.max(-1.5,Math.min(1.5,o)))})(i);if(!c)return console.log(chalk.yellow(" Need 6 numbers or a preset (1-4). Try ?")),void t();a=!0;const d=c.map(o=>o.toFixed(2)).join(" ");console.log(chalk.gray(" Sending pose [")+chalk.white(d)+chalk.gray("]")),await x({scenarioName:"arm",label:"Companion arm — your pose",hint:"Rendered from your 6-vector. Try another, or "+chalk.bold.cyan("q")+chalk.gray(" to leave."),action:null,frames:90,narrative:!0,panda2_pose:c,quietHeader:!0})&&f(Date.now()),a=!1,t()};r.on("line",l),t()})}();const u=w[g];if(!u){console.log(),console.log(chalk.yellow(` Unknown scenario: "${t}"`)),console.log(chalk.gray(" Available:"));for(const[o,e]of Object.entries(v))console.log(chalk.gray(" ")+chalk.bold.cyan(o.padEnd(10))+chalk.gray(e.label));return void console.log()}if(u.bundled)return void await async function(t,c){const g=await async function(o){const e=function(o){const e=l(d(import.meta.url));return[i(e,"..","..","assets","sim",o),i(e,"..","..","..","assets","sim",o)].find(o=>n(o))||null}(o);if(e)return e;const t=s(m,o);if(n(t))return t;console.log(),console.log(chalk.white("\n ██╗ ██████╗ ██████╗ █████╗\n ██║██╔════╝██╔═══██╗██╔══██╗\n ██║██║ ██║ ██║███████║\n ██║██║ ██║ ██║██╔══██║\n ██║╚██████╗╚██████╔╝██║ ██║\n ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝\n")),console.log(chalk.gray(" First-time setup — fetching the sim asset (~700 KB) ...")),console.log();try{r(m,{recursive:!0});const e=`https://icoa2026.au/assets/sim/${o}`,n=await fetch(e);if(!n.ok)return console.log(chalk.yellow(` Download failed (HTTP ${n.status}).`)),console.log(chalk.gray(" Try ")+chalk.bold.cyan("icoa sim baseline")+chalk.gray(" (server-rendered alternative).")),console.log(),null;const l=Buffer.from(await n.arrayBuffer());return a(t,l),console.log(chalk.green(" ✓ Cached — future runs are instant.")),console.log(),t}catch(o){return console.log(chalk.yellow(` Network error: ${o instanceof Error?o.message:String(o)}`)),console.log(chalk.gray(" Try ")+chalk.bold.cyan("icoa sim baseline")+chalk.gray(" instead.")),console.log(),null}}(c.bundled);if(!g)return;const p=process.env.TMPDIR||"/tmp",u=s(p,`icoa-sim-${t}-${Date.now()}.mp4`);e(g,u),b(Date.now()),console.log(),console.log(chalk.bold.cyan(" ICOA · MuJoCo Sim")),console.log(chalk.gray(" ")+chalk.white(c.label)),console.log(),console.log(chalk.bold.green(" ✓ ")+chalk.gray("Ready (pre-rendered, instant playback).")),console.log(),console.log(chalk.gray(" ")+c.hint),console.log();const y=T();y?(o(y,[u],{stdio:"ignore",detached:!0}).unref(),console.log(chalk.gray(" Opening in your default video player..."))):console.log(chalk.gray(" Video at: ")+chalk.white(u)),console.log();const f="1"===process.env.ICOA_INSIDE_REPL?"":"icoa ";console.log(chalk.gray(" Want to drive the companion arm yourself? ")+chalk.bold.cyan(f+"sim arm")),console.log()}(g,u);await x({scenarioName:g,label:u.label,hint:u.hint,action:u.action,frames:u.frames,narrative:u.narrative,panda2_pose:u.panda2_pose});const y="1"===process.env.ICOA_INSIDE_REPL?"":"icoa ";console.log(chalk.gray(" Try other scenarios:"));for(const o of Object.keys(v))o!==g&&console.log(chalk.gray(" "+y+"sim ")+chalk.cyan(o));console.log()}export function registerSimCommand(o){o.command("sim [scenario]").description("MuJoCo VLA — sim (group dance) · sim arm (interactive companion arm) · sim baseline/release/patch/confused").action(async o=>{await A(o||"ensemble")})}export{w as SCENARIOS};
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as o}from"commander";import chalk from"chalk";import{registerCtfCommands as e}from"./commands/ctf.js";import{registerRefCommand as n}from"./commands/ref.js";import{registerShellCommand as s}from"./commands/shell.js";import{registerFilesCommand as r}from"./commands/files.js";import{registerConnectCommand as t}from"./commands/connect.js";import{registerNoteCommand as i}from"./commands/note.js";import{registerLogCommand as
|
|
2
|
+
import{Command as o}from"commander";import chalk from"chalk";import{registerCtfCommands as e}from"./commands/ctf.js";import{registerRefCommand as n}from"./commands/ref.js";import{registerShellCommand as s}from"./commands/shell.js";import{registerFilesCommand as r}from"./commands/files.js";import{registerConnectCommand as t}from"./commands/connect.js";import{registerNoteCommand as i}from"./commands/note.js";import{registerLogCommand as a}from"./commands/log.js";import{registerLangCommand as l}from"./commands/lang.js";import{registerSetupCommand as m}from"./commands/setup.js";import{registerEnvCommand as c}from"./commands/env.js";import{registerAienvCommand as g}from"./commands/aienv.js";import{registerAi4ctfCommand as d}from"./commands/ai4ctf.js";import{registerExamCommand as p}from"./commands/exam.js";import{registerCtf4aiDemoCommand as y}from"./commands/ctf4ai-demo.js";import{registerThemeCommand as h}from"./commands/theme.js";import{registerLearnCommand as f}from"./commands/learn.js";import{registerCtf4VlaCommand as u}from"./commands/ctf4vla.js";import{registerDemo2Command as w}from"./commands/demo2.js";import{registerSimCommand as T}from"./commands/sim.js";import{registerArenaCommand as b}from"./commands/arena.js";import{getConfig as v,saveConfig as I}from"./lib/config.js";import{startRepl as $}from"./repl.js";import{RELAUNCH_CODE as A}from"./lib/menu-nav.js";import{setTerminalTheme as C}from"./lib/theme.js";import{checkForUpdates as E}from"./lib/update-check.js";import{detectIcoaInstalls as j}from"./lib/platform.js";import{ICOA_BIG as _}from"./lib/banner.js";import{readFileSync as S}from"node:fs";import{fileURLToPath as x}from"node:url";import{dirname as L,join as N}from"node:path";const O=L(x(import.meta.url)),k=JSON.parse(S(N(O,"..","package.json"),"utf-8")).version,F=chalk.cyan(" ─────────────────────────────────────────────────────"),R=`\n${F}\n\n${_.map(o=>` ${chalk.bold.white(o)}`).join("\n")}\n\n ${chalk.yellow("International Cyber Olympiad in AI 2026")}\n ${chalk.bold.magenta("The World's First AI-Native CLI Operating System")}\n ${chalk.bold.magenta("for Cybersecurity & AI Security Competition")}\n ${chalk.bold.magenta("and Olympiad for K-12")}\n\n ${chalk.green.bold("AI4CTF")} ${chalk.gray("[Day 1]")} ${chalk.white("AI as your teammate")}\n ${chalk.red.bold("CTF4AI")} ${chalk.gray("[Day 2]")} ${chalk.white("Challenge & evaluate AI systems")}\n ${chalk.cyan.bold("CTF4EAI")} ${chalk.gray("[Pioneer]")} ${chalk.white("Embodied AI security")}\n ${chalk.bold.yellow("AI is your teammate. AI is your target. AI is embodied.")}\n\n ${chalk.white("Sydney, Australia")} ${chalk.gray("Jun 27 - Jul 2, 2026")}\n ${chalk.cyan.underline("https://icoa2026.au")}\n\n ${chalk.gray(`CLI-Native Competition Terminal v${k}`)}\n\n${F}\n`;process.on("uncaughtException",o=>{"__REPL_NO_EXIT__"!==o.message&&(console.error(chalk.red("Error:"),o.message),process.exit(1))}),process.on("unhandledRejection",o=>{const e=o instanceof Error?o.message:String(o);"__REPL_NO_EXIT__"!==e&&(console.error(chalk.red("Error:"),e),process.exit(1))});{const o=process.argv.slice(2).some(o=>["-V","--version","-h","--help"].includes(o));if(!0===process.stdin.isTTY&&"1"!==process.env.ICOA_SUPERVISED&&!o){const{spawnSync:o}=await import("node:child_process");process.on("SIGINT",()=>{}),process.on("SIGTERM",()=>{});let e=0,n=!1;do{const s=o(process.execPath,process.argv.slice(1),{stdio:"inherit",env:{...process.env,ICOA_SUPERVISED:"1",...n?{ICOA_RELAUNCH:"1"}:{}}});if(s.signal){e=0;break}e=s.status,n=!0}while(e===A);process.exit(e??0)}}const U=new o;if(U.name("icoa").version(k).description("ICOA CLI — CLI-Native CTF Competition Terminal").option("--resume","Resume previous session").action(async o=>{const e=v();C("high-contrast"===e.themeVariant?"high-contrast":"dark"),E(),function(){const o=j();if(o.length<=1)return;const e=o[0];if([...o.map(o=>o.version||"0.0.0")].sort((o,e)=>function(o,e){const n=o.split(".").map(o=>parseInt(o,10)||0),s=e.split(".").map(o=>parseInt(o,10)||0);for(let o=0;o<3;o++)if((n[o]||0)!==(s[o]||0))return(n[o]||0)-(s[o]||0);return 0}(e,o))[0]!==e.version){console.log(),console.log(chalk.yellow.bold(" ⚠ Multiple icoa installations detected on PATH:"));for(let e=0;e<o.length;e++){const n=o[e],s=n.version?`v${n.version}`:"(version unreadable)",r=0===e?chalk.yellow("→"):" ",t=0===e?chalk.gray(" ← currently running (older — shadowing newer install)"):chalk.gray(" ← shadowed");console.log(` ${r} ${chalk.cyan(n.path.padEnd(28))} ${chalk.white(s)}${t}`)}console.log(),console.log(chalk.yellow(" The first install on PATH wins. To use the newer one, remove the older:")),console.log(),e.pkgDir?console.log(` ${chalk.bold.cyan(`sudo rm -rf ${e.pkgDir} ${e.path}`)}`):console.log(` ${chalk.bold.cyan(`sudo rm -rf ${e.path} # also delete its node_modules dir`)}`),console.log(),console.log(chalk.gray(" Then re-run icoa to confirm version banner shows the newer release.")),console.log()}}();const n=process.env.LANG||process.env.LC_ALL||process.env.LC_CTYPE||"";if(!/UTF-?8/i.test(n))if("win32"===process.platform){let o="";try{const{execFileSync:e}=await import("node:child_process");o=e("chcp.com",[],{encoding:"utf-8",timeout:1500,stdio:["ignore","pipe","ignore"]}).trim()}catch{}o.includes("65001")||(console.log(chalk.yellow(`⚠ Windows terminal is not using UTF-8 (current: ${o||"unknown"}).`)),console.log(chalk.gray(' Non-English text (Ukrainian, Chinese, Japanese, etc.) may show as "?" or garbled glyphs.')),console.log(chalk.gray(" Fix (run before ")+chalk.cyan("icoa")+chalk.gray("): ")+chalk.cyan("chcp 65001")),console.log(chalk.gray(" Or stay in English inside the CLI: ")+chalk.cyan("lang en")),console.log())}else console.log(chalk.yellow(`⚠ Your terminal locale is not UTF-8 (LANG=${n||"(unset)"}).`)),console.log(chalk.gray(' Non-English text and box characters may display as "?" or garbled glyphs.')),console.log(chalk.gray(" Fix: ")+chalk.cyan("export LANG=en_US.UTF-8")+chalk.gray(" (or your locale, e.g. ")+chalk.cyan("zh_CN.UTF-8")+chalk.gray(", ")+chalk.cyan("uk_UA.UTF-8")+chalk.gray(")")),console.log();if(console.log(R),process.argv.length<=2||o.resume)return o.resume||"1"===process.env.ICOA_RELAUNCH||await async function(){const o=process.stdin;if(o.isTTY&&"function"==typeof o.setRawMode)return new Promise(e=>{let n=!1;const s=()=>{if(!n){n=!0,o.removeListener("data",r);try{o.setRawMode(!1)}catch{}o.pause(),e()}},r=()=>s();o.setRawMode(!0),o.resume(),o.once("data",r),console.log(chalk.gray(" (press any key to continue...)")),setTimeout(s,3e3)});await new Promise(o=>setTimeout(o,3e3))}(),void $(U,!!o.resume)}),e(U),n(U),s(U),r(U),t(U),i(U),a(U),l(U),m(U),c(U),g(U),d(U),p(U),y(U),h(U),f(U),u(U),w(U),T(U),b(U),U.command("model",{hidden:!0}).argument("[name]","model name to switch to").action(o=>{const e=v().geminiModel||"gemini-2.5-flash-lite";o?(I({geminiModel:o}),console.log(),console.log(chalk.green(" Model switched: ")+chalk.gray(e)+chalk.white(" -> ")+chalk.bold.white(o)),console.log()):(console.log(),console.log(chalk.gray(" Current model: ")+chalk.white(e)),console.log(),console.log(chalk.gray(" Available models:")),console.log(chalk.bold.white(" Gemini 3.x (Latest)")),console.log(chalk.white(" model gemini-3.5-flash ")+chalk.gray("Newest stable, paid Tier 1+")),console.log(chalk.white(" model gemini-3-flash-preview ")+chalk.gray("Preview")),console.log(chalk.bold.white(" Gemini 2.5 (Stable)")),console.log(chalk.white(" model gemini-2.5-flash-lite ")+chalk.gray("Fastest, default, free tier")),console.log(chalk.white(" model gemini-2.5-flash ")+chalk.gray("Heavier, thinking enabled")),console.log(chalk.white(" model gemini-2.5-pro ")+chalk.gray("Strong reasoning, paid")),console.log(chalk.bold.white(" Open Source")),console.log(chalk.white(" model gemma-4-31b-it ")+chalk.gray("Free, open-source")),console.log(chalk.white(" model <any-model-id> ")+chalk.gray("Custom model")),console.log(),console.log(chalk.gray(" Translation uses gemini-3.5-flash for best quality.")),console.log())}),"1"===process.env.ICOA_RESET_STATE)try{const{clearExamState:o}=await import("./lib/exam-state.js");o(),console.log(chalk.yellow("⚠ ICOA_RESET_STATE=1 — local exam state wiped.")),console.log(chalk.gray(" (Token NOT revoked server-side. Re-enter a fresh token with `exam <token>`.)")),console.log()}catch(o){console.log(chalk.red("⚠ ICOA_RESET_STATE: could not clear state — ")+chalk.gray(String(o)))}U.parse();
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* aienv — the AI/ML notebook-arena environment (Phase 0 of the CLI notebook
|
|
3
|
+
* arena, see `project_cli_notebook_arena_plan`).
|
|
4
|
+
*
|
|
5
|
+
* This is the PURE, testable core. The orchestration (venv creation, pip
|
|
6
|
+
* installs, status rendering) lives in `src/commands/aienv.ts`.
|
|
7
|
+
*
|
|
8
|
+
* Hard rule (BUG3 / Team Indonesia): aienv provisions its OWN venv under
|
|
9
|
+
* ~/.icoa/aienv and NEVER repoints the system python3. It also stays fully
|
|
10
|
+
* independent of `env.ts` so the heavy ML stack (jupyter/numpy/torch...) can
|
|
11
|
+
* never drag down the lightweight exam-only `env setup` path, and so a broken
|
|
12
|
+
* ML install is fault-isolated from the competition toolkit.
|
|
13
|
+
*/
|
|
14
|
+
export type AiGroup = 'core' | 'data' | 'deep';
|
|
15
|
+
export interface AiPkg {
|
|
16
|
+
/** Display name + the name `pip show` uses. */
|
|
17
|
+
name: string;
|
|
18
|
+
/** Module name for the `import x` readiness probe. */
|
|
19
|
+
import: string;
|
|
20
|
+
/** pip install spec (floor-pinned — see note below). */
|
|
21
|
+
spec: string;
|
|
22
|
+
group: AiGroup;
|
|
23
|
+
note?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface PyVersion {
|
|
26
|
+
major: number;
|
|
27
|
+
minor: number;
|
|
28
|
+
}
|
|
29
|
+
export interface AienvPaths {
|
|
30
|
+
/** The venv root: <home>/.icoa/aienv */
|
|
31
|
+
root: string;
|
|
32
|
+
/** Directory holding the venv executables (bin on posix, Scripts on win). */
|
|
33
|
+
binDir: string;
|
|
34
|
+
/** Absolute path to the venv's python interpreter. */
|
|
35
|
+
python: string;
|
|
36
|
+
/** Absolute path to the venv's pip. */
|
|
37
|
+
pip: string;
|
|
38
|
+
/** Manifest written after a successful setup — records python version + groups. */
|
|
39
|
+
marker: string;
|
|
40
|
+
}
|
|
41
|
+
export declare const MIN_HOST_PY: PyVersion;
|
|
42
|
+
export declare const PREFERRED_PY: PyVersion;
|
|
43
|
+
export declare const AIENV_MARKER_VERSION = 1;
|
|
44
|
+
/**
|
|
45
|
+
* ML stack for the notebook arena.
|
|
46
|
+
*
|
|
47
|
+
* Versions are FLOOR-pinned (`>=`), not exact-pinned like the CTF toolkit in
|
|
48
|
+
* env.ts. The scientific/ML stack ships platform- and arch-specific wheels that
|
|
49
|
+
* churn fast; exact pins across macOS/Linux/WSL × x64/arm64 routinely fail wheel
|
|
50
|
+
* resolution. Phase 0 only provisions — fairness-critical determinism lives
|
|
51
|
+
* server-side on the (fixed-model) GPU box, not in the student's local venv.
|
|
52
|
+
*
|
|
53
|
+
* - core: the Jupyter kernel-protocol foundation Phase 1's cell loop talks to.
|
|
54
|
+
* - data: the everyday data-science stack students compute with on CPU.
|
|
55
|
+
* - deep: multi-GB DL stack — OPT-IN via `aienv setup --deep` only. Under the
|
|
56
|
+
* fixed-model GPU design, most students call the server's inference API and
|
|
57
|
+
* never need torch locally, so it must not be in the default footprint.
|
|
58
|
+
*/
|
|
59
|
+
export declare const AIENV_PACKAGES: AiPkg[];
|
|
60
|
+
export declare function packagesForGroups(groups: AiGroup[]): AiPkg[];
|
|
61
|
+
/** Compute the venv paths for a given home directory + platform. */
|
|
62
|
+
export declare function aienvPaths(home: string, plat?: NodeJS.Platform): AienvPaths;
|
|
63
|
+
/**
|
|
64
|
+
* Ordered host-python candidates to build the venv from. We probe a versioned
|
|
65
|
+
* 3.12 BEFORE the bare `python3` so we don't accidentally seed the venv with a
|
|
66
|
+
* distro 3.10 when a 3.12 is present. We never touch the system python3 beyond
|
|
67
|
+
* reading its --version.
|
|
68
|
+
*/
|
|
69
|
+
export declare function hostPythonCandidates(plat?: NodeJS.Platform): string[];
|
|
70
|
+
/** Parse `Python 3.12.13` → {major:3, minor:12}. Tolerant of trailing newline. */
|
|
71
|
+
export declare function parsePyVersion(out: string | null | undefined): PyVersion | null;
|
|
72
|
+
/** True if `v` is at least `min` (major-then-minor comparison). */
|
|
73
|
+
export declare function meetsMinPy(v: PyVersion, min: PyVersion): boolean;
|
|
74
|
+
export interface HostPython {
|
|
75
|
+
cmd: string;
|
|
76
|
+
version: PyVersion;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the best host python to create the venv from.
|
|
80
|
+
*
|
|
81
|
+
* `probe(cmd)` runs `cmd --version` and returns its stdout (or null if the
|
|
82
|
+
* command is absent / errored) — injected so this stays pure + testable.
|
|
83
|
+
*
|
|
84
|
+
* Returns the first 3.12 interpreter found; otherwise the first python3 that
|
|
85
|
+
* meets MIN_HOST_PY; otherwise null.
|
|
86
|
+
*/
|
|
87
|
+
export declare function resolveHostPython(probe: (cmd: string) => string | null, plat?: NodeJS.Platform): HostPython | null;
|
|
88
|
+
/**
|
|
89
|
+
* Build the readiness probe for one package, run via the VENV python (absolute
|
|
90
|
+
* path) — never the system python3. Mirrors env.ts's `python3 -c "import x"`
|
|
91
|
+
* convention but always targets the venv interpreter.
|
|
92
|
+
*/
|
|
93
|
+
export declare function importCheckCmd(venvPython: string, pkg: AiPkg): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{posix as o,win32 as r}from"node:path";export const MIN_HOST_PY={major:3,minor:10};export const PREFERRED_PY={major:3,minor:12};export const AIENV_MARKER_VERSION=1;export const AIENV_PACKAGES=[{name:"jupyter_client",import:"jupyter_client",spec:"jupyter_client>=8.6",group:"core"},{name:"ipykernel",import:"ipykernel",spec:"ipykernel>=6.29",group:"core"},{name:"numpy",import:"numpy",spec:"numpy>=1.26",group:"data"},{name:"pandas",import:"pandas",spec:"pandas>=2.2",group:"data"},{name:"scikit-learn",import:"sklearn",spec:"scikit-learn>=1.4",group:"data"},{name:"matplotlib",import:"matplotlib",spec:"matplotlib>=3.8",group:"data"},{name:"torch",import:"torch",spec:"torch>=2.2",group:"deep",note:"large download (~2 GB)"},{name:"transformers",import:"transformers",spec:"transformers>=4.40",group:"deep"},{name:"datasets",import:"datasets",spec:"datasets>=2.19",group:"deep"}];export function packagesForGroups(o){const r=new Set(o);return AIENV_PACKAGES.filter(o=>r.has(o.group))}export function aienvPaths(n,t=process.platform){const e="win32"===t?r:o,p=e.join(n,".icoa","aienv");if("win32"===t){const o=e.join(p,"Scripts");return{root:p,binDir:o,python:e.join(o,"python.exe"),pip:e.join(o,"pip.exe"),marker:e.join(p,"icoa-aienv.json")}}const i=e.join(p,"bin");return{root:p,binDir:i,python:e.join(i,"python"),pip:e.join(i,"pip"),marker:e.join(p,"icoa-aienv.json")}}export function hostPythonCandidates(o=process.platform){return"win32"===o?["py -3.12","python","py -3","py"]:"darwin"===o?["python3.12","/opt/homebrew/opt/python@3.12/bin/python3.12","/usr/local/opt/python@3.12/bin/python3.12","python3"]:["python3.12","python3"]}export function parsePyVersion(o){if(!o)return null;const r=o.match(/(\d+)\.(\d+)(?:\.\d+)?/);return r?{major:Number(r[1]),minor:Number(r[2])}:null}export function meetsMinPy(o,r){return o.major!==r.major?o.major>r.major:o.minor>=r.minor}export function resolveHostPython(o,r=process.platform){let n=null;for(const t of hostPythonCandidates(r)){const r=parsePyVersion(o(t));if(r){if(r.major===PREFERRED_PY.major&&r.minor===PREFERRED_PY.minor)return{cmd:t,version:r};!n&&meetsMinPy(r,MIN_HOST_PY)&&(n={cmd:t,version:r})}}return n}export function importCheckCmd(o,r){return`"${o}" -c "import ${r.import}"`}
|
package/dist/lib/country-lang.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const COUNTRY_LANG={UA:"uk",PE:"es",CN:"zh",AU:"en",JP:"ja",KR:"ko",BR:"pt",SA:"ar",FR:"fr",DE:"de",IN:"hi",ID:"id",TH:"th",VN:"vi",TR:"tr",RU:"ru",EG:"ar",HT:"ht",PH:"en",MY:"en",MM:"en",SG:"en",ZA:"en",KE:"sw",TZ:"sw",MO:"zh",UZ:"uz",GH:"en",LA:"lo",BD:"bn",BI:"fr",US:"en",UK:"en",NZ:"en",LK:"si",BW:"en",VE:"es"};
|
|
1
|
+
export const COUNTRY_LANG={UA:"uk",PE:"es",CN:"zh",AU:"en",JP:"ja",KR:"ko",BR:"pt",SA:"ar",FR:"fr",DE:"de",IN:"hi",ID:"id",TH:"th",VN:"vi",TR:"tr",RU:"ru",EG:"ar",HT:"ht",PH:"en",MY:"en",MM:"en",SG:"en",ZA:"en",KE:"sw",TZ:"sw",MO:"zh",UZ:"uz",GH:"en",LA:"lo",BD:"bn",BI:"fr",US:"en",UK:"en",NZ:"en",LK:"si",BW:"en",VE:"es"};
|
package/dist/lib/hint-client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(a,b){const v=a0b,c=a();while(!![]){try{const d
|
|
1
|
+
(function(a,b){const v=a0b,c=a();while(!![]){try{const d=-parseInt(v(0x145))/(-0x1*-0x2185+0x1e83+-0x25*0x1bb)*(-parseInt(v(0x138))/(0x46*-0x49+0x23ac+-0xc9*0x14))+-parseInt(v(0x148))/(-0x1f68+0x94e*0x1+0x161d)*(-parseInt(v(0x12a))/(0x18be+0x6e*-0x13+0x424*-0x4))+parseInt(v(0x130))/(-0x1a79+-0x3*-0xa28+-0x3fa)*(parseInt(v(0x14b))/(0x17af*0x1+-0x14e*0x2+-0x13d*0x11))+-parseInt(v(0x137))/(-0xb88+0x1*0x389+0x806)*(-parseInt(v(0x142))/(-0x32+0x653+-0x7*0xdf))+parseInt(v(0x133))/(0x156b+0x3*0xb7f+0x1*-0x37df)*(parseInt(v(0x12d))/(0x20*-0xc4+-0x23bb*0x1+0x8b*0x6f))+-parseInt(v(0x135))/(0x1f*0x1d+-0x160b+-0xf*-0x13d)*(-parseInt(v(0x12c))/(-0x1*-0x1258+0xa*0x61+0xb*-0x202))+-parseInt(v(0x13d))/(0x82a+0x102d*0x1+-0x184a);if(d===b)break;else c['push'](c['shift']());}catch(e){c['push'](c['shift']());}}}(a0a,-0x3*0x2de1c+-0x1*-0x8a973+0x7d82e));function a0b(a,b){a=a-(-0x217d+-0x13*-0x115+0xe18);const c=a0a();let d=c[a];if(a0b['dqtyzH']===undefined){var e=function(i){const j='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let l='',m='';for(let n=0x112b+-0x1*0x61a+-0x1*0xb11,o,p,q=-0x1bdd+-0x119f+0x2d7c;p=i['charAt'](q++);~p&&(o=n%(-0x2b3*0x3+0x2123+-0x1906)?o*(0x309*-0x6+0x13e2+-0x16c)+p:p,n++%(0xb2*-0x4+0x239*0x1+0x93*0x1))?l+=String['fromCharCode'](-0x3a7+0x1*0x118c+-0xce6*0x1&o>>(-(-0x2*-0x98e+0x2083+-0x339d)*n&0x49*-0xe+-0x1e99*-0x1+-0x1a95)):-0x1003+0x457+0xbac){p=j['indexOf'](p);}for(let r=0x18d1*-0x1+0x47*-0x5e+0x32e3,s=l['length'];r<s;r++){m+='%'+('00'+l['charCodeAt'](r)['toString'](-0x18f9+0x38f+0x157a))['slice'](-(0x1494+0x25b0+-0x3a42));}return decodeURIComponent(m);};a0b['OYuOVf']=e,a0b['UYukMM']={},a0b['dqtyzH']=!![];}const f=c[0x51*-0x6f+-0x1d*0x4d+0x2bd8],g=a+f,h=a0b['UYukMM'][g];return!h?(d=a0b['OYuOVf'](d),a0b['UYukMM'][g]=d):d=h,d;}import{getConfig as a0c}from'./config.js';export async function requestHint(d){const w=a0b,f=a0c(),g=f[w(0x12e)]||w(0x149),h=d[w(0x12b)]||f[w(0x13b)]||'en',j=d[w(0x14d)]??-0x341d+-0x2*-0x193a+0x20e9,k=[g+w(0x147)+d[w(0x13a)]+w(0x143),g+w(0x139)+d[w(0x13a)]+'/hint'];let l=null;for(const p of k)try{const q=await fetch(p,{'method':w(0x136),'headers':{'Content-Type':w(0x12f),'User-Agent':w(0x13c)},'body':JSON['stringify']({'token':d['token'],'question':d['question'],'level':d[w(0x140)],'lang':h}),'signal':AbortSignal['timeout'](j)}),r=await q[w(0x132)]()[w(0x134)](()=>({}));if(!q['ok']||!(-0x119f+-0x1fcd+0x316d)===r[w(0x131)]){if(l={'status':q[w(0x144)],'message':r?.[w(0x146)]||w(0x141)+q['status']+')'},q[w(0x144)]>=0x2123+0x23ee+-0x4381&&q[w(0x144)]<0x309*-0x6+0x13e2+0x48)throw l;continue;}return r[w(0x13e)];}catch(u){if(u&&w(0x13f)==typeof u&&w(0x144)in u)throw u;l={'status':0x0,'message':u?.[w(0x146)]||w(0x14c)};}const m={};m[w(0x144)]=0x0,m[w(0x146)]=w(0x14a);throw l||m;}function a0a(){const x=['Ahr0Chm6lY9WCMfJDgLJzs5Py29HmJaYnI5HDq','AgLUDcbbueKGDw5YzwfJAgfIBgu','nda1oty1ngn1uu1wBG','BMv0D29YAYbLCNjVCG','DgLTzw91De1Z','mtG3mdmYs2zQq3D3','BgfUzW','nZa0mZi4ANnmD1bj','nZbQBePbDvy','y3rMzfvYBa','yxbWBgLJyxrPB24VANnVBG','nwnowMjMyq','C3vJy2vZCW','ANnVBG','mtmZmtmZnfvAEhzuAa','y2f0y2G','mJjerejUAhe','ue9tva','nJKXmtyWnhftwKvKDG','mte3otmYyMDWq3nT','oJKWotaVyxbPl2LJB2eVzxHHBxmV','zxHHBuLK','BgfUz3vHz2u','AwnVys1JBgK','mZG1ntqXnZHHtMv1wxO','zgf0yq','B2jQzwn0','Bgv2zwW','AgLUDcbYzxf1zxn0igzHAwXLzcaO','ohLAANH5tG','l2HPBNq','C3rHDhvZ','mwj4vu1prq','BwvZC2fNzq','l2fWAs9Py29Hl2v4yw1ZlW','mZLOAMLwuK0'];a0a=function(){return x;};return a0a();}
|
|
@@ -212,12 +212,6 @@ export declare function isValidCurriculum(data: unknown): data is Curriculum;
|
|
|
212
212
|
* 4. null → no cache, no server
|
|
213
213
|
*/
|
|
214
214
|
export declare function loadCurriculumById(id: string): Promise<Curriculum | null>;
|
|
215
|
-
/**
|
|
216
|
-
* Pre-v2.19.204 sync entry-point. Kept for backward compat with old call
|
|
217
|
-
* sites in learn.ts; new code should `await loadCurriculumById(id)` directly.
|
|
218
|
-
* Always returns null now — callers must use the async path.
|
|
219
|
-
*/
|
|
220
|
-
export declare function loadCurriculum(_token: string): Curriculum | null;
|
|
221
215
|
/**
|
|
222
216
|
* Server-side validation for EAxxxxxxxx tokens.
|
|
223
217
|
* Returns curriculum identifier + status; the actual cards are still
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function localized(r,t){if(!t.startsWith("zh")||!r._zh)return r;const e=r._zh,c={...r};for(const r of Object.keys(e))void 0!==e[r]&&(c[r]=e[r]);return c}export function isValidCurriculum(r){if(!r||"object"!=typeof r)return!1;const t=r;return"string"==typeof t.id&&"number"==typeof t.totalCards&&Array.isArray(t.cards)&&Array.isArray(t.modules)}import{readFileSync as r,writeFileSync as t,mkdirSync as e,existsSync as c,statSync as n,utimesSync as a}from"node:fs";import{join as i}from"node:path";import{homedir as o}from"node:os";const u=i(o(),".icoa","learn-cache");function s(r){const t=r.replace(/[^A-Za-z0-9_-]/g,"_");return i(u,`${t}.json`)}function l(r){const t=r.replace(/[^A-Za-z0-9_-]/g,"_");return i(u,`${t}.etag`)}export async function loadCurriculumById(d){"ctf4eai-12"===d&&(d="LEARNDEMO01");const m=function(t){const e=s(t);if(!c(e))return null;try{const t=n(e),c=Date.now()-t.mtimeMs;return{curriculum:JSON.parse(r(e,"utf-8")),ageMs:c}}catch{return null}}(d);if(m&&m.ageMs<6048e5&&isValidCurriculum(m.curriculum))return m.curriculum;const f=await async function(t){const e=function(){try{const t=JSON.parse(r(i(o(),".icoa","config.json"),"utf-8"));if("string"==typeof t.ctfdUrl&&t.ctfdUrl)return t.ctfdUrl}catch{}return"https://practice.icoa2026.au"}().replace(/\/$/,"")+"/api/icoa/learn/curriculum/"+encodeURIComponent(t),n={},a=function(t){const e=l(t);if(!c(e))return null;try{return r(e,"utf-8").trim()||null}catch{return null}}(t);a&&(n["If-None-Match"]=a);try{const r=await fetch(e,{headers:n,signal:AbortSignal.timeout(6e4)});if(304===r.status)return{kind:"unchanged"};if(!r.ok)return{kind:"error"};const t=await r.json();if(!t.success||!t.data)return{kind:"error"};if(!isValidCurriculum(t.data))return{kind:"error"};const c=r.headers.get("etag")||r.headers.get("ETag")||void 0;return{kind:"fresh",curriculum:t.data,etag:c||void 0}}catch{return{kind:"error"}}}(d);return"fresh"===f.kind?(function(r,c,n){try{e(u,{recursive:!0}),t(s(r),JSON.stringify(c)),n&&t(l(r),n)}catch{}}(d,f.curriculum,f.etag),f.curriculum):"unchanged"===f.kind&&m&&isValidCurriculum(m.curriculum)?(function(r){try{const t=s(r);if(c(t)){const r=Date.now()/1e3;a(t,r,r)}}catch{}}(d),m.curriculum):m&&isValidCurriculum(m.curriculum)?m.curriculum:null}export
|
|
1
|
+
export function localized(r,t){if(!t.startsWith("zh")||!r._zh)return r;const e=r._zh,c={...r};for(const r of Object.keys(e))void 0!==e[r]&&(c[r]=e[r]);return c}export function isValidCurriculum(r){if(!r||"object"!=typeof r)return!1;const t=r;return"string"==typeof t.id&&"number"==typeof t.totalCards&&Array.isArray(t.cards)&&Array.isArray(t.modules)}import{readFileSync as r,writeFileSync as t,mkdirSync as e,existsSync as c,statSync as n,utimesSync as a}from"node:fs";import{join as i}from"node:path";import{homedir as o}from"node:os";const u=i(o(),".icoa","learn-cache");function s(r){const t=r.replace(/[^A-Za-z0-9_-]/g,"_");return i(u,`${t}.json`)}function l(r){const t=r.replace(/[^A-Za-z0-9_-]/g,"_");return i(u,`${t}.etag`)}export async function loadCurriculumById(d){"ctf4eai-12"===d&&(d="LEARNDEMO01");const m=function(t){const e=s(t);if(!c(e))return null;try{const t=n(e),c=Date.now()-t.mtimeMs;return{curriculum:JSON.parse(r(e,"utf-8")),ageMs:c}}catch{return null}}(d);if(m&&m.ageMs<6048e5&&isValidCurriculum(m.curriculum))return m.curriculum;const f=await async function(t){const e=function(){try{const t=JSON.parse(r(i(o(),".icoa","config.json"),"utf-8"));if("string"==typeof t.ctfdUrl&&t.ctfdUrl)return t.ctfdUrl}catch{}return"https://practice.icoa2026.au"}().replace(/\/$/,"")+"/api/icoa/learn/curriculum/"+encodeURIComponent(t),n={},a=function(t){const e=l(t);if(!c(e))return null;try{return r(e,"utf-8").trim()||null}catch{return null}}(t);a&&(n["If-None-Match"]=a);try{const r=await fetch(e,{headers:n,signal:AbortSignal.timeout(6e4)});if(304===r.status)return{kind:"unchanged"};if(!r.ok)return{kind:"error"};const t=await r.json();if(!t.success||!t.data)return{kind:"error"};if(!isValidCurriculum(t.data))return{kind:"error"};const c=r.headers.get("etag")||r.headers.get("ETag")||void 0;return{kind:"fresh",curriculum:t.data,etag:c||void 0}}catch{return{kind:"error"}}}(d);return"fresh"===f.kind?(function(r,c,n){try{e(u,{recursive:!0}),t(s(r),JSON.stringify(c)),n&&t(l(r),n)}catch{}}(d,f.curriculum,f.etag),f.curriculum):"unchanged"===f.kind&&m&&isValidCurriculum(m.curriculum)?(function(r){try{const t=s(r);if(c(t)){const r=Date.now()/1e3;a(t,r,r)}}catch{}}(d),m.curriculum):m&&isValidCurriculum(m.curriculum)?m.curriculum:null}export async function validateEAToken(r,t){const e=t.replace(/\/$/,"")+"/api/icoa/learn/validate";try{const t=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:r.toUpperCase()}),signal:AbortSignal.timeout(8e3)});if(!t.ok)return{ok:!1,message:(await t.json().catch(()=>({}))).message||`HTTP ${t.status}`};const c=await t.json();return c.success&&c.data?{ok:!0,curriculumId:c.data.curriculum_id,status:c.data.status,validUntil:c.data.valid_until}:{ok:!1,message:c.message||"Validation failed"}}catch(r){return{ok:!1,message:`Network error: ${r instanceof Error?r.message:String(r)}`}}}export async function syncProgress(r,t,e){if("LEARNDEMO01"===r.toUpperCase())return;const c=t.replace(/\/$/,"")+"/api/icoa/learn/progress/"+r.toUpperCase();try{await fetch(c,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({card_number:e.card_number,event_type:e.event_type,mcq_answer:e.mcq_answer,mcq_correct:e.mcq_correct?1:0,check_answer:e.check_answer,check_correct:e.check_correct?1:0,time_on_card_ms:e.time_on_card_ms}),signal:AbortSignal.timeout(5e3)})}catch{}}export async function syncCardFeedback(r,t,e){const c=t.replace(/\/$/,"")+"/api/icoa/learn/feedback/"+r.toUpperCase();try{await fetch(c,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({curriculum_id:e.curriculum_id,card_number:e.card_number,feedback_text:e.feedback_text,time_on_card_ms:e.time_on_card_ms}),signal:AbortSignal.timeout(5e3)})}catch{}}
|
package/dist/lib/learn-input.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const t={ai4ctf:"AI4CTFDEMO01",ctf4ai:"CTF4AIDEMO01",ctf4eai:"LEARNDEMO01","ai4ctf-12":"AI4CTFDEMO01","ctf4ai-12":"CTF4AIDEMO01","ctf4eai-12":"LEARNDEMO01",ctf4vla:"LEARNDEMO01",vla:"LEARNDEMO01",embodied:"LEARNDEMO01",ai4ctfdemo:"AI4CTFDEMO01",ctf4aidemo:"CTF4AIDEMO01",learndemo:"LEARNDEMO01"},e={1:"AI4CTFDEMO01",2:"CTF4AIDEMO01",3:"LEARNDEMO01"},o=/^[A-Z]{2}[A-Z0-9]{6,}$/,n=/^(AI4CTFDEMO\d+|CTF4AIDEMO\d+|LEARNDEMO\d+)$/;export function resolveLearnInput(i){const r=i.trim();if(!r)return{kind:"back"};
|
|
1
|
+
const t={ai4ctf:"AI4CTFDEMO01",ctf4ai:"CTF4AIDEMO01",ctf4eai:"LEARNDEMO01","ai4ctf-12":"AI4CTFDEMO01","ctf4ai-12":"CTF4AIDEMO01","ctf4eai-12":"LEARNDEMO01",ctf4vla:"LEARNDEMO01",vla:"LEARNDEMO01",embodied:"LEARNDEMO01",ai4ctfdemo:"AI4CTFDEMO01",ctf4aidemo:"CTF4AIDEMO01",learndemo:"LEARNDEMO01"},e={1:"AI4CTFDEMO01",2:"CTF4AIDEMO01",3:"LEARNDEMO01"},o=/^[A-Z]{2}[A-Z0-9]{6,}$/,n=/^(AI4CTFDEMO\d+|CTF4AIDEMO\d+|LEARNDEMO\d+)$/;export function resolveLearnInput(i){const r=i.trim();if(!r)return{kind:"back"};const a=r.replace(/^\s*(?:[/>])?\s*(?:icoa\s+)?learn\s+/i,"").trim();if(!a)return{kind:"ambiguous",hint:"Type just the token (e.g. LEARNDEMO01) or one of: ai4ctf / ctf4ai / ctf4eai"};if(e[a])return{kind:"token",token:e[a]};const c=a.toLowerCase();if(t[c])return{kind:"token",token:t[c]};if("demo"===c||"demos"===c)return{kind:"ambiguous",hint:"Which demo? Type: 1 (ai4ctf) / 2 (ctf4ai) / 3 (ctf4eai)"};if(/\s/.test(a))return{kind:"unknown",hint:`Don't recognize "${r}". Try one of: ai4ctf / ctf4ai / ctf4eai (for the free demos), 1/2/3, or paste your AC/CA/EA/EI/IO/IA + 8 chars cohort token. Blank to go back.`};const f=a.toUpperCase();return n.test(f)||o.test(f)||/^[A-Z0-9]{8,}$/.test(f)?{kind:"token",token:f}:{kind:"unknown",hint:`Don't recognize "${r}". Try one of: ai4ctf / ctf4ai / ctf4eai (for the free demos), 1/2/3, or paste your AC/CA/EA/EI/IO/IA + 8 chars cohort token. Blank to go back.`}}
|
|
@@ -27,7 +27,6 @@ export type LearnState = {
|
|
|
27
27
|
};
|
|
28
28
|
export declare function loadLearnState(): LearnState | null;
|
|
29
29
|
export declare function saveLearnState(state: LearnState): void;
|
|
30
|
-
export declare function clearLearnState(): void;
|
|
31
30
|
export declare function newLearnState(token: string, curriculumId: string, totalCards: number): LearnState;
|
|
32
31
|
/**
|
|
33
32
|
* Update streak based on last-seen-at vs today.
|
|
@@ -41,4 +40,3 @@ export declare function recordMCQ(state: LearnState, cardNumber: number, result:
|
|
|
41
40
|
export declare function recordCheck(state: LearnState, cardNumber: number, result: CheckResult): void;
|
|
42
41
|
export declare function markPracticalComplete(state: LearnState, cardNumber: number): void;
|
|
43
42
|
export declare function addAchievement(state: LearnState, badge: string): void;
|
|
44
|
-
export declare function getStateFilePath(): string;
|
package/dist/lib/learn-state.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as e,mkdirSync as t,readFileSync as r,writeFileSync as a}from"node:fs";import{homedir as n}from"node:os";import{join as o}from"node:path";const s=o(n(),".icoa"),c=o(s,"learn-state.json");export function loadLearnState(){if(!e(c))return null;try{return JSON.parse(r(c,"utf-8"))}catch{return null}}export function saveLearnState(r){e(s)||t(s,{recursive:!0}),r.lastSeenAt=(new Date).toISOString(),a(c,JSON.stringify(r,null,2))}export function
|
|
1
|
+
import{existsSync as e,mkdirSync as t,readFileSync as r,writeFileSync as a}from"node:fs";import{homedir as n}from"node:os";import{join as o}from"node:path";const s=o(n(),".icoa"),c=o(s,"learn-state.json");export function loadLearnState(){if(!e(c))return null;try{return JSON.parse(r(c,"utf-8"))}catch{return null}}export function saveLearnState(r){e(s)||t(s,{recursive:!0}),r.lastSeenAt=(new Date).toISOString(),a(c,JSON.stringify(r,null,2))}export function newLearnState(e,t,r){const a=(new Date).toISOString();return{token:e,curriculumId:t,currentCard:1,totalCards:r,startedAt:a,lastSeenAt:a,streakDays:1,longestStreak:1,cardsCompleted:[],mcqResults:{},checkResults:{},practicalsCompleted:[],bookmarks:[],achievements:[],totalSecondsActive:0}}export function updateStreak(e){const t=new Date(e.lastSeenAt),r=new Date,a=Math.floor(t.getTime()/864e5),n=Math.floor(r.getTime()/864e5)-a;0!==n&&(1===n?(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)}
|