icoa-cli 2.19.541 → 2.19.543

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.
@@ -0,0 +1 @@
1
+ import{createInterface as e,Interface as o}from"node:readline";import chalk from"chalk";import{getDeviceFingerprint as t}from"../lib/access.js";import{getMainRl as n}from"../lib/main-rl.js";const s=(process.env.ICOA_GPUTASK_SERVER||"https://practice.icoa2026.au").replace(/\/$/,"");let i=[],a=null,l=null;function r(o){return new Promise(t=>{const s=n();if(s)return l=t,s.setPrompt(o),void s.prompt();const i=e({input:process.stdin,output:process.stdout});i.question(o,e=>{i.close(),t(e)})})}let c="en";const g={banner:{en:"[🧪 Committee GPU sandbox — clean 40-pkg IOAI environment]",zh:"[🧪 命题组 GPU 沙箱 — 纯净 40 包 IOAI 标准环境]"},langTip:{en:"Tip: type lang zh anytime to switch to 中文 (default English).",zh:"提示:随时输入 lang en 切回英文。"},tokenAsk:{en:"Enter your committee access token: ",zh:"请输入命题组访问 token:"},tokenBad:{en:"Token not recognised — check for typos and try again.",zh:"token 无效 — 请检查拼写后重试。"},tokenDevice:{en:"This token is bound to another computer. One token = one device — use your own token on this machine.",zh:"这枚 token 已绑定在另一台电脑上。一枚 token 只绑一台设备 — 请在本机使用你自己的 token。"},tokenGiveUp:{en:"Three failed attempts — ask your coordinator to verify your token.",zh:"连续三次失败 — 请联系协调人核对你的 token。"},hoursAsk:{en:"Session length? [1] 1 hour [2] 2 hours [3] 3 hours [5] 5 hours: ",zh:"选择时长? [1] 1 小时 [2] 2 小时 [3] 3 小时 [5] 5 小时:"},hoursBad:{en:"Please type 1, 2, 3 or 5.",zh:"请输入 1、2、3 或 5。"},seatsBusy:{en:"All 3 committee seats are in use right now — try again in a little while.",zh:"3 个并发席位当前全部占用 — 请稍后再试。"},already:{en:"You already have an active session — reconnecting to it.",zh:"你已有一个进行中的 session — 正在接回。"},deploying:{en:"⏳ Deploying a clean v2 box (pure 40-pkg IOAI standard env, RTX 4000 Ada) — about 13 min…",zh:"⏳ 正在部署纯净 v2 箱(40 包 IOAI 标准环境,RTX 4000 Ada)— 约 13 分钟…"},stCreating:{en:"creating the instance…",zh:"正在创建实例…"},stBooting:{en:"image restored — booting + kernel fix…",zh:"镜像展开完成 — 启动中 + 内核修正…"},stVerifying:{en:"box is up — verifying the 40-package environment…",zh:"机器已启动 — 正在校验 40 包环境…"},failed:{en:"Deployment failed: ",zh:"部署失败:"},readyTitle:{en:"✓ Your GPU box is ready (RTX 4000 Ada · 20 GB VRAM · 8 vCPU · 32 GB RAM · 512 GB disk)",zh:"✓ GPU 箱已就绪 (RTX 4000 Ada · 20 GB 显存 · 8 vCPU · 32 GB 内存 · 512 GB 磁盘)"},loginHow:{en:"Log in (password shown once — testing use, fine to display):",zh:"登录方式(密码一次性、每次不同 — 测试用,明示无妨):"},pkgsOk:{en:"✓ 40/40 IOAI-standard packages verified (per the IOAI 2026 official environment list):",zh:"✓ 40/40 个 IOAI 标准包校验通过(依据 IOAI 2026 官方环境清单):"},pkgsFree:{en:"You may pip-install anything on top — egress is open, the venv is writable, you are root.",zh:"可以随意 pip 安装任何包 — 外网开放、venv 可写、root 权限。"},killLine1:{en:"⏱ Auto-destroy at ",zh:"⏱ 自动销毁时间:"},killLine2:{en:"Everything on the box is DELETED then — copy out anything you need before that.",zh:"届时机器上的一切数据将被删除 — 需要的文件请提前拷出。"},footer:{en:"Commands: status time left · stats live GPU/RAM/disk usage · stop end early · lang zh · quit leave (box keeps running)",zh:"命令:status 剩余时间 · stats 显存/内存/磁盘占用率 · stop 提前结束 · lang en · quit 退出(机器继续运行)"},weekly:{en:"Heads-up: the committee has used {H}h of GPU since Monday — no limit, just so you know. Thanks for all the work! 🙂",zh:"小提示:命题组本周一以来累计已用 {H} 小时 GPU — 没有限制,纯提示。辛苦了 🙂"},warn10:{en:"⚠ 10 minutes left — the box auto-destroys soon. Copy out anything you need NOW.",zh:"⚠ 剩余 10 分钟 — 机器即将自动销毁,请立刻拷出需要的数据。"},stopAsk:{en:"End the session and destroy the box now? Everything on it is lost. (y/n): ",zh:"确认立即结束并销毁机器?数据将全部丢失。(y/n):"},stopping:{en:"Stopping — the box will be destroyed within a minute.",zh:"正在结束 — 机器将在一分钟内销毁。"},quitNote:{en:"Leaving this screen — your box KEEPS RUNNING until its auto-destroy time. Type gpufortask to reconnect.",zh:"已退出此界面 — 机器会继续运行到自动销毁时间。再次输入 gpufortask 可接回。"},destroyed:{en:"Session ended — the box has been destroyed.",zh:"session 已结束 — 机器已销毁。"},noStats:{en:"No utilisation sample yet — try again in ~30 s.",zh:"还没有采样数据 — 约 30 秒后再试。"},minLeft:{en:"min left",zh:"分钟剩余"},netErr:{en:"Cannot reach the broker right now — check your network and try again.",zh:"暂时连不上服务器 — 请检查网络后重试。"}},u=e=>g[e]?.[c]??e;async function d(e,o){try{const t=await fetch(`${s}/api/icoa/gputask/${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o),signal:AbortSignal.timeout(15e3)}),n=await t.json();return n.data??n}catch{return null}}function y(e){const o=e.stats;if(!o)return void console.log(chalk.gray(` ${u("noStats")}`));const t=Math.max(0,Math.round(Date.now()/1e3-Number(o.at||0)));console.log(chalk.bold.cyan("zh"===c?" 实时占用":" Live utilisation")+chalk.gray(` (${t}s ago)`)),console.log(chalk.white(` GPU ${String(o.gpu_util_pct).padStart(3)}%`)+chalk.gray(" · ")+chalk.white(`VRAM ${o.vram_used_mb}/${o.vram_total_mb} MB`)+chalk.gray(" · ")+chalk.white(`${o.temp_c}°C · ${o.power_w} W`)),console.log(chalk.white(` RAM ${o.ram_used_mb}/${o.ram_total_mb} MB`)+chalk.gray(" · ")+chalk.white(`disk ${o.disk_used}/${o.disk_total} (${o.disk_pct})`)),console.log(chalk.bold.cyan("zh"===c?" RTX 4000 Ada 规格":" RTX 4000 Ada — card specs")),console.log(chalk.gray(" CUDA cores 6144 · Compute Capability 8.9 (sm_89) · FP32 ~26.7 TFLOPS")),console.log(chalk.gray(" VRAM 20 GB GDDR6 ECC · bandwidth ~360 GB/s · TDP 130 W · CUDA 12.6 (cu126 wheels)")),console.log(chalk.gray("zh"===c?" 整机:8 vCPU · 32 GB 内存 · 512 GB 磁盘 · Ubuntu 25.10 · Python 3.13":" Box: 8 vCPU · 32 GB RAM · 512 GB disk · Ubuntu 25.10 · Python 3.13"))}function h(e){console.log(),console.log(chalk.bold.green(` ${u("readyTitle")}`)),console.log(),console.log(chalk.white(` ${u("loginHow")}`)),console.log(chalk.bold.cyan(` ssh root@${e.ip}`)),console.log(chalk.bold.white(" password: ")+chalk.bold.yellow(e.root_pass??"?")),console.log(),console.log(chalk.green(` ${u("pkgsOk")}`)),function(e){const o=Math.min(process.stdout.columns||120,120),t=o>=100?3:o>=66?2:1,n=Math.floor((o-4)/t)-2,s=Math.ceil(e.length/t);for(let o=0;o<s;o++){let i=" ";for(let a=0;a<t;a++){const t=e[a*s+o];t&&(i+=t.padEnd(n).slice(0,n)+" ")}console.log(chalk.white(i.trimEnd()))}}(e.pkgs??[]),console.log(chalk.gray(` ${u("pkgsFree")}`)),console.log();const o=Math.round((e.seconds_left??0)/60);var t;console.log(chalk.yellow(` ${u("killLine1")}${t=e.kill_at??0,new Intl.DateTimeFormat("en-AU",{hour:"numeric",minute:"2-digit",hour12:!0,timeZone:"Australia/Sydney"}).format(new Date(1e3*t))} Sydney · ${o} ${u("minLeft")}`)),console.log(chalk.yellow(` ${u("killLine2")}`)),e.weekly_soft_over&&(console.log(),console.log(chalk.gray(` ${u("weekly").replace("{H}",String(e.weekly_hours??0))}`))),console.log(),console.log(chalk.gray(` ${u("footer")}`))}export function registerGpuForTaskCommand(e){e.command("gpufortask",{hidden:!0}).description("Committee GPU sandbox — clean 40-pkg IOAI environment (hidden)").action(async()=>{try{await async function(){c="en",function(){const e=n();e&&(i=e.listeners("line").slice(),e.removeAllListeners("line"),a=e.prompt.bind(e),e.prompt=o.prototype.prompt.bind(e),e.on("line",e=>{const o=l;l=null,o&&o(e)}))}();let e=null;try{console.log(),console.log(chalk.cyan.bold(` ${u("banner")}`)),console.log(chalk.gray(` ${u("langTip")}`)),console.log();const e=t();let o="";for(let t=0;t<3;){const n=(await r(chalk.cyan(` ${u("tokenAsk")}`))).trim(),i=n.toLowerCase();if("lang zh"===i){c="zh";continue}if("lang en"===i){c="en";continue}if("quit"===i||"back"===i||""===n){if(""===n)continue;return}const a=n.toUpperCase().replace(/\s+/g,""),l=await d("status",{token:a,fp:e});if(null===l)return void console.log(chalk.yellow(` ${u("netErr")}`));if("token_device_mismatch"!==l.error){if(!l.error){if(o=a,"none"!==l.state)return console.log(chalk.gray(` ${u("already")}`)),void await s(o,e,l);break}console.log(chalk.yellow(` ${u("tokenBad")}`)),t++}else console.log(chalk.yellow(` ${u("tokenDevice")}`)),t++}if(!o)return void console.log(chalk.yellow(` ${u("tokenGiveUp")}`));let n=0;for(;0===n;){const e=(await r(chalk.cyan(` ${u("hoursAsk")}`))).trim().toLowerCase();if("lang zh"===e){c="zh";continue}if("lang en"===e){c="en";continue}if("quit"===e||"back"===e)return;const o=Number.parseInt(e.replace(/\D/g,""),10);[1,2,3,5].includes(o)?n=o:console.log(chalk.yellow(` ${u("hoursBad")}`))}const i=await d("start",{token:o,fp:e,hours:n});if(null===i)return void console.log(chalk.yellow(` ${u("netErr")}`));if("all_seats_busy"===i.error)return void console.log(chalk.yellow(` ${u("seatsBusy")}`));if("session_already_active"===i.error){const t=await d("status",{token:o,fp:e});return void(t&&await s(o,e,t))}console.log(chalk.gray(` ${u("deploying")}`)),await s(o,e,i)}finally{e&&clearInterval(e),function(){const e=n();if(e){e.removeAllListeners("line"),a&&(e.prompt=a);for(const o of i)e.on("line",o);i=[],a=null,l=null}}()}async function s(o,t,n){let s=n,i="";const a=Date.now();for(;s&&"ready"!==s.state;){if("failed"===s.state||"destroyed"===s.state)return void console.log(chalk.red(` ${u("failed")}${s.fail_reason??s.state}`));const e="pending"===s.state||"creating"===s.state?u("stCreating"):"booting"===s.state?u("stBooting"):u("stVerifying");if(e!==i){const o=Math.round((Date.now()-a)/6e4);console.log(chalk.gray(` [${o}m] ${e}`)),i=e}await new Promise(e=>setTimeout(e,1e4)),s=await d("status",{token:o,fp:t})}if(!s)return void console.log(chalk.yellow(` ${u("netErr")}`));h(s);let l=!1;for(e=setInterval(async()=>{const e=await d("status",{token:o,fp:t});e&&!l&&"ready"===e.state&&(e.seconds_left??0)>0&&(e.seconds_left??0)<=600&&(l=!0,console.log(),console.log(chalk.bold.yellow(` ${u("warn10")}`)))},6e4);;){const e=(await r(chalk.cyan("icoa gpufortask> "))).trim().toLowerCase();if(""!==e)if("lang zh"!==e)if("lang en"!==e){if("quit"===e||"back"===e||"exit"===e)return void console.log(chalk.gray(` ${u("quitNote")}`));if("status"===e||"s"===e||"time"===e){const e=await d("status",{token:o,fp:t});if(e){if("ready"!==e.state)return void console.log(chalk.gray(` ${u("destroyed")}`));h(e)}else console.log(chalk.yellow(` ${u("netErr")}`));continue}if("stats"===e||"stat"===e||"usage"===e||"top"===e){const e=await d("status",{token:o,fp:t});if(e){if("ready"!==e.state)return void console.log(chalk.gray(` ${u("destroyed")}`));y(e)}else console.log(chalk.yellow(` ${u("netErr")}`));continue}if("stop"===e||"end"===e||"destroy"===e){const e=(await r(chalk.yellow(` ${u("stopAsk")}`))).trim().toLowerCase();if("y"===e||"yes"===e)return await d("stop",{token:o,fp:t}),void console.log(chalk.green(` ${u("stopping")}`));continue}console.log(chalk.gray(` ${u("footer")}`))}else c="en",console.log(chalk.gray(` ${u("footer")}`));else c="zh",console.log(chalk.gray(` ${u("footer")}`))}}}()}catch(e){const o=e instanceof Error?e.message:String(e);if("__REPL_NO_EXIT__"===o)throw e;console.log(chalk.yellow("\n gpufortask ended: "+o))}})}
@@ -1 +1 @@
1
- import{spawn as e}from"node:child_process";import{existsSync as o,mkdirSync as t,readFileSync as n,writeFileSync as r}from"node:fs";import{homedir as a}from"node:os";import{join as s}from"node:path";import{Interface as i,createInterface as c}from"node:readline";import chalk from"chalk";import{editTextInEditor as l}from"../lib/editor-spawn.js";import{buildSshArgs as g,leaseConnectProblems as u,parseLeaseResponse as y,queueLine as d}from"../lib/gpu.js";import{logCommand as m}from"../lib/logger.js";import{getMainRl as f}from"../lib/main-rl.js";import{openFile as p}from"../lib/open-file.js";import{printError as h}from"../lib/ui.js";const w=[{id:"pick-cube",env:"PrimeLite-PickCube-v0",blurb:"Robot arm picks a cube off the table (imitation-learning demo)"}],b=/^[a-z0-9-]{1,32}$/,v=/^MP4:(\/[A-Za-z0-9._/-]+\.mp4)\s*$/m,k=/^HDF5:(\/[A-Za-z0-9._/-]+\.hdf5)\s*$/m,$=/^CKPT:(\/[A-Za-z0-9._/-]+\.pth)\s*$/m,x=/^METRICS:(\{.*\})\s*$/m,P=/^algo(?:_used)?:\s*([a-z_]+)\s*$/im,_="/opt/ioailab-mj/work",A=`${_}/05_custom_agent.py`;function I(){return process.env.ICOA_GPU_SERVER?process.env.ICOA_GPU_SERVER.replace(/\/$/,""):"https://practice.icoa2026.au"}function U(){return s(a(),".icoa","gpu-key")}function S(){if(process.env.ICOA_GPU_KEY)return process.env.ICOA_GPU_KEY.trim();try{return n(U(),"utf-8").trim()||null}catch{return null}}function G(e){t(s(a(),".icoa"),{recursive:!0}),r(U(),`${e.trim()}\n`,{mode:384})}async function O(e,o,t){try{const n=await fetch(`${I()}/api/icoa/gpu/${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t?{key:o,task:t}:{key:o}),signal:AbortSignal.timeout(2e4)});return y(await n.json())}catch{return{state:"error",message:"GPU broker not reachable"}}}async function C(e){try{const o=await fetch(`${I()}/api/icoa/gpu/queue`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({key:e}),signal:AbortSignal.timeout(15e3)}),t=await o.json();return t&&!0===t.success?"ok":"invalid"}catch{return"offline"}}let L=null,T=null,E=null,R=!1,j=!1,q=!1,N=!1,Y=!1,M=null;function K(){return s(a(),"icoa-ioailab","05_custom_agent.py")}function V(o,t,n,r){return new Promise(a=>{const s="local"===o.transport?{command:"/bin/sh",args:["-c",n]}:{command:"ssh",args:g(o,t,n)},i=e(s.command,s.args,{stdio:["ignore","pipe","pipe"]});let c="",l="";const u=setTimeout(()=>i.kill("SIGKILL"),r);i.stdout.on("data",e=>{c+=e.toString(),l+=e.toString();let o=l.indexOf("\n");for(;o>=0;){const e=l.slice(0,o).trimEnd();l=l.slice(o+1),e.length>0&&console.log(chalk.white(` ${e}`)),o=l.indexOf("\n")}}),i.stderr.on("data",e=>{const o=e.toString().trimEnd();o.length>0&&console.log(chalk.gray(` ${o}`))}),i.on("close",e=>{clearTimeout(u),a({code:e??1,out:c})}),i.on("error",()=>{clearTimeout(u),a({code:127,out:c})})})}function z(o,t,n,a){return new Promise(s=>{const i=g(o,t,`cat ${n}`),c=e("ssh",i,{stdio:["ignore","pipe","pipe"]}),l=[],u=setTimeout(()=>c.kill("SIGKILL"),12e4);c.stdout.on("data",e=>l.push(e)),c.on("close",e=>{if(clearTimeout(u),0!==e||0===l.length)return s(!1);try{r(a,Buffer.concat(l)),s(!0)}catch{s(!1)}}),c.on("error",()=>{clearTimeout(u),s(!1)})})}async function D(o,t){if(!L)return!1;if("local"===L.transport)try{return r(t,n(o)),!0}catch{return!1}return function(o,t,r,a){return new Promise(s=>{let i;try{i=n(r)}catch{return s(!1)}const c=g(o,t,`mkdir -p ${_} && cat > ${a}`),l=e("ssh",c,{stdio:["pipe","ignore","pipe"]}),u=setTimeout(()=>l.kill("SIGKILL"),12e4);l.on("close",e=>{clearTimeout(u),s(0===e)}),l.on("error",()=>{clearTimeout(u),s(!1)}),l.stdin&&(l.stdin.on("error",()=>{}),l.stdin.write(i),l.stdin.end())})}(L,T,o,t)}function F(){return!(!L||"ssh"===L.transport&&!T)||(console.log(chalk.yellow(" No GPU box leased — ")+chalk.cyan("on")+chalk.gray(" first.")),!1)}function B(e,o,t){let n;for(let t=0;t<e.length;t++){const r=e[t];r===`--${o}`&&t+1<e.length?n=e[t+1]:r.startsWith(`--${o}=`)?n=r.slice(o.length+3):void 0===n&&/^[0-9]+$/.test(r)&&(n=r)}if(void 0===n)return t;const r=Number.parseInt(n,10);return Number.isFinite(r)?Math.min(999,Math.max(1,r)):t}async function W(e,o,t,n){if(!L||"ssh"===L.transport&&!T)return null;const r=w[0].id;return V(L,T??"",`sudo -n /usr/local/bin/ioailab-run ${r} ${e} --${o} ${t}`,n)}function J(e){4===e?console.log(chalk.yellow(" The box says an earlier step is missing — run it first (")+chalk.cyan("guide")+chalk.yellow(" shows where you are).")):2===e?console.log(chalk.red(" The box rejected the request (bad arguments).")):console.log(chalk.red(" Step failed on the box.")+chalk.gray(" (lease may have expired — ")+chalk.cyan("status")+chalk.gray(")"))}async function Z(e,o){if(!function(e){const o=L?.outDir??"";return!(""===o||!e.startsWith(o))||e===_||e.startsWith(`${_}/`)}(e))return void console.log(chalk.red(" Rejected unexpected video path from the box."));const i=s(a(),"icoa-ioailab");t(i,{recursive:!0});const c=s(i,`${o}-${Date.now()}.mp4`);process.stdout.write(chalk.gray(" Pulling the rendered clip…")),("local"===L?.transport?(()=>{try{return r(c,n(e)),!0}catch{return!1}})():await z(L,T,e,c))?(E=c,console.log(chalk.green(" done.")),console.log(chalk.magenta(" 🎬 clip → ")+chalk.cyan(c)),p(c)?console.log(chalk.gray(" Opened in your video viewer. ")+chalk.cyan("watch")+chalk.gray(" reopens it any time.")):console.log(chalk.gray(" Open it with your video player."))):console.log(chalk.red(" failed."))}function H(e){const o=e.match(x);if(!o)return void console.log(chalk.yellow(" Finished, but no metrics came back from the box."));let t;try{t=JSON.parse(o[1])}catch{return void console.log(chalk.yellow(" (metrics came back in an unexpected format.)"))}if(M=t,"number"==typeof t.success_rate){const e="number"==typeof t.episodes?` over ${t.episodes} episode(s)`:"";console.log(chalk.bold.green(` ★ success rate: ${(100*t.success_rate).toFixed(1)}%`)+chalk.gray(e))}for(const[e,o]of Object.entries(t))"success_rate"!==e&&"episodes"!==e&&("number"!=typeof o&&"string"!=typeof o||console.log(chalk.gray(` ${e}: `)+chalk.white(String(o))))}function Q(){let e;e=R?j?q?N?"edit custom":"eval":"train":"mimic":"collect",console.log(chalk.gray(" Next: ")+chalk.cyan(e)+chalk.gray(" (")+chalk.cyan("guide")+chalk.gray(" = where you are)"))}function X(){let e=!1;const o=[["collect",R],["mimic",j],["train",q],["eval",N],["custom",Y]].map(([o,t])=>t?`${o} ${chalk.green("✓")}`:e?`${o} ${chalk.gray("○")}`:(e=!0,`${o} ${chalk.yellow("…")}`));console.log(),console.log(chalk.bold.white(" ═══ 📖 IOAI Lab — the 5-step workflow ═══")),console.log(),console.log(chalk.gray(" Where you are: ")+o.join(chalk.gray(" · "))),console.log(" "+(L?chalk.green("⚡ lease active")+chalk.gray(" (")+chalk.cyan("status")+chalk.gray(" for time left)"):chalk.gray("no lease — ")+chalk.cyan("on")+chalk.gray(" to lease a box"))),M&&"number"==typeof M.success_rate&&console.log(chalk.gray(" Last result: ")+chalk.green(`${(100*M.success_rate).toFixed(1)}% success`)),console.log(),console.log(chalk.bold.white(" Your move — drive the contest workflow:")),console.log(chalk.gray(" 1. ")+chalk.cyan("collect")+chalk.gray(" expert demos → watch the arm pick the cube")),console.log(chalk.gray(" 2. ")+chalk.cyan("mimic")+chalk.gray(" expand your demos (pose-randomized, MimicGen-lite)")),console.log(chalk.gray(" 3. ")+chalk.cyan("train")+chalk.gray(" robomimic Diffusion Policy on YOUR demos (GPU, scored)")),console.log(chalk.gray(" 4. ")+chalk.cyan("eval")+chalk.gray(" run YOUR policy → success rate → watch")),console.log(chalk.gray(" 5. ")+chalk.cyan("edit custom")+chalk.gray(" / ")+chalk.cyan("custom")+chalk.gray(" write your own BaseAgent → run it → watch")),console.log(),console.log(chalk.bold.white(" 🤝 Want AI help? Set up your cockpit:")),console.log(chalk.gray(" 1. split the screen: ")+chalk.bold.cyan("Ctrl-b %")+chalk.gray(" (lesson/work left · AI right)")),console.log(chalk.gray(" 2. in the right pane: ")+chalk.bold.cyan("icoa")+chalk.gray(" then ")+chalk.bold.cyan("ai4ioai")+chalk.gray(" → live Gemma 3")),console.log(chalk.gray(" 3. ask it to explain a step or debug your agent — then ")+chalk.bold.white("VERIFY")+chalk.gray(" by re-running ")+chalk.cyan("eval")+chalk.gray(".")),console.log(),console.log(chalk.cyan(" on")+chalk.gray("/")+chalk.cyan("off")+chalk.gray(" lease a box · ")+chalk.cyan("status")+chalk.gray(" time left · ")+chalk.cyan("watch")+chalk.gray(" replay last clip · ")+chalk.cyan("run")+chalk.gray(" quick showcase · ")+chalk.cyan("exit")+chalk.gray(" leave")),console.log()}async function ee(e=!1){const o=S();o&&L&&await O("release",o),L&&!e&&console.log(chalk.gray(" GPU lease released.")),L=null,T=null,R=!1,j=!1,q=!1,N=!1,Y=!1,M=null}async function oe(e){const i=e.trim();if(0===i.length)return"continue";const[c,...g]=i.split(/\s+/),y=c.toLowerCase();return m(`ioailab ${y}`),"exit"===y||"quit"===y||"q"===y||"back"===y||"menu"===y?(await ee(!0),console.log(chalk.gray(" Leaving IOAI Lab — any GPU lease was released.")),"exit"):("guide"===y||"help"===y||"/guide"===y||"?"===y?X():"tasks"===y||"task"===y?function(){console.log();for(const e of w)console.log(chalk.cyan(` ${e.id.padEnd(12)}`)+chalk.gray(e.blurb));console.log()}():"key"===y?0===g.length?console.log(chalk.gray(" Usage: ")+chalk.cyan("key <your-access-key>")):(G(g[0]),console.log(chalk.green(" ✓ GPU access key saved.")+chalk.gray(" Next: ")+chalk.cyan("on"))):"on"===y||"lease"===y||"gpu"===y?await async function(){if(L)return void console.log(chalk.gray(" already holding a GPU lease — ")+chalk.cyan("status")+chalk.gray(" for time left."));const e=S();if(!e)return console.log(chalk.yellow(" No GPU access key found.")),void console.log(chalk.gray(" Save yours once with ")+chalk.cyan("key <your-key>")+chalk.gray(" (issued by your coach/ICOA)."));process.stdout.write(chalk.gray(" Requesting a GPU box…"));let o=await O("lease",e,"ioailab"),n=0;for(;"queued"===o.state&&n<24;)0===n&&console.log(),process.stdout.write(`\r ${chalk.yellow(d(o))} `),await new Promise(e=>setTimeout(e,5e3)),o=await O("status",e),n++;if("queued"===o.state)return console.log(),void console.log(chalk.yellow(" Still queued — your place is held server-side. Try ")+chalk.cyan("on")+chalk.gray(" again in a few minutes."));if("ready"!==o.state)return void console.log(chalk.red(" failed.")+chalk.gray(` ${o.message??"no lease granted"}`));const i=u(o);if("local"!==o.transport||process.env.ICOA_GPU_SERVER||i.push("local transport from a non-dev broker"),i.length>0)return console.log(chalk.red(" rejected: ")+chalk.gray(i.join(", "))),void await O("release",e);let c=null;if("ssh"===o.transport&&(c=function(e){const o=s(a(),".icoa","gpu","ioailab-lease-key");try{return t(s(a(),".icoa","gpu"),{recursive:!0}),r(o,e.keyPem,{mode:384}),o}catch{return null}}(o),!c))return console.log(chalk.red(" could not write the ssh key locally.")),void await O("release",e);L=o,T=c;const l=Math.round((o.timeLeftSec??0)/60);console.log(chalk.bold.green(" ⚡ GPU box leased.")+chalk.gray(` ~${l}m on this lease.`)),console.log(chalk.gray(" Next: ")+chalk.cyan("collect")+chalk.gray(" — start the 5-step chain (or ")+chalk.cyan("run")+chalk.gray(" for a quick one-shot showcase)."))}():"off"===y||"release"===y?await ee():"status"===y||"queue"===y?await async function(){const e=S();if(!e)return void console.log(chalk.yellow(" No GPU access key saved — ")+chalk.cyan("key <k>")+chalk.gray(" first."));const o=await O("status",e);if("ready"===o.state){const e=Math.round((o.timeLeftSec??0)/60);console.log(chalk.green(` ⚡ lease active · ~${e}m left.`))}else"queued"===o.state?console.log(` ${chalk.yellow(d(o))}`):console.log(chalk.gray(" no active lease — ")+chalk.cyan("on")+chalk.gray(" to request a box."))}():"run"===y?await async function(e){const o=w.find(o=>o.id===e);if(!o||!b.test(e))return void console.log(chalk.yellow(` Unknown task "${e}" — `)+chalk.cyan("tasks")+chalk.gray(" lists what is available."));if(!F())return;console.log(chalk.gray(` Running the ${o.id} task (ICOA-VLA arm) on the GPU box (headless — takes a moment)…`));const t=`sudo -n /usr/local/bin/ioailab-run ${e} 1`,{code:n,out:r}=await V(L,T??"",t,72e4);if(0!==n)return void console.log(chalk.red(" Episode failed on the box.")+chalk.gray(" (lease may have expired — ")+chalk.cyan("status")+chalk.gray(")"));const a=r.match(v);a?await Z(a[1],e):console.log(chalk.yellow(" Episode finished but no video path came back — box-side renderer issue."))}((g[0]??w[0].id).toLowerCase()):"collect"===y?await async function(e){if(!F())return;const o=B(e,"episodes",5);console.log(chalk.gray(` Collecting ${o} expert demo(s) on the ICOA-VLA arm (headless — takes a moment)…`));const t=await W("collect","episodes",o,72e4);if(!t)return;if(0!==t.code)return J(t.code);const n=t.out.match(k);n&&(R=!0,console.log(chalk.green(" ✓ expert demos recorded → ")+chalk.gray(n[1]))),console.log(chalk.gray(" The expert is a scripted reach→grasp→lift waypoint controller (our stand-in for")),console.log(chalk.gray(" the contest's motion planner) — a clean demonstration source, not a learned policy yet."));const r=t.out.match(v);r&&await Z(r[1],"collect"),Q()}(g):"mimic"===y?await async function(e){if(!F())return;if(!R)return void console.log(chalk.yellow(" No demos yet — run ")+chalk.cyan("collect")+chalk.gray(" first."));const o=B(e,"episodes",36);console.log(chalk.gray(` Expanding your demos into ${o} training episode(s)…`));const t=await W("mimic","episodes",o,72e4);if(!t)return;if(0!==t.code)return J(t.code);const n=t.out.match(k);n&&(j=!0,console.log(chalk.green(" ✓ training demos generated → ")+chalk.gray(n[1]))),console.log(chalk.gray(" MimicGen-lite: we randomize the cube pose and re-run the pose-conditioned expert —")),console.log(chalk.gray(" real rollouts, same robomimic schema, not the object-centric MimicGen the full sim uses.")),Q()}(g):"train"===y?await async function(e){if(!F())return;if(!j)return void console.log(chalk.yellow(" No training set yet — run ")+chalk.cyan("mimic")+chalk.gray(" first."));const o=B(e,"epochs",20),t=54e5,n=L?.timeLeftSec??0;if(n>0&&1e3*n<t){const e=Math.round(n/60);console.log(chalk.yellow(` ⚠ your lease has ~${e}m left`)+chalk.gray(" — a long train may outlast it. ")+chalk.cyan("off")+chalk.gray("/")+chalk.cyan("on")+chalk.gray(" to refresh first if needed."))}console.log(chalk.gray(` Training a robomimic Diffusion Policy for ${o} epoch(s) on YOUR demos — the scored step (GPU)…`));const r=await W("train","epochs",o,t);if(!r)return;if(0!==r.code)return J(r.code);const a=r.out.match($);a&&(q=!0,console.log(chalk.green(" ✓ checkpoint saved → ")+chalk.gray(a[1])));const s=r.out.match(P);s&&/bc/i.test(s[1])?console.log(chalk.gray(" (behavior-cloning fallback — real diffusion pending on this box.)")):console.log(chalk.gray(" Same open robomimic Diffusion Policy the real contest uses — high-fidelity where it counts.")),Q()}(g):"eval"===y?await async function(e){if(!F())return;if(!q)return void console.log(chalk.yellow(" No trained policy yet — run ")+chalk.cyan("train")+chalk.gray(" first."));const o=B(e,"episodes",20);console.log(chalk.gray(` Running YOUR trained policy for ${o} episode(s)…`));const t=await W("eval","episodes",o,72e4);if(!t)return;if(0!==t.code)return J(t.code);H(t.out),N=!0,console.log(chalk.gray(" Your number reflects the practice arm + your demo count/epochs — it's the workflow signal,")),console.log(chalk.gray(" not a contest leaderboard score."));const n=t.out.match(v);n&&await Z(n[1],"eval"),Q()}(g):"custom"===y?await async function(e){if(!F())return;const t=K();if(!o(t))return void console.log(chalk.yellow(" No custom agent yet — ")+chalk.cyan("edit custom")+chalk.gray(" to write one first."));process.stdout.write(chalk.gray(" Uploading your agent…"));const n=await D(t,A);if(console.log(n?chalk.green(" done."):chalk.red(" failed.")),!n)return;const r=B(e,"episodes",9);console.log(chalk.gray(` Running YOUR agent for ${r} episode(s)…`));const a=await W("custom","episodes",r,72e4);if(!a)return;if(0!==a.code)return J(a.code);H(a.out),Y=!0;const s=a.out.match(v);s?await Z(s[1],"custom"):console.log(chalk.gray(" (no replay — for it to render, your agent must be constructible with no required args. Metrics above.)")),console.log(chalk.gray(" Iterate: ")+chalk.cyan("edit custom")+chalk.gray(" → ")+chalk.cyan("custom")+chalk.gray(" → watch, repeat."))}(g):"edit"===y?"custom"===(g[0]??"").toLowerCase()?await async function(){if(!F())return;const e=s(a(),"icoa-ioailab");t(e,{recursive:!0});const i=K();o(i)||(process.stdout.write(chalk.gray(" Fetching the starter agent…")),await async function(e,o){if(!L)return!1;if("local"===L.transport)try{return r(o,n(e)),!0}catch{return!1}return z(L,T,e,o)}("/opt/ioailab-mj/examples/05_custom_agent.py",i)?console.log(chalk.green(" done.")):(r(i,'"""05_custom_agent.py — your agent for the ICOA-VLA arm.\n\nSubclass BaseAgent and implement act(env, env_ids) -> array [rows, action_dim].\nKeep the constructor argument-free so the replay can render your agent\n(like the SinusoidAgent starter below). Then: edit custom -> custom -> watch.\n"""\nimport numpy as np\n\ntry:\n from icoa_ioailab.agents import BaseAgent\nexcept Exception: # resolved on the GPU box\n BaseAgent = object\n\n\nclass SinusoidAgent(BaseAgent):\n def __init__(self):\n self._t = 0\n\n def act(self, env, env_ids):\n rows = len(env_ids)\n dim = getattr(env, "action_dim", 7)\n self._t += 1\n cmd = 0.1 * np.sin(self._t * 0.2)\n return np.full((rows, dim), cmd, dtype=np.float32)\n'),console.log(chalk.gray(" using a local starter."))));const c=n(i,"utf-8"),g=l(c,".py");if(null===g)return void console.log(chalk.yellow(" Editor cancelled — agent unchanged.")+chalk.gray(" (set $EDITOR to choose your editor)"));r(i,g),process.stdout.write(chalk.gray(" Saving to the box…"));const u=await D(i,A);console.log(u?chalk.green(" done."):chalk.red(" failed.")),console.log(chalk.gray(" Saved → run ")+chalk.cyan("custom")+chalk.gray(" to try it."))}():console.log(chalk.gray(" Usage: ")+chalk.cyan("edit custom")+chalk.gray(" (write your own agent)")):"watch"===y||"open"===y?E&&p(E)?console.log(chalk.gray(" Reopened the last clip.")):console.log(chalk.gray(" No clip yet — ")+chalk.cyan("collect")+chalk.gray(" or ")+chalk.cyan("run")+chalk.gray(" first.")):console.log(chalk.yellow(` Unknown command "${y}" — `)+chalk.cyan("guide")+chalk.gray(" lists everything.")),"continue")}export async function runIoailabStandalone(){if(!await async function(){const{input:e}=await import("@inquirer/prompts"),o=S();if(o){const e=await C(o);if("ok"===e)return!0;if("offline"===e)return console.log(chalk.yellow(" Cannot reach the GPU service to verify your key right now — try again later.")),!1;console.log(chalk.yellow(" Your saved GPU key was not accepted. Enter a valid one below."))}else console.log(chalk.gray(" IOAI Lab is a paid GPU service. Enter the GPU access key issued with your purchase."));for(let o=0;o<3;o++){let t;try{t=await e({message:chalk.bold.magenta("icoa ioailab key>"),theme:{prefix:"",style:{message:e=>e}}})}catch{return!1}const n=t.trim();if(""===n||"exit"===n.toLowerCase()||"q"===n.toLowerCase())return!1;const r=await C(n);if("ok"===r)return G(n),console.log(chalk.green(" ✓ Key accepted — saved to this device. You will not be asked again.")),!0;if("offline"===r)return console.log(chalk.yellow(" Cannot reach the GPU service to verify — try again later.")),!1;console.log(chalk.red(` Key not recognised (${o<2?2-o+" tries left":"no tries left"}).`))}return!1}())return;console.log(),console.log(chalk.bold.magenta(" 🤖 IOAI Lab — Embodied AI practice twin (ICOA-VLA arm)")),console.log(chalk.gray(" The official Team-Challenge sim uses assets we can't hand out — so this is a")),console.log(chalk.gray(" practice twin: the same 5-step workflow, the same Python API, a real arm you drive.")),console.log(chalk.gray(" The scored part — training a diffusion policy from your own demos with robomimic")),console.log(chalk.gray(" (open source) — is byte-for-byte the framework the real contest uses.")),console.log(chalk.gray(" Runs on an ICOA GPU box — nothing installs here; the video opens locally.")),console.log(),console.log(chalk.white(" You will drive the exact contest workflow:")),console.log(" "+chalk.bold.cyan("collect")+chalk.gray(" → ")+chalk.bold.cyan("mimic")+chalk.gray(" → ")+chalk.bold.cyan("train")+chalk.gray(" → ")+chalk.bold.cyan("eval")+chalk.gray(" → ")+chalk.bold.cyan("custom")),console.log(chalk.gray(" v1 is a single task (pick-cube) on a MuJoCo arm; the real contest is multi-task on a")),console.log(chalk.gray(" higher-fidelity simulator — the learning core is the same.")),X();const e=f(),o=null!==e,t=o?e.listeners("line").slice():[];o&&e.removeAllListeners("line");const n=o?e:c({input:process.stdin,output:process.stdout,terminal:!0}),r=o?n.prompt:null;o&&(n.prompt=i.prototype.prompt);const a=()=>{n.setPrompt(chalk.bold.magenta("icoa ioailab> ")),n.prompt()};return new Promise(e=>{const s=i=>{oe(i).then(i=>{"exit"===i?((()=>{if(n.removeListener("line",s),o){r&&(n.prompt=r);for(const e of t)n.on("line",e)}else n.close()})(),e()):a()}).catch(e=>{h(`ioailab error: ${e instanceof Error?e.message:String(e)}`),a()})};n.on("line",s),a()})}
1
+ import{spawn as e}from"node:child_process";import{existsSync as o,mkdirSync as t,readFileSync as n,writeFileSync as r}from"node:fs";import{homedir as a}from"node:os";import{join as s}from"node:path";import{Interface as i,createInterface as c}from"node:readline";import chalk from"chalk";import{editTextInEditor as l}from"../lib/editor-spawn.js";import{buildSshArgs as g,leaseConnectProblems as u,parseLeaseResponse as y,queueLine as d,windowLine as m}from"../lib/gpu.js";import{logCommand as f}from"../lib/logger.js";import{getMainRl as p}from"../lib/main-rl.js";import{openFile as h}from"../lib/open-file.js";import{printError as w}from"../lib/ui.js";const b=[{id:"pick-cube",env:"PrimeLite-PickCube-v0",blurb:"Robot arm picks a cube off the table (imitation-learning demo)"}],v=/^[a-z0-9-]{1,32}$/,k=/^MP4:(\/[A-Za-z0-9._/-]+\.mp4)\s*$/m,$=/^HDF5:(\/[A-Za-z0-9._/-]+\.hdf5)\s*$/m,x=/^CKPT:(\/[A-Za-z0-9._/-]+\.pth)\s*$/m,P=/^METRICS:(\{.*\})\s*$/m,_=/^algo(?:_used)?:\s*([a-z_]+)\s*$/im,U="/opt/ioailab-mj/work",A=`${U}/05_custom_agent.py`;function I(){return process.env.ICOA_GPU_SERVER?process.env.ICOA_GPU_SERVER.replace(/\/$/,""):"https://practice.icoa2026.au"}function G(){return s(a(),".icoa","gpu-key")}function S(){if(process.env.ICOA_GPU_KEY)return process.env.ICOA_GPU_KEY.trim();try{return n(G(),"utf-8").trim()||null}catch{return null}}function O(e){t(s(a(),".icoa"),{recursive:!0}),r(G(),`${e.trim()}\n`,{mode:384})}async function C(e,o,t){try{const n=await fetch(`${I()}/api/icoa/gpu/${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t?{key:o,task:t}:{key:o}),signal:AbortSignal.timeout(2e4)});return y(await n.json())}catch{return{state:"error",message:"GPU broker not reachable"}}}async function T(e){try{const o=await fetch(`${I()}/api/icoa/gpu/queue`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({key:e}),signal:AbortSignal.timeout(15e3)}),t=await o.json();return t&&!0===t.success?"ok":"invalid"}catch{return"offline"}}let L=null,E=null,R=null,j=!1,q=!1,N=!1,Y=!1,M=!1,K=null;function V(){return s(a(),"icoa-ioailab","05_custom_agent.py")}function z(o,t,n,r){return new Promise(a=>{const s="local"===o.transport?{command:"/bin/sh",args:["-c",n]}:{command:"ssh",args:g(o,t,n)},i=e(s.command,s.args,{stdio:["ignore","pipe","pipe"]});let c="",l="";const u=setTimeout(()=>i.kill("SIGKILL"),r);i.stdout.on("data",e=>{c+=e.toString(),l+=e.toString();let o=l.indexOf("\n");for(;o>=0;){const e=l.slice(0,o).trimEnd();l=l.slice(o+1),e.length>0&&console.log(chalk.white(` ${e}`)),o=l.indexOf("\n")}}),i.stderr.on("data",e=>{const o=e.toString().trimEnd();o.length>0&&console.log(chalk.gray(` ${o}`))}),i.on("close",e=>{clearTimeout(u),a({code:e??1,out:c})}),i.on("error",()=>{clearTimeout(u),a({code:127,out:c})})})}function D(o,t,n,a){return new Promise(s=>{const i=g(o,t,`cat ${n}`),c=e("ssh",i,{stdio:["ignore","pipe","pipe"]}),l=[],u=setTimeout(()=>c.kill("SIGKILL"),12e4);c.stdout.on("data",e=>l.push(e)),c.on("close",e=>{if(clearTimeout(u),0!==e||0===l.length)return s(!1);try{r(a,Buffer.concat(l)),s(!0)}catch{s(!1)}}),c.on("error",()=>{clearTimeout(u),s(!1)})})}async function F(o,t){if(!L)return!1;if("local"===L.transport)try{return r(t,n(o)),!0}catch{return!1}return function(o,t,r,a){return new Promise(s=>{let i;try{i=n(r)}catch{return s(!1)}const c=g(o,t,`mkdir -p ${U} && cat > ${a}`),l=e("ssh",c,{stdio:["pipe","ignore","pipe"]}),u=setTimeout(()=>l.kill("SIGKILL"),12e4);l.on("close",e=>{clearTimeout(u),s(0===e)}),l.on("error",()=>{clearTimeout(u),s(!1)}),l.stdin&&(l.stdin.on("error",()=>{}),l.stdin.write(i),l.stdin.end())})}(L,E,o,t)}function B(){return!(!L||"ssh"===L.transport&&!E)||(console.log(chalk.yellow(" No GPU box leased — ")+chalk.cyan("on")+chalk.gray(" first.")),!1)}function W(e,o,t){let n;for(let t=0;t<e.length;t++){const r=e[t];r===`--${o}`&&t+1<e.length?n=e[t+1]:r.startsWith(`--${o}=`)?n=r.slice(o.length+3):void 0===n&&/^[0-9]+$/.test(r)&&(n=r)}if(void 0===n)return t;const r=Number.parseInt(n,10);return Number.isFinite(r)?Math.min(999,Math.max(1,r)):t}async function J(e,o,t,n){if(!L||"ssh"===L.transport&&!E)return null;const r=b[0].id;return z(L,E??"",`sudo -n /usr/local/bin/ioailab-run ${r} ${e} --${o} ${t}`,n)}function Z(e){4===e?console.log(chalk.yellow(" The box says an earlier step is missing — run it first (")+chalk.cyan("guide")+chalk.yellow(" shows where you are).")):2===e?console.log(chalk.red(" The box rejected the request (bad arguments).")):console.log(chalk.red(" Step failed on the box.")+chalk.gray(" (lease may have expired — ")+chalk.cyan("status")+chalk.gray(")"))}async function H(e,o){if(!function(e){const o=L?.outDir??"";return!(""===o||!e.startsWith(o))||e===U||e.startsWith(`${U}/`)}(e))return void console.log(chalk.red(" Rejected unexpected video path from the box."));const i=s(a(),"icoa-ioailab");t(i,{recursive:!0});const c=s(i,`${o}-${Date.now()}.mp4`);process.stdout.write(chalk.gray(" Pulling the rendered clip…")),("local"===L?.transport?(()=>{try{return r(c,n(e)),!0}catch{return!1}})():await D(L,E,e,c))?(R=c,console.log(chalk.green(" done.")),console.log(chalk.magenta(" 🎬 clip → ")+chalk.cyan(c)),h(c)?console.log(chalk.gray(" Opened in your video viewer. ")+chalk.cyan("watch")+chalk.gray(" reopens it any time.")):console.log(chalk.gray(" Open it with your video player."))):console.log(chalk.red(" failed."))}function Q(e){const o=e.match(P);if(!o)return void console.log(chalk.yellow(" Finished, but no metrics came back from the box."));let t;try{t=JSON.parse(o[1])}catch{return void console.log(chalk.yellow(" (metrics came back in an unexpected format.)"))}if(K=t,"number"==typeof t.success_rate){const e="number"==typeof t.episodes?` over ${t.episodes} episode(s)`:"";console.log(chalk.bold.green(` ★ success rate: ${(100*t.success_rate).toFixed(1)}%`)+chalk.gray(e))}for(const[e,o]of Object.entries(t))"success_rate"!==e&&"episodes"!==e&&("number"!=typeof o&&"string"!=typeof o||console.log(chalk.gray(` ${e}: `)+chalk.white(String(o))))}function X(){let e;e=j?q?N?Y?"edit custom":"eval":"train":"mimic":"collect",console.log(chalk.gray(" Next: ")+chalk.cyan(e)+chalk.gray(" (")+chalk.cyan("guide")+chalk.gray(" = where you are)"))}function ee(){let e=!1;const o=[["collect",j],["mimic",q],["train",N],["eval",Y],["custom",M]].map(([o,t])=>t?`${o} ${chalk.green("✓")}`:e?`${o} ${chalk.gray("○")}`:(e=!0,`${o} ${chalk.yellow("…")}`));console.log(),console.log(chalk.bold.white(" ═══ 📖 IOAI Lab — the 5-step workflow ═══")),console.log(),console.log(chalk.gray(" Where you are: ")+o.join(chalk.gray(" · "))),console.log(" "+(L?chalk.green("⚡ lease active")+chalk.gray(" (")+chalk.cyan("status")+chalk.gray(" for time left)"):chalk.gray("no lease — ")+chalk.cyan("on")+chalk.gray(" to lease a box"))),K&&"number"==typeof K.success_rate&&console.log(chalk.gray(" Last result: ")+chalk.green(`${(100*K.success_rate).toFixed(1)}% success`)),console.log(),console.log(chalk.bold.white(" Your move — drive the contest workflow:")),console.log(chalk.gray(" 1. ")+chalk.cyan("collect")+chalk.gray(" expert demos → watch the arm pick the cube")),console.log(chalk.gray(" 2. ")+chalk.cyan("mimic")+chalk.gray(" expand your demos (pose-randomized, MimicGen-lite)")),console.log(chalk.gray(" 3. ")+chalk.cyan("train")+chalk.gray(" robomimic Diffusion Policy on YOUR demos (GPU, scored)")),console.log(chalk.gray(" 4. ")+chalk.cyan("eval")+chalk.gray(" run YOUR policy → success rate → watch")),console.log(chalk.gray(" 5. ")+chalk.cyan("edit custom")+chalk.gray(" / ")+chalk.cyan("custom")+chalk.gray(" write your own BaseAgent → run it → watch")),console.log(),console.log(chalk.bold.white(" 🤝 Want AI help? Set up your cockpit:")),console.log(chalk.gray(" 1. split the screen: ")+chalk.bold.cyan("Ctrl-b %")+chalk.gray(" (lesson/work left · AI right)")),console.log(chalk.gray(" 2. in the right pane: ")+chalk.bold.cyan("icoa")+chalk.gray(" then ")+chalk.bold.cyan("ai4ioai")+chalk.gray(" → live Gemma 3")),console.log(chalk.gray(" 3. ask it to explain a step or debug your agent — then ")+chalk.bold.white("VERIFY")+chalk.gray(" by re-running ")+chalk.cyan("eval")+chalk.gray(".")),console.log(),console.log(chalk.cyan(" on")+chalk.gray("/")+chalk.cyan("off")+chalk.gray(" lease a box · ")+chalk.cyan("status")+chalk.gray(" time left · ")+chalk.cyan("watch")+chalk.gray(" replay last clip · ")+chalk.cyan("run")+chalk.gray(" quick showcase · ")+chalk.cyan("exit")+chalk.gray(" leave")),console.log()}async function oe(e=!1){const o=S();o&&L&&await C("release",o),L&&!e&&console.log(chalk.gray(" GPU lease released.")),L=null,E=null,j=!1,q=!1,N=!1,Y=!1,M=!1,K=null}async function te(e){const i=e.trim();if(0===i.length)return"continue";const[c,...g]=i.split(/\s+/),y=c.toLowerCase();return f(`ioailab ${y}`),"exit"===y||"quit"===y||"q"===y||"back"===y||"menu"===y?(await oe(!0),console.log(chalk.gray(" Leaving IOAI Lab — any GPU lease was released.")),"exit"):("guide"===y||"help"===y||"/guide"===y||"?"===y?ee():"tasks"===y||"task"===y?function(){console.log();for(const e of b)console.log(chalk.cyan(` ${e.id.padEnd(12)}`)+chalk.gray(e.blurb));console.log()}():"key"===y?0===g.length?console.log(chalk.gray(" Usage: ")+chalk.cyan("key <your-access-key>")):(O(g[0]),console.log(chalk.green(" ✓ GPU access key saved.")+chalk.gray(" Next: ")+chalk.cyan("on"))):"on"===y||"lease"===y||"gpu"===y?await async function(){if(L)return void console.log(chalk.gray(" already holding a GPU lease — ")+chalk.cyan("status")+chalk.gray(" for time left."));const e=S();if(!e)return console.log(chalk.yellow(" No GPU access key found.")),void console.log(chalk.gray(" Save yours once with ")+chalk.cyan("key <your-key>")+chalk.gray(" (issued by your coach/ICOA)."));process.stdout.write(chalk.gray(" Requesting a GPU box…"));let o=await C("lease",e,"ioailab"),n=0;for(;"queued"===o.state&&n<24;)0===n&&console.log(),process.stdout.write(`\r ${chalk.yellow(d(o))} `),await new Promise(e=>setTimeout(e,5e3)),o=await C("status",e),n++;if("queued"===o.state)return console.log(),void console.log(chalk.yellow(" Still queued — your place is held server-side. Try ")+chalk.cyan("on")+chalk.gray(" again in a few minutes."));if("ready"!==o.state)return void console.log(chalk.red(" failed.")+chalk.gray(` ${o.message??"no lease granted"}`));const i=u(o);if("local"!==o.transport||process.env.ICOA_GPU_SERVER||i.push("local transport from a non-dev broker"),i.length>0)return console.log(chalk.red(" rejected: ")+chalk.gray(i.join(", "))),void await C("release",e);let c=null;if("ssh"===o.transport&&(c=function(e){const o=s(a(),".icoa","gpu","ioailab-lease-key");try{return t(s(a(),".icoa","gpu"),{recursive:!0}),r(o,e.keyPem,{mode:384}),o}catch{return null}}(o),!c))return console.log(chalk.red(" could not write the ssh key locally.")),void await C("release",e);L=o,E=c;const l=Math.round((o.timeLeftSec??0)/60);console.log(chalk.bold.green(" ⚡ GPU box leased.")+chalk.gray(` ~${l}m on this lease.`));const g=m(o.windowCloseEpoch);g&&console.log(chalk.gray(` ⏱ Tonight's GPU window: ${g}.`)),console.log(chalk.gray(" Next: ")+chalk.cyan("collect")+chalk.gray(" — start the 5-step chain (or ")+chalk.cyan("run")+chalk.gray(" for a quick one-shot showcase)."))}():"off"===y||"release"===y?await oe():"status"===y||"queue"===y?await async function(){const e=S();if(!e)return void console.log(chalk.yellow(" No GPU access key saved — ")+chalk.cyan("key <k>")+chalk.gray(" first."));const o=await C("status",e);if("ready"===o.state){const e=Math.round((o.timeLeftSec??0)/60);console.log(chalk.green(` ⚡ lease active · ~${e}m left.`));const t=m(o.windowCloseEpoch);t&&console.log(chalk.gray(` ⏱ Tonight's GPU window: ${t}.`))}else"queued"===o.state?console.log(` ${chalk.yellow(d(o))}`):console.log(chalk.gray(" no active lease — ")+chalk.cyan("on")+chalk.gray(" to request a box."))}():"run"===y?await async function(e){const o=b.find(o=>o.id===e);if(!o||!v.test(e))return void console.log(chalk.yellow(` Unknown task "${e}" — `)+chalk.cyan("tasks")+chalk.gray(" lists what is available."));if(!B())return;console.log(chalk.gray(` Running the ${o.id} task (ICOA-VLA arm) on the GPU box (headless — takes a moment)…`));const t=`sudo -n /usr/local/bin/ioailab-run ${e} 1`,{code:n,out:r}=await z(L,E??"",t,72e4);if(0!==n)return void console.log(chalk.red(" Episode failed on the box.")+chalk.gray(" (lease may have expired — ")+chalk.cyan("status")+chalk.gray(")"));const a=r.match(k);a?await H(a[1],e):console.log(chalk.yellow(" Episode finished but no video path came back — box-side renderer issue."))}((g[0]??b[0].id).toLowerCase()):"collect"===y?await async function(e){if(!B())return;const o=W(e,"episodes",5);console.log(chalk.gray(` Collecting ${o} expert demo(s) on the ICOA-VLA arm (headless — takes a moment)…`));const t=await J("collect","episodes",o,72e4);if(!t)return;if(0!==t.code)return Z(t.code);const n=t.out.match($);n&&(j=!0,console.log(chalk.green(" ✓ expert demos recorded → ")+chalk.gray(n[1]))),console.log(chalk.gray(" The expert is a scripted reach→grasp→lift waypoint controller (our stand-in for")),console.log(chalk.gray(" the contest's motion planner) — a clean demonstration source, not a learned policy yet."));const r=t.out.match(k);r&&await H(r[1],"collect"),X()}(g):"mimic"===y?await async function(e){if(!B())return;if(!j)return void console.log(chalk.yellow(" No demos yet — run ")+chalk.cyan("collect")+chalk.gray(" first."));const o=W(e,"episodes",36);console.log(chalk.gray(` Expanding your demos into ${o} training episode(s)…`));const t=await J("mimic","episodes",o,72e4);if(!t)return;if(0!==t.code)return Z(t.code);const n=t.out.match($);n&&(q=!0,console.log(chalk.green(" ✓ training demos generated → ")+chalk.gray(n[1]))),console.log(chalk.gray(" MimicGen-lite: we randomize the cube pose and re-run the pose-conditioned expert —")),console.log(chalk.gray(" real rollouts, same robomimic schema, not the object-centric MimicGen the full sim uses.")),X()}(g):"train"===y?await async function(e){if(!B())return;if(!q)return void console.log(chalk.yellow(" No training set yet — run ")+chalk.cyan("mimic")+chalk.gray(" first."));const o=W(e,"epochs",20),t=54e5,n=L?.timeLeftSec??0;if(n>0&&1e3*n<t){const e=Math.round(n/60);console.log(chalk.yellow(` ⚠ your lease has ~${e}m left`)+chalk.gray(" — a long train may outlast it. ")+chalk.cyan("off")+chalk.gray("/")+chalk.cyan("on")+chalk.gray(" to refresh first if needed."))}console.log(chalk.gray(` Training a robomimic Diffusion Policy for ${o} epoch(s) on YOUR demos — the scored step (GPU)…`));const r=await J("train","epochs",o,t);if(!r)return;if(0!==r.code)return Z(r.code);const a=r.out.match(x);a&&(N=!0,console.log(chalk.green(" ✓ checkpoint saved → ")+chalk.gray(a[1])));const s=r.out.match(_);s&&/bc/i.test(s[1])?console.log(chalk.gray(" (behavior-cloning fallback — real diffusion pending on this box.)")):console.log(chalk.gray(" Same open robomimic Diffusion Policy the real contest uses — high-fidelity where it counts.")),X()}(g):"eval"===y?await async function(e){if(!B())return;if(!N)return void console.log(chalk.yellow(" No trained policy yet — run ")+chalk.cyan("train")+chalk.gray(" first."));const o=W(e,"episodes",20);console.log(chalk.gray(` Running YOUR trained policy for ${o} episode(s)…`));const t=await J("eval","episodes",o,72e4);if(!t)return;if(0!==t.code)return Z(t.code);Q(t.out),Y=!0,console.log(chalk.gray(" Your number reflects the practice arm + your demo count/epochs — it's the workflow signal,")),console.log(chalk.gray(" not a contest leaderboard score."));const n=t.out.match(k);n&&await H(n[1],"eval"),X()}(g):"custom"===y?await async function(e){if(!B())return;const t=V();if(!o(t))return void console.log(chalk.yellow(" No custom agent yet — ")+chalk.cyan("edit custom")+chalk.gray(" to write one first."));process.stdout.write(chalk.gray(" Uploading your agent…"));const n=await F(t,A);if(console.log(n?chalk.green(" done."):chalk.red(" failed.")),!n)return;const r=W(e,"episodes",9);console.log(chalk.gray(` Running YOUR agent for ${r} episode(s)…`));const a=await J("custom","episodes",r,72e4);if(!a)return;if(0!==a.code)return Z(a.code);Q(a.out),M=!0;const s=a.out.match(k);s?await H(s[1],"custom"):console.log(chalk.gray(" (no replay — for it to render, your agent must be constructible with no required args. Metrics above.)")),console.log(chalk.gray(" Iterate: ")+chalk.cyan("edit custom")+chalk.gray(" → ")+chalk.cyan("custom")+chalk.gray(" → watch, repeat."))}(g):"edit"===y?"custom"===(g[0]??"").toLowerCase()?await async function(){if(!B())return;const e=s(a(),"icoa-ioailab");t(e,{recursive:!0});const i=V();o(i)||(process.stdout.write(chalk.gray(" Fetching the starter agent…")),await async function(e,o){if(!L)return!1;if("local"===L.transport)try{return r(o,n(e)),!0}catch{return!1}return D(L,E,e,o)}("/opt/ioailab-mj/examples/05_custom_agent.py",i)?console.log(chalk.green(" done.")):(r(i,'"""05_custom_agent.py — your agent for the ICOA-VLA arm.\n\nSubclass BaseAgent and implement act(env, env_ids) -> array [rows, action_dim].\nKeep the constructor argument-free so the replay can render your agent\n(like the SinusoidAgent starter below). Then: edit custom -> custom -> watch.\n"""\nimport numpy as np\n\ntry:\n from icoa_ioailab.agents import BaseAgent\nexcept Exception: # resolved on the GPU box\n BaseAgent = object\n\n\nclass SinusoidAgent(BaseAgent):\n def __init__(self):\n self._t = 0\n\n def act(self, env, env_ids):\n rows = len(env_ids)\n dim = getattr(env, "action_dim", 7)\n self._t += 1\n cmd = 0.1 * np.sin(self._t * 0.2)\n return np.full((rows, dim), cmd, dtype=np.float32)\n'),console.log(chalk.gray(" using a local starter."))));const c=n(i,"utf-8"),g=l(c,".py");if(null===g)return void console.log(chalk.yellow(" Editor cancelled — agent unchanged.")+chalk.gray(" (set $EDITOR to choose your editor)"));r(i,g),process.stdout.write(chalk.gray(" Saving to the box…"));const u=await F(i,A);console.log(u?chalk.green(" done."):chalk.red(" failed.")),console.log(chalk.gray(" Saved → run ")+chalk.cyan("custom")+chalk.gray(" to try it."))}():console.log(chalk.gray(" Usage: ")+chalk.cyan("edit custom")+chalk.gray(" (write your own agent)")):"watch"===y||"open"===y?R&&h(R)?console.log(chalk.gray(" Reopened the last clip.")):console.log(chalk.gray(" No clip yet — ")+chalk.cyan("collect")+chalk.gray(" or ")+chalk.cyan("run")+chalk.gray(" first.")):console.log(chalk.yellow(` Unknown command "${y}" — `)+chalk.cyan("guide")+chalk.gray(" lists everything.")),"continue")}export async function runIoailabStandalone(){if(!await async function(){const{input:e}=await import("@inquirer/prompts"),o=S();if(o){const e=await T(o);if("ok"===e)return!0;if("offline"===e)return console.log(chalk.yellow(" Cannot reach the GPU service to verify your key right now — try again later.")),!1;console.log(chalk.yellow(" Your saved GPU key was not accepted. Enter a valid one below."))}else console.log(chalk.gray(" IOAI Lab is a paid GPU service. Enter the GPU access key issued with your purchase."));for(let o=0;o<3;o++){let t;try{t=await e({message:chalk.bold.magenta("icoa ioailab key>"),theme:{prefix:"",style:{message:e=>e}}})}catch{return!1}const n=t.trim();if(""===n||"exit"===n.toLowerCase()||"q"===n.toLowerCase())return!1;const r=await T(n);if("ok"===r)return O(n),console.log(chalk.green(" ✓ Key accepted — saved to this device. You will not be asked again.")),!0;if("offline"===r)return console.log(chalk.yellow(" Cannot reach the GPU service to verify — try again later.")),!1;console.log(chalk.red(` Key not recognised (${o<2?2-o+" tries left":"no tries left"}).`))}return!1}())return;console.log(),console.log(chalk.bold.magenta(" 🤖 IOAI Lab — Embodied AI practice twin (ICOA-VLA arm)")),console.log(chalk.gray(" The official Team-Challenge sim uses assets we can't hand out — so this is a")),console.log(chalk.gray(" practice twin: the same 5-step workflow, the same Python API, a real arm you drive.")),console.log(chalk.gray(" The scored part — training a diffusion policy from your own demos with robomimic")),console.log(chalk.gray(" (open source) — is byte-for-byte the framework the real contest uses.")),console.log(chalk.gray(" Runs on an ICOA GPU box — nothing installs here; the video opens locally.")),console.log(),console.log(chalk.white(" You will drive the exact contest workflow:")),console.log(" "+chalk.bold.cyan("collect")+chalk.gray(" → ")+chalk.bold.cyan("mimic")+chalk.gray(" → ")+chalk.bold.cyan("train")+chalk.gray(" → ")+chalk.bold.cyan("eval")+chalk.gray(" → ")+chalk.bold.cyan("custom")),console.log(chalk.gray(" v1 is a single task (pick-cube) on a MuJoCo arm; the real contest is multi-task on a")),console.log(chalk.gray(" higher-fidelity simulator — the learning core is the same.")),ee();const e=p(),o=null!==e,t=o?e.listeners("line").slice():[];o&&e.removeAllListeners("line");const n=o?e:c({input:process.stdin,output:process.stdout,terminal:!0}),r=o?n.prompt:null;o&&(n.prompt=i.prototype.prompt);const a=()=>{n.setPrompt(chalk.bold.magenta("icoa ioailab> ")),n.prompt()};return new Promise(e=>{const s=i=>{te(i).then(i=>{"exit"===i?((()=>{if(n.removeListener("line",s),o){r&&(n.prompt=r);for(const e of t)n.on("line",e)}else n.close()})(),e()):a()}).catch(e=>{w(`ioailab error: ${e instanceof Error?e.message:String(e)}`),a()})};n.on("line",s),a()})}
@@ -1 +1 @@
1
- import{existsSync as e,mkdirSync as o,readFileSync as n,writeFileSync as l}from"node:fs";import{homedir as t}from"node:os";import{Interface as r,createInterface as a}from"node:readline";import{basename as s,dirname as c,isAbsolute as i,join as g,resolve as y}from"node:path";import chalk from"chalk";import{aienvPaths as d}from"../lib/aienv.js";import{AI_MAX_INPUT_CHARS as u,askIoaiOneShot as h}from"./ai4ioai.js";import{getConfig as p}from"../lib/config.js";import{editTextInEditor as m}from"../lib/editor-spawn.js";import{buildSshArgs as f,leaseConnectProblems as w,parseLeaseResponse as b,queueLine as k}from"../lib/gpu.js";import{shouldExecuteCell as v}from"../lib/ipynb-input.js";import{looksLikeMarkdown as $,renderMarkdown as x}from"../lib/md-term.js";import{NotebookKernel as P,buildRemoteBridgeCommand as S}from"../lib/kernel.js";import{getMainRl as E,setMainRl as G}from"../lib/main-rl.js";import{logCommand as A}from"../lib/logger.js";import{startLogSync as j,stopLogSync as I}from"../lib/log-sync.js";import{cellPreview as N,parseIpynb as U,parsePercentPy as q,parseRunRange as C,serializeIpynb as O,statusChar as L}from"../lib/notebook-doc.js";import{openFile as T}from"../lib/open-file.js";const _=new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`,"g");function R(){return(p().language||"en").toLowerCase().startsWith("zh")}export function registerIpynbCommand(e){e.command("ipynb").argument("[file]","notebook (.ipynb) or script (.py, split on # %%) to open").description("Open an in-terminal Python notebook (AI/ML — needs `aienv setup`)").action(async e=>{await runIpynb(e)})}function M(){console.log(),console.log(chalk.bold.white(" ICOA Notebook ")+chalk.gray("· in-terminal Python · AI/ML notebook arena")),console.log(chalk.gray(" ─────────────────────────────────────────────"))}const K=new Set(["guide","/guide","help","?","quit","exit","back","menu","cells","run all"]);function B(){console.log(chalk.gray(" 🧭 stuck? type ")+chalk.cyan("guide")+chalk.gray(" for the basics · ")+chalk.cyan("help")+chalk.gray(" for every command."))}const D="https://practice.icoa2026.au";function W(){return process.env.ICOA_GPU_SERVER?process.env.ICOA_GPU_SERVER.replace(/\/$/,""):D}function J(){return g(t(),".icoa","gpu-key")}function Y(){if(process.env.ICOA_GPU_KEY)return process.env.ICOA_GPU_KEY.trim();try{return n(J(),"utf-8").trim()||null}catch{return null}}async function z(e,o,n){try{const l=await fetch(`${W()}/api/icoa/gpu/${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n?{key:o,task:n}:{key:o}),signal:AbortSignal.timeout(2e4)});return b(await l.json())}catch{return{state:"error",message:"GPU broker not reachable"}}}function V(e){const o=e?.match(/icoa-kaggle[/\\]([a-z0-9-]+)[/\\]/);return o?o[1]:null}let F=0,H=null;function Q(e,o){for(const n of e.outputs)if("stream"===n.kind){const e=n.text.replace(/\n$/,"");if(0===e.length)continue;console.log("stderr"===n.name?chalk.yellow(e):chalk.white(e))}else if("result"===n.kind)console.log(chalk.cyan(` Out[${e.execCount??"*"}]: `)+chalk.white(n.text));else if("error"===n.kind){console.log(chalk.bold.red(` ${n.ename}: ${n.evalue}`));for(const e of n.traceback)console.log(chalk.gray(` ${e.replace(_,"")}`))}else"display"===n.kind&&X(n.data,o)}function X(e,n){if(e["image/png"])try{o(n,{recursive:!0}),F+=1;const t=g(n,`figure-${F}.png`);return l(t,Buffer.from(e["image/png"],"base64")),H=t,console.log(chalk.magenta(" 🖼 figure saved → ")+chalk.cyan(t)),void(T(t)?console.log(chalk.gray(" opening in your image viewer… (type ")+chalk.cyan("open")+chalk.gray(" to reopen)")):console.log(chalk.gray(" open it with your file browser (inline images need a graphical viewer).")))}catch{}e["text/plain"]&&console.log(chalk.white(` ${e["text/plain"]}`))}export async function runIpynb(p){const b=d(t()),D=g(t(),".icoa","ipynb-out");if(M(),!e(b.python))return console.log(chalk.yellow(" The AI/ML environment is not set up yet.")),console.log(chalk.gray(" Run ")+chalk.bold.cyan("aienv setup")+chalk.gray(" first (one-time, ~300 MB).")),void console.log();const F=p?function(o){const l=i(o)?o:y(process.cwd(),o);if(!e(l))return{doc:{cells:[],path:l,dirty:!1},note:chalk.gray(" new notebook — will be created on ")+chalk.cyan("save")};try{const e=n(l,"utf8"),o=l.endsWith(".ipynb")?U(e):q(e);return{doc:{cells:o,path:l,dirty:!1},note:chalk.gray(" loaded ")+chalk.cyan(l)+chalk.gray(` — ${o.length} cell${1===o.length?"":"s"}. Type `)+chalk.cyan("cells")+chalk.gray(" to list them.")}}catch(e){return{doc:{cells:[],path:null,dirty:!1},note:chalk.yellow(` could not open ${l}: ${e instanceof Error?e.message:String(e)}`)+chalk.gray(" — starting an empty notebook.")}}}(p):null,X=F?F.doc:{cells:[],path:null,dirty:!1};let Z=new P({venvPython:b.python,venvRoot:b.root}),ee=null,oe=V(X.path);process.stdout.write(chalk.gray(" Starting Python kernel…"));try{await Z.start()}catch{return console.log(chalk.red(" failed.")),console.log(chalk.gray(" Check the environment: ")+chalk.cyan("aienv status")),void console.log()}console.log(chalk.green(" ready.")),F&&console.log(F.note),console.log(chalk.bold.white(" ▶ Start here: ")+chalk.bold.cyan("run all")+chalk.gray(" — runs every cell (trains the baseline, writes submission.csv).")),console.log(chalk.gray(" You are already inside the notebook — do ")+chalk.bold.red("not")+chalk.gray(" type ")+chalk.cyan("python file.ipynb")+chalk.gray(". Just ")+chalk.bold.cyan("run all")+chalk.gray(".")),console.log(chalk.gray(" Then: ")+chalk.cyan("cells")+chalk.gray(" list · ")+chalk.cyan("edit N")+chalk.gray(" edit · ")+chalk.cyan("add")+chalk.gray(" new cell · ")+chalk.cyan("save")+chalk.gray(" · ")+chalk.cyan("help")+chalk.gray(" full · ")+chalk.cyan("quit")),console.log(chalk.gray(" Or just type Python and press Enter (blocks continue until a blank line).")),console.log(chalk.bold.yellow(" 🧭 Lost at any point? Type ")+chalk.bold.cyan("guide")+chalk.bold.yellow(" — the full walkthrough + where you are.")),console.log();const ne=E(),le=null!==ne,te=le?ne.listeners("line").slice():[];le&&ne.removeAllListeners("line");const re=le?ne:a({input:process.stdin,output:process.stdout,terminal:!0});le||G(re);const ae=le?re.prompt:null,se=le?re.getPrompt():"";le&&(re.prompt=r.prototype.prompt),le||j();const ce=[];let ie=!1,ge=!1,ye=!1;const de=()=>{const e=ee?"icoa ipynb ⚡gpu":"icoa ipynb";re.setPrompt(chalk.bold.cyan(ce.length?`${e} ...> `:`${e}> `)),re.prompt()};let ue=()=>{};const he=new Promise(e=>{ue=e}),pe=async e=>{const[n,...r]=e.trim().split(/\s+/),a=(n??"").toLowerCase();"on"===a||"connect"===a?await(async()=>{if(ee)return void console.log(chalk.gray(" already on the GPU kernel — ")+chalk.cyan("gpu status")+chalk.gray(" for time left."));const e=Y();if(!e)return console.log(chalk.yellow(" No GPU access key found.")),void console.log(chalk.gray(" Save yours once with ")+chalk.cyan("gpu key <your-key>")+chalk.gray(" (issued by your coach/ICOA)."));oe||(oe=V(X.path)),process.stdout.write(chalk.gray(" Requesting a GPU box…"));let n=await z("lease",e,oe??void 0),r=0;for(;"queued"===n.state&&r<24;)0===r&&console.log(),process.stdout.write(`\r ${chalk.yellow(k(n))} `),await new Promise(e=>setTimeout(e,5e3)),n=await z("status",e),r++;if("queued"===n.state)return console.log(),console.log(chalk.yellow(" Still queued — your place is held server-side.")),void console.log(chalk.gray(" Keep working locally; ")+chalk.cyan("gpu on")+chalk.gray(" again in a few minutes."));if("ready"!==n.state)return void console.log(chalk.red(" failed.")+chalk.gray(` ${n.message??"no lease granted"}`));const a=w(n);if("local"!==n.transport||process.env.ICOA_GPU_SERVER||a.push("local transport from a non-dev broker"),a.length>0)return console.log(chalk.red(" rejected: ")+chalk.gray(a.join(", "))),void await z("release",e);const s=oe?`${n.dataRoot}/${oe}`:n.dataRoot,c=S(n.python,{ICOA_KAGGLE_DATA:s,ICOA_KAGGLE_OUT:n.outDir},n.outDir);let i;if("local"===n.transport)i={command:"/bin/sh",args:["-c",c]};else{const r=g(t(),".icoa","gpu","lease-key");try{o(g(t(),".icoa","gpu"),{recursive:!0}),l(r,n.keyPem,{mode:384})}catch{return console.log(chalk.red(" could not write the ssh key locally.")),void await z("release",e)}i={command:"ssh",args:f(n,r,c)}}const y=new P({spawnSpec:i,readyTimeoutMs:9e4});process.stdout.write(chalk.gray(" connecting…"));try{await y.start()}catch(o){return console.log(chalk.red(" connect failed.")),console.log(chalk.gray(` ${o instanceof Error?o.message:String(o)}`)),void await z("release",e)}await Z.shutdown().catch(()=>{}),Z=y,ee=n;const d=Math.round((n.timeLeftSec??0)/60);console.log(chalk.bold.green(" ⚡ GPU connected.")),console.log(chalk.gray(` Cells now run on the GPU box · ${d}m left on this lease.`)),console.log(chalk.gray(" Variables were reset — ")+chalk.cyan("run all")+chalk.gray(" to retrain there.")),oe&&console.log(chalk.gray(` Dataset is already on the box (${oe}) — same cells, no edits.`)),console.log(chalk.gray(" When done: ")+chalk.cyan("download")+chalk.gray(" your submission, then ")+chalk.cyan("gpu off")+chalk.gray("."))})():"off"===a||"disconnect"===a?await(async()=>{if(!ee)return void console.log(chalk.gray(" not on a GPU kernel."));const e=Y();await Z.shutdown().catch(()=>{}),e&&await z("release",e),ee=null,console.log(chalk.green(" ⚡ GPU lease released")+chalk.gray(" — billing stopped.")),await(async()=>{Z=new P({venvPython:b.python,venvRoot:b.root}),process.stdout.write(chalk.gray(" Starting local kernel…"));try{await Z.start(),console.log(chalk.green(" ready.")+chalk.gray(" (local CPU — variables were reset)"))}catch{console.log(chalk.red(" failed.")+chalk.gray(" Check ")+chalk.cyan("aienv status"))}})()})():"status"===a||""===a?await(async()=>{const e=Y();if(!e)return void console.log(chalk.gray(" no GPU key saved — ")+chalk.cyan("gpu key <your-key>"));const o=await z("status",e);if("ready"===o.state){const e=Math.round((o.timeLeftSec??0)/60);console.log(chalk.green(` ⚡ GPU lease active · ${e}m left`)+chalk.gray(ee?" (this notebook is connected)":" (not connected here — gpu on)"))}else"queued"===o.state?console.log(chalk.yellow(` ${k(o)}`)):"none"===o.state?console.log(chalk.gray(" no GPU lease — ")+chalk.cyan("gpu on")+chalk.gray(" to request one.")):console.log(chalk.yellow(` ${o.message??"GPU broker not reachable"}`))})():"queue"===a||"q"===a?await(async()=>{const e=Y();if(!e)return void console.log(chalk.gray(" no GPU key saved — ")+chalk.cyan("gpu key <your-key>"));let o=null;try{const n=await fetch(`${W()}/api/icoa/gpu/queue`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({key:e}),signal:AbortSignal.timeout(15e3)}),l=await n.json();o=l.data??l}catch{o=null}if(!o||"object"!=typeof o)return void console.log(chalk.yellow(" GPU queue not reachable right now — try again in a moment."));const n="number"==typeof o.boxes?o.boxes:0,l="number"==typeof o.free?o.free:0,t=Array.isArray(o.running)?o.running:[],r=Array.isArray(o.waiting)?o.waiting:[];console.log(),console.log(chalk.bold.cyan(" GPU queue")+chalk.gray(` · ${n} box${1===n?"":"es"} · ${l} free`)),console.log(chalk.bold.white(" ▶ running now:")),0===t.length&&console.log(chalk.gray(" (idle — gpu on takes a box immediately)"));for(const e of t){const o="string"==typeof e.label?e.label:"?",n="number"==typeof e.time_left_sec?e.time_left_sec:0,l=Math.floor(n/60),t=String(n%60).padStart(2,"0"),r=e.is_you?chalk.green(" ← you"):"";console.log(chalk.gray(" ")+chalk.white(o.padEnd(8))+chalk.gray(` ${l}:${t} left`)+r)}console.log(chalk.bold.white(` ⏳ waiting (${r.length}):`)),0===r.length&&console.log(chalk.gray(" (nobody queued)"));for(const e of r){const o="string"==typeof e.label?e.label:"?",n="number"==typeof e.position?e.position:"?",l=e.is_you?chalk.green(" ← you"):"";console.log(chalk.gray(` #${n} `)+chalk.white(o.padEnd(8))+l)}console.log()})():"key"===a?(e=>{const n=e.trim();if(n)try{o(g(t(),".icoa"),{recursive:!0}),l(J(),`${n}\n`,{mode:384}),console.log(chalk.green(" ✓ GPU key saved")+chalk.gray(" — ")+chalk.cyan("gpu on")+chalk.gray(" when you need the box."))}catch{console.log(chalk.red(" could not save the key")+chalk.gray(` (${J()})`))}else console.log(chalk.yellow(" usage: ")+chalk.cyan("gpu key <your-key>"))})(r.join(" ")):console.log(chalk.yellow(" usage: ")+chalk.cyan("gpu on")+chalk.yellow(" · ")+chalk.cyan("gpu off")+chalk.yellow(" · ")+chalk.cyan("gpu status")+chalk.yellow(" · ")+chalk.cyan("gpu queue")+chalk.yellow(" · ")+chalk.cyan("gpu key <k>"))},me=e=>{const o=Number(e.trim());return!Number.isInteger(o)||o<1||o>X.cells.length?(console.log(0===X.cells.length?chalk.yellow(" no cells yet — type Python or use ")+chalk.cyan("add"):chalk.yellow(` no cell ${e.trim()||"?"} — pick 1-${X.cells.length} (see `)+chalk.cyan("cells")+chalk.yellow(")")),null):{cell:X.cells[o-1],idx:o}},fe=(e,o)=>{if(e.length<=o)return e;const n=Math.floor(.6*o);return`${e.slice(0,n)}\n…(${e.length-o} chars elided)…\n${e.slice(-(o-n))}`};let we=!1;const be=(e,o)=>!(!e.result||e.result.ok)&&!(!e.result.outputs.some(e=>"error"===e.kind&&"SyntaxError"===e.ename)||!$(e.source)||(console.log(chalk.gray(" ✎ looks like text/markdown, not Python — ")+chalk.cyan(`md ${o}`)+chalk.gray(" converts this cell to markdown (new note: ")+chalk.cyan("add md")+chalk.gray(").")),0)),ke=async t=>{if(t.trim()&&A(`ipynb: ${t.trim()}`),ge)return void(t.trim()&&console.log(chalk.gray(" (busy — wait for the current cell to finish, then retype)")));let r=t.trim();if(0===ce.length&&/^icoa\s+[a-z/]/i.test(r)&&(r=r.replace(/^icoa\s+/i,"")),0===ce.length){const t=r.split(/\s+/),a=(t[0]??"").toLowerCase(),d=r.slice(t[0]?.length??0).trim();if("quit"!==a&&"exit"!==a&&"q"!==a&&(ye=!1),"quit"===a||"exit"===a||"back"===a||"q"===a||"menu"===a)return X.dirty&&!ye?(ye=!0,console.log(chalk.yellow(" unsaved changes — ")+chalk.cyan("save")+chalk.yellow(" first, or type ")+chalk.cyan("quit")+chalk.yellow(" again to discard.")),void de()):void await(async()=>{if(re.removeAllListeners("line"),le||I(),console.log(),ee){const e=Y();e&&await z("release",e),ee=null,console.log(chalk.green(" ⚡ GPU lease released")+chalk.gray(" — billing stopped."))}if(console.log(chalk.gray(" Notebook closed. Kernel stopped.")),console.log(),await Z.shutdown().catch(()=>{}),le){ae&&(re.prompt=ae),re.setPrompt(se);for(const e of te)re.on("line",e);re.prompt(),ue()}else G(null),ue(),re.close()})();if("clear"===a||"cls"===a)return console.clear(),M(),void de();if("restart"===a)return await(async()=>{process.stdout.write(chalk.gray(" Restarting kernel…")),await Z.shutdown().catch(()=>{});try{await Z.start(),console.log(chalk.green(" fresh kernel ready.")+chalk.gray(" (all variables cleared)"))}catch{console.log(chalk.red(" restart failed.")+chalk.gray(" Leave and re-enter the notebook."))}})(),void de();if("help"===a||"?"===a)return console.log(),console.log(chalk.bold.white(" Notebook commands")),console.log(chalk.cyan(" cells")+chalk.gray(" list cells (○ not run · ✓ ok · ✗ error)")),console.log(chalk.cyan(" show N")+chalk.gray(" print cell N source + outputs")),console.log(chalk.cyan(" edit N")+chalk.gray(" edit cell N in your editor ($EDITOR, default vi)")),console.log(chalk.cyan(" add")+chalk.gray(" write a new Python cell in your editor")),console.log(chalk.cyan(" add md")+chalk.gray(" write a markdown/text cell (notes, headings)")),console.log(chalk.cyan(" md N / py N")+chalk.gray(" convert cell N to markdown / Python")),console.log(chalk.cyan(" del N")+chalk.gray(" delete cell N")),console.log(chalk.cyan(" run N")+chalk.gray(" run cell N — markdown cells render (also: run 2-5, run all)")),console.log(chalk.cyan(" ai [N] [问题]")+chalk.gray(R()?" 问 AI 这一格(默认最近报错的那格;带上代码和 traceback)":" ask the AI about a cell (defaults to the last one that errored)")),console.log(chalk.cyan(" save [file]")+chalk.gray(" save as a real .ipynb")),console.log(chalk.cyan(" restart")+chalk.gray(" restart the kernel (wipes all variables)")),console.log(chalk.cyan(" gpu on/off")+chalk.gray(" run cells on a leased GPU box (dataset pre-mounted)")),console.log(chalk.cyan(" gpu status")+chalk.gray(" your lease/queue · ")+chalk.cyan("gpu queue")+chalk.gray(" who is on the GPU now")),console.log(chalk.cyan(" gpu key <k>")+chalk.gray(" save your access key")),console.log(chalk.cyan(" download")+chalk.gray(" pull submission.csv from the GPU box to this machine")),console.log(chalk.cyan(" check / submit")+chalk.gray(" verify / score your submission.csv — right here, no quit needed")),console.log(chalk.cyan(" board")+chalk.gray(" live standings for this competition")),console.log(chalk.cyan(" open")+chalk.gray(" reopen the last figure in your image viewer")),console.log(chalk.cyan(" clear")+chalk.gray(" clear the screen · ")+chalk.cyan("quit")+chalk.gray(" leave")),console.log(chalk.gray(" Everything else runs as Python in the persistent kernel (and is appended as a cell).")),console.log(),void de();if("guide"===a||"/guide"===a)return(()=>{const o=X.path?c(X.path):process.cwd(),l=V(X.path);let t="",r=!1;try{const e=JSON.parse(n(g(o,"meta.json"),"utf-8"));t=e.judge??"",r=!0===e.needs_gpu}catch{}const a=X.cells.filter(e=>null!==e.result).length,i=e(g(o,"submission.csv"));console.log(),console.log(chalk.bold.white(" ═══ 📖 Notebook guide ═══")),console.log(),console.log(chalk.gray(" Where you are: ")+chalk.white(X.path?s(X.path):"new empty notebook")+chalk.gray(` · ${X.cells.length} cells (${a} run) · submission.csv: `)+(i?chalk.green("written ✓"):chalk.yellow("not yet — run all"))),ee?console.log(chalk.gray(" ⚡ GPU: ")+chalk.green("connected")+chalk.gray(" — ")+chalk.cyan("gpu status")+chalk.gray(" your time left · ")+chalk.cyan("gpu queue")+chalk.gray(" who is on · ")+chalk.cyan("download <file>")+chalk.gray(" pull results")):r&&console.log(chalk.gray(" ⚡ GPU task, not connected — ")+chalk.cyan("gpu key <your-key>")+chalk.gray(" then ")+chalk.cyan("gpu on")+chalk.gray(" · ")+chalk.cyan("gpu queue")+chalk.gray(" shows who is on")),console.log(),console.log(chalk.bold.white(" The loop:")),console.log(chalk.gray(" 1. ")+chalk.bold.cyan("run all")+chalk.gray(" — trains the baseline, writes submission.csv")),l&&"icoa-holdout"===t?(console.log(chalk.gray(" 2. ")+chalk.bold.cyan("submit")+chalk.gray(" — scored + ranked right here, no need to leave (")+chalk.cyan("board")+chalk.gray(" shows standings)")),console.log(chalk.gray(" 3. come back, improve the MODEL cell (")+chalk.cyan("cells")+chalk.gray(" lists them, ")+chalk.cyan("edit N")+chalk.gray(" edits) — beat your score."))):l&&r?(console.log(chalk.gray(" 2. ")+chalk.cyan("download <file>")+chalk.gray(" — pull your results (submission.csv / predictions.zip) off the GPU box")),console.log(chalk.gray(" 3. submit on kaggle.com — the notebook's final cell has the exact steps")),console.log(chalk.gray(" 4. come back, improve the MODEL cell (")+chalk.cyan("cells")+chalk.gray(" lists them, ")+chalk.cyan("edit N")+chalk.gray(" edits) — beat it."))):l?(console.log(chalk.gray(" 2. ")+chalk.bold.cyan("check")+chalk.gray(" — verifies the file format, right here")),console.log(chalk.gray(" 3. upload submission.csv on kaggle.com — read your public score")),console.log(chalk.gray(" 4. come back, improve the MODEL cell (")+chalk.cyan("cells")+chalk.gray(" lists them, ")+chalk.cyan("edit N")+chalk.gray(" edits) — beat it."))):console.log(chalk.gray(" 2. edit cells (")+chalk.cyan("edit N")+chalk.gray("), re-run, ")+chalk.cyan("save")+chalk.gray(" — this is a free notebook.")),console.log(),console.log(chalk.bold.white(" If it looks stuck:")),console.log(chalk.gray(" · prompt shows ")+chalk.cyan("...>")+chalk.gray(" → you are inside a code block. Press Enter on an ")+chalk.bold.white("empty line")+chalk.gray(".")),console.log(chalk.gray(" · never type ")+chalk.cyan("python file.ipynb")+chalk.gray(" here — you are already inside. Just ")+chalk.bold.cyan("run all")+chalk.gray(".")),console.log(),console.log(chalk.bold.white(" 🤝 AI help: ")+chalk.gray("split the screen ")+chalk.bold.cyan("Ctrl-b %")+chalk.gray(" · right pane: ")+chalk.bold.cyan("icoa")+chalk.gray(" → ↑↓ pick 🧠 Learn → at ")+chalk.cyan("icoa learn>")+chalk.gray(" type ")+chalk.bold.cyan("ai4ioai")+chalk.gray(" — live Gemma 3")),console.log(chalk.gray(" All commands: ")+chalk.cyan("help")+chalk.gray(" · leave: ")+chalk.cyan("quit")),console.log()})(),void de();if("python"===a||"python3"===a)return console.log(chalk.gray(" You're already inside the notebook kernel — no need to run python.")),console.log(chalk.gray(" Type ")+chalk.bold.cyan("run all")+chalk.gray(" to run every cell, or ")+chalk.cyan("guide")+chalk.gray(" for the basics.")),void de();if("open"===a)return H&&T(H)?console.log(chalk.gray(" reopening ")+chalk.cyan(H)):console.log(chalk.gray(" no figure yet — plot something (e.g. matplotlib) first.")),void de();if("cells"===a||"ls"===a)return(()=>{if(0===X.cells.length)return void console.log(chalk.gray(" no cells yet — type Python, or ")+chalk.cyan("add")+chalk.gray(" to write one in your editor."));console.log();const e=Math.max(20,(process.stdout.columns??80)-12);X.cells.forEach((o,n)=>{const l=L(o),t="✓"===l?chalk.green(l.padEnd(2)):"✗"===l?chalk.red(l.padEnd(2)):"md"===l?chalk.magenta(l.padEnd(2)):chalk.gray(l.padEnd(2));console.log(`${chalk.cyan(String(n+1).padStart(4))} ${t} ${chalk.white(N(o,e))}`)}),console.log(),console.log(chalk.gray(" ")+chalk.cyan("show N")+chalk.gray(" · ")+chalk.cyan("edit N")+chalk.gray(" · ")+chalk.cyan("run N")+chalk.gray(" · ")+chalk.cyan("run all"))})(),void de();if("show"===a)return(e=>{const o=me(e);if(o){if(console.log(),console.log(chalk.gray(` ── cell ${o.idx} ${"markdown"===o.cell.kind?"(markdown) ":""}──`)),"markdown"===o.cell.kind){for(const e of x(o.cell.source))console.log(e);console.log(chalk.gray(" (rendered — ")+chalk.cyan(`edit ${o.idx}`)+chalk.gray(" for the raw source)"))}else for(const e of o.cell.source.split("\n"))console.log(chalk.white(` ${e}`));o.cell.result&&(console.log(chalk.gray(" ── output ──")),Q(o.cell.result,D)),console.log()}})(d),void de();if("edit"===a)return(e=>{const o=me(e);if(!o)return;re.pause();const n=m(o.cell.source,"markdown"===o.cell.kind?".md":".py");if(re.resume(),null===n)return void console.log(chalk.yellow(" editor cancelled or failed — cell unchanged.")+chalk.gray(" (set $EDITOR to choose your editor)"));const l=n.replace(/\s+$/,"");l!==o.cell.source?(o.cell.source=l,o.cell.result=null,o.cell.rawOutputs=void 0,o.cell.rawExecCount=void 0,X.dirty=!0,console.log(chalk.green(` ✓ cell ${o.idx} updated`)+chalk.gray(" — ")+chalk.cyan(`run ${o.idx}`)+chalk.gray(" to execute."))):console.log(chalk.gray(" no changes."))})(d),void de();if("add"===a)return(e=>{const o=e.trim().toLowerCase(),n="md"===o||"markdown"===o||"text"===o||"txt"===o;if(o&&!n&&"py"!==o&&"code"!==o&&"python"!==o)return void console.log(chalk.yellow(" usage: ")+chalk.cyan("add")+chalk.yellow(" (Python cell) · ")+chalk.cyan("add md")+chalk.yellow(" (markdown/text cell)"));re.pause();const l=m(n?"\x3c!-- markdown cell — write notes/headings, save & exit --\x3e\n":"",n?".md":".py");re.resume();const t=null===l?"":l.replace(/^<!-- markdown cell[^\n]*-->\n?/,"").replace(/\s+$/,"");t?(X.cells.push({kind:n?"markdown":"code",source:t,result:null}),X.dirty=!0,console.log(chalk.green(` ✓ added ${n?"markdown ":""}cell ${X.cells.length}`)+chalk.gray(" — ")+chalk.cyan(`run ${X.cells.length}`)+chalk.gray(n?" to render it.":" to execute."))):console.log(chalk.gray(" nothing added."))})(d),void de();if("del"===a||"delete"===a)return(e=>{const o=me(e);o&&(X.cells.splice(o.idx-1,1),X.dirty=!0,console.log(chalk.gray(` deleted cell ${o.idx} — ${X.cells.length} left.`)+(o.idx<=X.cells.length?chalk.yellow(" ⚠ 后面的 cell 号已顺移")+chalk.gray(" — ")+chalk.cyan("cells")+chalk.gray(" 重看一眼"):"")))})(d),void de();if(("md"===a||"py"===a)&&/^\d+$/.test(d))return((e,o)=>{const n=me(e);if(!n)return;const l="markdown"===o?"markdown":"Python";n.cell.kind!==o?(n.cell.kind=o,n.cell.result=null,n.cell.rawOutputs=void 0,n.cell.rawExecCount=void 0,X.dirty=!0,console.log(chalk.green(` ✓ cell ${n.idx} → ${l}`)+chalk.gray(" — ")+chalk.cyan(`run ${n.idx}`)+chalk.gray("markdown"===o?" to render it.":" to execute."))):console.log(chalk.gray(` cell ${n.idx} is already ${l}.`))})(d,"md"===a?"markdown":"code"),void de();if("gpu"===a)return ge=!0,await pe(d),ge=!1,void de();if("download"===a||"dl"===a)return ge=!0,await(async e=>{if(!ee)return void console.log(chalk.gray(" local kernel — your files are already on this machine (no download needed)."));const n=e.trim()||"submission.csv",t=n.startsWith("/")?n:`${ee.outDir}/${n}`;let r;try{r=await Z.fetchFile(t)}catch(e){return console.log(chalk.yellow(` could not fetch ${t}: `)+chalk.gray(e instanceof Error?e.message:String(e))),void console.log(chalk.gray(" (run the submission cell first — it writes into the GPU workspace)"))}const a=oe?g(process.cwd(),"icoa-kaggle",oe):process.cwd(),c=g(a,s(n));try{o(a,{recursive:!0}),l(c,r)}catch{return void console.log(chalk.yellow(" could not write the file here — check directory permissions."))}console.log(chalk.green(" ✓ downloaded ")+chalk.white(c)+chalk.gray(` (${r.length} bytes)`)),oe&&"submission.csv"===s(n)&&console.log(chalk.gray(" verify it: ")+chalk.cyan(`kaggle check ${oe}`)+chalk.gray(" — then upload on Kaggle yourself."))})(d),ge=!1,void de();if("check"===a||"submit"===a||"kaggle"===a){const e=await import("./kaggle.js"),o=d.split(/\s+/).filter(Boolean),n="kaggle"===a?(o[0]||"").toLowerCase():a,l=("kaggle"===a?o[1]:o[0])||V(X.path)||void 0;ge=!0;try{"submit"===n?await e.runKaggleSubmit(l):"check"===n?await e.runKaggleCheck(l):"guide"===n?await e.runKaggleGuide(l):await e.runKaggle("kaggle"===a?o[0]:l)}catch(e){console.log(chalk.yellow(" That command failed: ")+chalk.gray(String(e)))}return ge=!1,void de()}if("board"===a){const{runBoard:e}=await import("./board.js"),o=d.split(/\s+/).filter(Boolean);ge=!0;try{await e(o[0],o[1])}catch(e){console.log(chalk.yellow(" That command failed: ")+chalk.gray(String(e)))}return ge=!1,void de()}if("ai"===a||"/ai"===a)return ge=!0,await(async e=>{const o=e.trim().split(/\s+/).filter(Boolean),l=o.length>0&&/^\d+$/.test(o[0]),t=(l?o.slice(1):o).join(" ").trim(),r=l?Number(o[0]):(()=>{for(let e=X.cells.length-1;e>=0;e--)if((X.cells[e].result?.outputs??[]).some(e=>"error"===e.kind))return e+1;return null})()??X.cells.length;if(0===X.cells.length)return void console.log(chalk.yellow(" 还没有 cell —— 先写点代码,或用 ")+chalk.cyan("add"));const a=me(String(r));if(!a)return;if("markdown"===a.cell.kind)return void console.log(chalk.yellow(` cell ${a.idx} 是 markdown —— 挑一个代码格,比如 `)+chalk.cyan("ai 2"));const s=(e=>{const o=[];for(const n of e.result?.outputs??[])"stream"===n.kind||"result"===n.kind?o.push(n.text):"error"===n.kind&&(o.push(`${n.ename}: ${n.evalue}`),o.push(n.traceback.map(e=>e.replace(_,"")).join("\n")));return o.join("\n").replace(_,"").replace(/\n{3,}/g,"\n\n").trim()})(a.cell),i=(()=>{if(!X.path||!V(X.path))return"";try{const e=JSON.parse(n(g(c(X.path),"meta.json"),"utf-8")),o=[e.title,e.metric?`metric: ${e.metric}`:"",e.description].filter(Boolean).join("\n");return fe(o,350)}catch{return""}})(),y=R(),d=y?"你在帮一个 IOAI 选手调试 notebook。先说清楚这一格为什么会这样,再给出最小改动。用中文回答,简洁,给可直接粘回去的代码。":"You are helping an IOAI contestant debug a notebook cell. Say why it behaves this way, then give the smallest fix. Be concise and give code that can be pasted straight back.",p=d.length+t.length+120,m=Math.max(400,u-p-i.length),f=fe(a.cell.source,Math.floor(.6*m)),w=s?(b=s,k=m-f.length,b.length<=k?b:`…(${b.length-k} chars elided)…\n${b.slice(-k)}`):"";var b,k;const v=[d,i?`\n[task]\n${i}`:"",`\n[cell ${a.idx} code]\n${f}`,w?`\n[cell ${a.idx} output]\n${w}`:"\n(this cell has not been run yet)",t?`\n[question]\n${t}`:""].filter(Boolean).join("\n"),$=v.includes("chars elided"),P=y;console.log(),we||(we=!0,console.log(chalk.gray(" ")+chalk.bold.white("IOAI 2026 ")+chalk.white("Gemma 3n E4B")+chalk.gray(" (32k) · ")+chalk.white(R()?"单轮无记忆":"single-turn, no memory")+chalk.gray(" · ")+chalk.white(`${u} ${R()?"字符/条":"chars/message"}`)+chalk.gray(" · ")+chalk.white(R()?"1000 token/回复":"1000 tokens/reply")),console.log(chalk.gray(R()?" —— 现场就是这个模型和这几条上限,在这里练的手感能原样带过去。":" — the onsite model and the onsite limits, so the habit transfers as-is."))),console.log(chalk.gray(` → cell ${a.idx} · ${v.length}/${u} chars`)+chalk.gray(P?" · 单轮(竞赛档模型)":" · single-turn (contest model)")+(s?chalk.gray(P?" · 含输出":" · output included"):"")+($?chalk.yellow(P?" ⚠ 上下文被裁过,加一句具体问题会更准":" ⚠ context trimmed — a specific question helps"):"")),A(`ipynb: ai ${a.idx}${t?` — ${t}`:""}`);const S=await h(v);if(console.log(),!S.ok)return console.log(chalk.yellow(" AI 现在连不上 —— ")+chalk.gray(S.text)),console.log(chalk.gray(" 过一会儿再试,或用 ")+chalk.cyan("ai4ioai")+chalk.gray(" 单开一个会话。")),void console.log();for(const e of x(S.text))console.log(e);console.log(),console.log(chalk.gray(" ")+chalk.cyan(`ai ${a.idx} ${P?"<你的问题>":"<your question>"}`)+chalk.gray(P?" 追问 · ":" follow up · ")+chalk.cyan(`edit ${a.idx}`)+chalk.gray(P?" 改这一格 · ":" edit it · ")+chalk.cyan(`run ${a.idx}`)+chalk.gray(P?" 再跑一次":" run it again")),console.log(chalk.gray(P?" 这是调试助手(超限自动裁剪);想练现场那堵墙用 ":" This is the debugging aid (it trims to fit). To train against the real wall: ")+chalk.cyan("ai4ioai")),console.log()})(d),ge=!1,void de();if("run"===a)return ge=!0,await(async e=>{const o=C(e,X.cells.length);if(o)for(const e of o){const n=X.cells[e-1];if("markdown"!==n.kind){console.log(chalk.gray(` ── run cell ${e}: `)+chalk.white(N(n,50))+chalk.gray(" ──"));try{n.result=await Z.execute(n.source)}catch(e){return console.log(chalk.red(" Kernel error: ")+chalk.gray(e instanceof Error?e.message:String(e))),void B()}if(X.dirty=!0,Q(n.result,D),be(n,e),!n.result.ok){const n=e!==o[o.length-1];return console.log(chalk.yellow(` ✗ cell ${e} raised${n?" — stopping here.":"."}`)+chalk.gray(" Fix it (")+chalk.cyan(`edit ${e}`)+chalk.gray(") and re-run.")),console.log(chalk.gray(R()?" 卡住了? ":" Stuck? ")+chalk.bold.cyan(`ai ${e}`)+chalk.gray(R()?" 把这一格的代码和报错一起问 AI(不用离开 notebook)":" sends this cell and its traceback to the AI — without leaving the notebook")),void B()}}else{console.log(chalk.gray(` ── cell ${e} (markdown) ──`));for(const e of x(n.source))console.log(e);console.log()}}else console.log(0===X.cells.length?chalk.yellow(" no cells yet — type Python or use ")+chalk.cyan("add"):chalk.yellow(" usage: ")+chalk.cyan("run 3")+chalk.yellow(" · ")+chalk.cyan("run 1-5")+chalk.yellow(" · ")+chalk.cyan("run all"))})(d),ge=!1,void de();if("save"===a)return(e=>{if(0===X.cells.length)return void console.log(chalk.yellow(" nothing to save — the notebook is empty."));let o;o=e.trim()?i(e.trim())?e.trim():y(process.cwd(),e.trim()):X.path?X.path.endsWith(".ipynb")?X.path:`${X.path.replace(/\.[^./]+$/,"")}.ipynb`:y(process.cwd(),"notebook.ipynb"),o.endsWith(".ipynb")||(o+=".ipynb");try{l(o,O(X.cells)),X.path=o,X.dirty=!1,console.log(chalk.green(" ✓ saved ")+chalk.cyan(o)+chalk.gray(` (${X.cells.length} cells)`))}catch(e){console.log(chalk.red(" save failed: ")+chalk.gray(e instanceof Error?e.message:String(e)))}})(d),void de();if(""===r)return void de()}if(ce.length>0&&K.has(r.toLowerCase()))return console.log(chalk.gray(" (⏸ you are inside a multi-line block — press Enter on an ")+chalk.bold.white("empty line")+chalk.gray(" to run it; then ")+chalk.cyan(r.toLowerCase().replace(/^\//,""))+chalk.gray(" will work. This line was not added to the block.)")),void de();if(ce.push(t),!v(ce))return 1!==ce.length||ie||(ie=!0,console.log(chalk.gray(" (multi-line block started — keep typing; an empty line runs it)"))),void de();const a=ce.join("\n").replace(/\s+$/,"");if(ce.length=0,""!==a.trim()){ge=!0;try{const e=await Z.execute(a);X.cells.push({kind:"code",source:a,result:e}),X.dirty=!0,Q(e,D);const o=be(X.cells[X.cells.length-1],X.cells.length);e.ok||o||B()}catch(e){console.log(chalk.red(" Kernel error: ")+chalk.gray(e instanceof Error?e.message:String(e))),B()}ge=!1,de()}else de()};re.on("line",e=>{ke(e)}),le||re.on("close",async()=>{await Z.shutdown().catch(()=>{}),process.exit(0)}),de(),await he}
1
+ import{existsSync as e,mkdirSync as o,readFileSync as n,writeFileSync as l}from"node:fs";import{homedir as t}from"node:os";import{Interface as r,createInterface as a}from"node:readline";import{basename as s,dirname as c,isAbsolute as i,join as g,resolve as y}from"node:path";import chalk from"chalk";import{aienvPaths as d}from"../lib/aienv.js";import{AI_MAX_INPUT_CHARS as u,askIoaiOneShot as h}from"./ai4ioai.js";import{getConfig as p}from"../lib/config.js";import{editTextInEditor as m}from"../lib/editor-spawn.js";import{buildSshArgs as w,leaseConnectProblems as f,parseLeaseResponse as b,queueLine as k,windowLine as v}from"../lib/gpu.js";import{shouldExecuteCell as $}from"../lib/ipynb-input.js";import{looksLikeMarkdown as x,renderMarkdown as P}from"../lib/md-term.js";import{NotebookKernel as E,buildRemoteBridgeCommand as G}from"../lib/kernel.js";import{getMainRl as S,setMainRl as A}from"../lib/main-rl.js";import{logCommand as U}from"../lib/logger.js";import{startLogSync as j,stopLogSync as C}from"../lib/log-sync.js";import{cellPreview as I,parseIpynb as N,parsePercentPy as q,parseRunRange as O,serializeIpynb as _,statusChar as T}from"../lib/notebook-doc.js";import{openFile as L}from"../lib/open-file.js";const R=new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`,"g");function M(){return(p().language||"en").toLowerCase().startsWith("zh")}export function registerIpynbCommand(e){e.command("ipynb").argument("[file]","notebook (.ipynb) or script (.py, split on # %%) to open").description("Open an in-terminal Python notebook (AI/ML — needs `aienv setup`)").action(async e=>{await runIpynb(e)})}function K(){console.log(),console.log(chalk.bold.white(" ICOA Notebook ")+chalk.gray("· in-terminal Python · AI/ML notebook arena")),console.log(chalk.gray(" ─────────────────────────────────────────────"))}const B=new Set(["guide","/guide","help","?","quit","exit","back","menu","cells","run all"]);function D(){console.log(chalk.gray(" 🧭 stuck? type ")+chalk.cyan("guide")+chalk.gray(" for the basics · ")+chalk.cyan("help")+chalk.gray(" for every command."))}const W="https://practice.icoa2026.au";function J(){return process.env.ICOA_GPU_SERVER?process.env.ICOA_GPU_SERVER.replace(/\/$/,""):W}function Y(){return g(t(),".icoa","gpu-key")}function z(){if(process.env.ICOA_GPU_KEY)return process.env.ICOA_GPU_KEY.trim();try{return n(Y(),"utf-8").trim()||null}catch{return null}}async function V(e,o,n){try{const l=await fetch(`${J()}/api/icoa/gpu/${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n?{key:o,task:n}:{key:o}),signal:AbortSignal.timeout(2e4)});return b(await l.json())}catch{return{state:"error",message:"GPU broker not reachable"}}}function F(e){const o=e?.match(/icoa-kaggle[/\\]([a-z0-9-]+)[/\\]/);return o?o[1]:null}let H=0,Q=null;function X(e,o){for(const n of e.outputs)if("stream"===n.kind){const e=n.text.replace(/\n$/,"");if(0===e.length)continue;console.log("stderr"===n.name?chalk.yellow(e):chalk.white(e))}else if("result"===n.kind)console.log(chalk.cyan(` Out[${e.execCount??"*"}]: `)+chalk.white(n.text));else if("error"===n.kind){console.log(chalk.bold.red(` ${n.ename}: ${n.evalue}`));for(const e of n.traceback)console.log(chalk.gray(` ${e.replace(R,"")}`))}else"display"===n.kind&&Z(n.data,o)}function Z(e,n){if(e["image/png"])try{o(n,{recursive:!0}),H+=1;const t=g(n,`figure-${H}.png`);return l(t,Buffer.from(e["image/png"],"base64")),Q=t,console.log(chalk.magenta(" 🖼 figure saved → ")+chalk.cyan(t)),void(L(t)?console.log(chalk.gray(" opening in your image viewer… (type ")+chalk.cyan("open")+chalk.gray(" to reopen)")):console.log(chalk.gray(" open it with your file browser (inline images need a graphical viewer).")))}catch{}e["text/plain"]&&console.log(chalk.white(` ${e["text/plain"]}`))}export async function runIpynb(p){const b=d(t()),W=g(t(),".icoa","ipynb-out");if(K(),!e(b.python))return console.log(chalk.yellow(" The AI/ML environment is not set up yet.")),console.log(chalk.gray(" Run ")+chalk.bold.cyan("aienv setup")+chalk.gray(" first (one-time, ~300 MB).")),void console.log();const H=p?function(o){const l=i(o)?o:y(process.cwd(),o);if(!e(l))return{doc:{cells:[],path:l,dirty:!1},note:chalk.gray(" new notebook — will be created on ")+chalk.cyan("save")};try{const e=n(l,"utf8"),o=l.endsWith(".ipynb")?N(e):q(e);return{doc:{cells:o,path:l,dirty:!1},note:chalk.gray(" loaded ")+chalk.cyan(l)+chalk.gray(` — ${o.length} cell${1===o.length?"":"s"}. Type `)+chalk.cyan("cells")+chalk.gray(" to list them.")}}catch(e){return{doc:{cells:[],path:null,dirty:!1},note:chalk.yellow(` could not open ${l}: ${e instanceof Error?e.message:String(e)}`)+chalk.gray(" — starting an empty notebook.")}}}(p):null,Z=H?H.doc:{cells:[],path:null,dirty:!1};let ee=new E({venvPython:b.python,venvRoot:b.root}),oe=null,ne=F(Z.path);process.stdout.write(chalk.gray(" Starting Python kernel…"));try{await ee.start()}catch{return console.log(chalk.red(" failed.")),console.log(chalk.gray(" Check the environment: ")+chalk.cyan("aienv status")),void console.log()}console.log(chalk.green(" ready.")),H&&console.log(H.note),console.log(chalk.bold.white(" ▶ Start here: ")+chalk.bold.cyan("run all")+chalk.gray(" — runs every cell (trains the baseline, writes submission.csv).")),console.log(chalk.gray(" You are already inside the notebook — do ")+chalk.bold.red("not")+chalk.gray(" type ")+chalk.cyan("python file.ipynb")+chalk.gray(". Just ")+chalk.bold.cyan("run all")+chalk.gray(".")),console.log(chalk.gray(" Then: ")+chalk.cyan("cells")+chalk.gray(" list · ")+chalk.cyan("edit N")+chalk.gray(" edit · ")+chalk.cyan("add")+chalk.gray(" new cell · ")+chalk.cyan("save")+chalk.gray(" · ")+chalk.cyan("help")+chalk.gray(" full · ")+chalk.cyan("quit")),console.log(chalk.gray(" Or just type Python and press Enter (blocks continue until a blank line).")),console.log(chalk.bold.yellow(" 🧭 Lost at any point? Type ")+chalk.bold.cyan("guide")+chalk.bold.yellow(" — the full walkthrough + where you are.")),console.log();const le=S(),te=null!==le,re=te?le.listeners("line").slice():[];te&&le.removeAllListeners("line");const ae=te?le:a({input:process.stdin,output:process.stdout,terminal:!0});te||A(ae);const se=te?ae.prompt:null,ce=te?ae.getPrompt():"";te&&(ae.prompt=r.prototype.prompt),te||j();const ie=[];let ge=!1,ye=!1,de=!1;const ue=()=>{const e=oe?"icoa ipynb ⚡gpu":"icoa ipynb";ae.setPrompt(chalk.bold.cyan(ie.length?`${e} ...> `:`${e}> `)),ae.prompt()};let he=()=>{};const pe=new Promise(e=>{he=e}),me=async e=>{const[n,...r]=e.trim().split(/\s+/),a=(n??"").toLowerCase();"on"===a||"connect"===a?await(async()=>{if(oe)return void console.log(chalk.gray(" already on the GPU kernel — ")+chalk.cyan("gpu status")+chalk.gray(" for time left."));const e=z();if(!e)return console.log(chalk.yellow(" No GPU access key found.")),void console.log(chalk.gray(" Save yours once with ")+chalk.cyan("gpu key <your-key>")+chalk.gray(" (issued by your coach/ICOA)."));ne||(ne=F(Z.path)),process.stdout.write(chalk.gray(" Requesting a GPU box…"));let n=await V("lease",e,ne??void 0),r=0;for(;"queued"===n.state&&r<24;)0===r&&console.log(),process.stdout.write(`\r ${chalk.yellow(k(n))} `),await new Promise(e=>setTimeout(e,5e3)),n=await V("status",e),r++;if("queued"===n.state)return console.log(),console.log(chalk.yellow(" Still queued — your place is held server-side.")),void console.log(chalk.gray(" Keep working locally; ")+chalk.cyan("gpu on")+chalk.gray(" again in a few minutes."));if("ready"!==n.state)return void console.log(chalk.red(" failed.")+chalk.gray(` ${n.message??"no lease granted"}`));const a=f(n);if("local"!==n.transport||process.env.ICOA_GPU_SERVER||a.push("local transport from a non-dev broker"),a.length>0)return console.log(chalk.red(" rejected: ")+chalk.gray(a.join(", "))),void await V("release",e);const s=ne?`${n.dataRoot}/${ne}`:n.dataRoot,c=G(n.python,{ICOA_KAGGLE_DATA:s,ICOA_KAGGLE_OUT:n.outDir},n.outDir);let i;if("local"===n.transport)i={command:"/bin/sh",args:["-c",c]};else{const r=g(t(),".icoa","gpu","lease-key");try{o(g(t(),".icoa","gpu"),{recursive:!0}),l(r,n.keyPem,{mode:384})}catch{return console.log(chalk.red(" could not write the ssh key locally.")),void await V("release",e)}i={command:"ssh",args:w(n,r,c)}}const y=new E({spawnSpec:i,readyTimeoutMs:9e4});process.stdout.write(chalk.gray(" connecting…"));try{await y.start()}catch(o){return console.log(chalk.red(" connect failed.")),console.log(chalk.gray(` ${o instanceof Error?o.message:String(o)}`)),void await V("release",e)}await ee.shutdown().catch(()=>{}),ee=y,oe=n;const d=Math.round((n.timeLeftSec??0)/60);console.log(chalk.bold.green(" ⚡ GPU connected.")),console.log(chalk.gray(` Cells now run on the GPU box · ${d}m left on this lease.`));const u=v(n.windowCloseEpoch);u&&console.log(chalk.gray(` ⏱ Tonight's GPU window: ${u}.`)),console.log(chalk.gray(" Variables were reset — ")+chalk.cyan("run all")+chalk.gray(" to retrain there.")),ne&&console.log(chalk.gray(` Dataset is already on the box (${ne}) — same cells, no edits.`)),console.log(chalk.gray(" When done: ")+chalk.cyan("download")+chalk.gray(" your submission, then ")+chalk.cyan("gpu off")+chalk.gray("."))})():"off"===a||"disconnect"===a?await(async()=>{if(!oe)return void console.log(chalk.gray(" not on a GPU kernel."));const e=z();await ee.shutdown().catch(()=>{}),e&&await V("release",e),oe=null,console.log(chalk.green(" ⚡ GPU lease released")+chalk.gray(" — billing stopped.")),await(async()=>{ee=new E({venvPython:b.python,venvRoot:b.root}),process.stdout.write(chalk.gray(" Starting local kernel…"));try{await ee.start(),console.log(chalk.green(" ready.")+chalk.gray(" (local CPU — variables were reset)"))}catch{console.log(chalk.red(" failed.")+chalk.gray(" Check ")+chalk.cyan("aienv status"))}})()})():"status"===a||""===a?await(async()=>{const e=z();if(!e)return void console.log(chalk.gray(" no GPU key saved — ")+chalk.cyan("gpu key <your-key>"));const o=await V("status",e);if("ready"===o.state){const e=Math.round((o.timeLeftSec??0)/60);console.log(chalk.green(` ⚡ GPU lease active · ${e}m left`)+chalk.gray(oe?" (this notebook is connected)":" (not connected here — gpu on)"));const n=v(o.windowCloseEpoch);n&&console.log(chalk.gray(` ⏱ Tonight's GPU window: ${n}.`))}else"queued"===o.state?console.log(chalk.yellow(` ${k(o)}`)):"none"===o.state?console.log(chalk.gray(" no GPU lease — ")+chalk.cyan("gpu on")+chalk.gray(" to request one.")):console.log(chalk.yellow(` ${o.message??"GPU broker not reachable"}`))})():"queue"===a||"q"===a?await(async()=>{const e=z();if(!e)return void console.log(chalk.gray(" no GPU key saved — ")+chalk.cyan("gpu key <your-key>"));let o=null;try{const n=await fetch(`${J()}/api/icoa/gpu/queue`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({key:e}),signal:AbortSignal.timeout(15e3)}),l=await n.json();o=l.data??l}catch{o=null}if(!o||"object"!=typeof o)return void console.log(chalk.yellow(" GPU queue not reachable right now — try again in a moment."));const n="number"==typeof o.boxes?o.boxes:0,l="number"==typeof o.free?o.free:0,t=Array.isArray(o.running)?o.running:[],r=Array.isArray(o.waiting)?o.waiting:[];console.log(),console.log(chalk.bold.cyan(" GPU queue")+chalk.gray(` · ${n} box${1===n?"":"es"} · ${l} free`));const a=v("number"==typeof o.window_close_epoch?o.window_close_epoch:void 0);a&&console.log(chalk.gray(` ⏱ Tonight's GPU window: ${a}.`)),console.log(chalk.bold.white(" ▶ running now:")),0===t.length&&console.log(chalk.gray(" (idle — gpu on takes a box immediately)"));for(const e of t){const o="string"==typeof e.label?e.label:"?",n="number"==typeof e.time_left_sec?e.time_left_sec:0,l=Math.floor(n/60),t=String(n%60).padStart(2,"0"),r=e.is_you?chalk.green(" ← you"):"";console.log(chalk.gray(" ")+chalk.white(o.padEnd(8))+chalk.gray(` ${l}:${t} left`)+r)}console.log(chalk.bold.white(` ⏳ waiting (${r.length}):`)),0===r.length&&console.log(chalk.gray(" (nobody queued)"));for(const e of r){const o="string"==typeof e.label?e.label:"?",n="number"==typeof e.position?e.position:"?",l=e.is_you?chalk.green(" ← you"):"";console.log(chalk.gray(` #${n} `)+chalk.white(o.padEnd(8))+l)}console.log()})():"key"===a?(e=>{const n=e.trim();if(n)try{o(g(t(),".icoa"),{recursive:!0}),l(Y(),`${n}\n`,{mode:384}),console.log(chalk.green(" ✓ GPU key saved")+chalk.gray(" — ")+chalk.cyan("gpu on")+chalk.gray(" when you need the box."))}catch{console.log(chalk.red(" could not save the key")+chalk.gray(` (${Y()})`))}else console.log(chalk.yellow(" usage: ")+chalk.cyan("gpu key <your-key>"))})(r.join(" ")):console.log(chalk.yellow(" usage: ")+chalk.cyan("gpu on")+chalk.yellow(" · ")+chalk.cyan("gpu off")+chalk.yellow(" · ")+chalk.cyan("gpu status")+chalk.yellow(" · ")+chalk.cyan("gpu queue")+chalk.yellow(" · ")+chalk.cyan("gpu key <k>"))},we=e=>{const o=Number(e.trim());return!Number.isInteger(o)||o<1||o>Z.cells.length?(console.log(0===Z.cells.length?chalk.yellow(" no cells yet — type Python or use ")+chalk.cyan("add"):chalk.yellow(` no cell ${e.trim()||"?"} — pick 1-${Z.cells.length} (see `)+chalk.cyan("cells")+chalk.yellow(")")),null):{cell:Z.cells[o-1],idx:o}},fe=(e,o)=>{if(e.length<=o)return e;const n=Math.floor(.6*o);return`${e.slice(0,n)}\n…(${e.length-o} chars elided)…\n${e.slice(-(o-n))}`};let be=!1;const ke=(e,o)=>!(!e.result||e.result.ok)&&!(!e.result.outputs.some(e=>"error"===e.kind&&"SyntaxError"===e.ename)||!x(e.source)||(console.log(chalk.gray(" ✎ looks like text/markdown, not Python — ")+chalk.cyan(`md ${o}`)+chalk.gray(" converts this cell to markdown (new note: ")+chalk.cyan("add md")+chalk.gray(").")),0)),ve=async t=>{if(t.trim()&&U(`ipynb: ${t.trim()}`),ye)return void(t.trim()&&console.log(chalk.gray(" (busy — wait for the current cell to finish, then retype)")));let r=t.trim();if(0===ie.length&&/^icoa\s+[a-z/]/i.test(r)&&(r=r.replace(/^icoa\s+/i,"")),0===ie.length){const t=r.split(/\s+/),a=(t[0]??"").toLowerCase(),d=r.slice(t[0]?.length??0).trim();if("quit"!==a&&"exit"!==a&&"q"!==a&&(de=!1),"quit"===a||"exit"===a||"back"===a||"q"===a||"menu"===a)return Z.dirty&&!de?(de=!0,console.log(chalk.yellow(" unsaved changes — ")+chalk.cyan("save")+chalk.yellow(" first, or type ")+chalk.cyan("quit")+chalk.yellow(" again to discard.")),void ue()):void await(async()=>{if(ae.removeAllListeners("line"),te||C(),console.log(),oe){const e=z();e&&await V("release",e),oe=null,console.log(chalk.green(" ⚡ GPU lease released")+chalk.gray(" — billing stopped."))}if(console.log(chalk.gray(" Notebook closed. Kernel stopped.")),console.log(),await ee.shutdown().catch(()=>{}),te){se&&(ae.prompt=se),ae.setPrompt(ce);for(const e of re)ae.on("line",e);ae.prompt(),he()}else A(null),he(),ae.close()})();if("clear"===a||"cls"===a)return console.clear(),K(),void ue();if("restart"===a)return await(async()=>{process.stdout.write(chalk.gray(" Restarting kernel…")),await ee.shutdown().catch(()=>{});try{await ee.start(),console.log(chalk.green(" fresh kernel ready.")+chalk.gray(" (all variables cleared)"))}catch{console.log(chalk.red(" restart failed.")+chalk.gray(" Leave and re-enter the notebook."))}})(),void ue();if("help"===a||"?"===a)return console.log(),console.log(chalk.bold.white(" Notebook commands")),console.log(chalk.cyan(" cells")+chalk.gray(" list cells (○ not run · ✓ ok · ✗ error)")),console.log(chalk.cyan(" show N")+chalk.gray(" print cell N source + outputs")),console.log(chalk.cyan(" edit N")+chalk.gray(" edit cell N in your editor ($EDITOR, default vi)")),console.log(chalk.cyan(" add")+chalk.gray(" write a new Python cell in your editor")),console.log(chalk.cyan(" add md")+chalk.gray(" write a markdown/text cell (notes, headings)")),console.log(chalk.cyan(" md N / py N")+chalk.gray(" convert cell N to markdown / Python")),console.log(chalk.cyan(" del N")+chalk.gray(" delete cell N")),console.log(chalk.cyan(" run N")+chalk.gray(" run cell N — markdown cells render (also: run 2-5, run all)")),console.log(chalk.cyan(" ai [N] [问题]")+chalk.gray(M()?" 问 AI 这一格(默认最近报错的那格;带上代码和 traceback)":" ask the AI about a cell (defaults to the last one that errored)")),console.log(chalk.cyan(" save [file]")+chalk.gray(" save as a real .ipynb")),console.log(chalk.cyan(" restart")+chalk.gray(" restart the kernel (wipes all variables)")),console.log(chalk.cyan(" gpu on/off")+chalk.gray(" run cells on a leased GPU box (dataset pre-mounted)")),console.log(chalk.cyan(" gpu status")+chalk.gray(" your lease/queue · ")+chalk.cyan("gpu queue")+chalk.gray(" who is on the GPU now")),console.log(chalk.cyan(" gpu key <k>")+chalk.gray(" save your access key")),console.log(chalk.cyan(" download")+chalk.gray(" pull submission.csv from the GPU box to this machine")),console.log(chalk.cyan(" check / submit")+chalk.gray(" verify / score your submission.csv — right here, no quit needed")),console.log(chalk.cyan(" board")+chalk.gray(" live standings for this competition")),console.log(chalk.cyan(" open")+chalk.gray(" reopen the last figure in your image viewer")),console.log(chalk.cyan(" clear")+chalk.gray(" clear the screen · ")+chalk.cyan("quit")+chalk.gray(" leave")),console.log(chalk.gray(" Everything else runs as Python in the persistent kernel (and is appended as a cell).")),console.log(),void ue();if("guide"===a||"/guide"===a)return(()=>{const o=Z.path?c(Z.path):process.cwd(),l=F(Z.path);let t="",r=!1;try{const e=JSON.parse(n(g(o,"meta.json"),"utf-8"));t=e.judge??"",r=!0===e.needs_gpu}catch{}const a=Z.cells.filter(e=>null!==e.result).length,i=e(g(o,"submission.csv"));console.log(),console.log(chalk.bold.white(" ═══ 📖 Notebook guide ═══")),console.log(),console.log(chalk.gray(" Where you are: ")+chalk.white(Z.path?s(Z.path):"new empty notebook")+chalk.gray(` · ${Z.cells.length} cells (${a} run) · submission.csv: `)+(i?chalk.green("written ✓"):chalk.yellow("not yet — run all"))),oe?console.log(chalk.gray(" ⚡ GPU: ")+chalk.green("connected")+chalk.gray(" — ")+chalk.cyan("gpu status")+chalk.gray(" your time left · ")+chalk.cyan("gpu queue")+chalk.gray(" who is on · ")+chalk.cyan("download <file>")+chalk.gray(" pull results")):r&&console.log(chalk.gray(" ⚡ GPU task, not connected — ")+chalk.cyan("gpu key <your-key>")+chalk.gray(" then ")+chalk.cyan("gpu on")+chalk.gray(" · ")+chalk.cyan("gpu queue")+chalk.gray(" shows who is on")),console.log(),console.log(chalk.bold.white(" The loop:")),console.log(chalk.gray(" 1. ")+chalk.bold.cyan("run all")+chalk.gray(" — trains the baseline, writes submission.csv")),l&&"icoa-holdout"===t?(console.log(chalk.gray(" 2. ")+chalk.bold.cyan("submit")+chalk.gray(" — scored + ranked right here, no need to leave (")+chalk.cyan("board")+chalk.gray(" shows standings)")),console.log(chalk.gray(" 3. come back, improve the MODEL cell (")+chalk.cyan("cells")+chalk.gray(" lists them, ")+chalk.cyan("edit N")+chalk.gray(" edits) — beat your score."))):l&&r?(console.log(chalk.gray(" 2. ")+chalk.cyan("download <file>")+chalk.gray(" — pull your results (submission.csv / predictions.zip) off the GPU box")),console.log(chalk.gray(" 3. submit on kaggle.com — the notebook's final cell has the exact steps")),console.log(chalk.gray(" 4. come back, improve the MODEL cell (")+chalk.cyan("cells")+chalk.gray(" lists them, ")+chalk.cyan("edit N")+chalk.gray(" edits) — beat it."))):l?(console.log(chalk.gray(" 2. ")+chalk.bold.cyan("check")+chalk.gray(" — verifies the file format, right here")),console.log(chalk.gray(" 3. upload submission.csv on kaggle.com — read your public score")),console.log(chalk.gray(" 4. come back, improve the MODEL cell (")+chalk.cyan("cells")+chalk.gray(" lists them, ")+chalk.cyan("edit N")+chalk.gray(" edits) — beat it."))):console.log(chalk.gray(" 2. edit cells (")+chalk.cyan("edit N")+chalk.gray("), re-run, ")+chalk.cyan("save")+chalk.gray(" — this is a free notebook.")),console.log(),console.log(chalk.bold.white(" If it looks stuck:")),console.log(chalk.gray(" · prompt shows ")+chalk.cyan("...>")+chalk.gray(" → you are inside a code block. Press Enter on an ")+chalk.bold.white("empty line")+chalk.gray(".")),console.log(chalk.gray(" · never type ")+chalk.cyan("python file.ipynb")+chalk.gray(" here — you are already inside. Just ")+chalk.bold.cyan("run all")+chalk.gray(".")),console.log(),console.log(chalk.bold.white(" 🤝 AI help: ")+chalk.gray("split the screen ")+chalk.bold.cyan("Ctrl-b %")+chalk.gray(" · right pane: ")+chalk.bold.cyan("icoa")+chalk.gray(" → ↑↓ pick 🧠 Learn → at ")+chalk.cyan("icoa learn>")+chalk.gray(" type ")+chalk.bold.cyan("ai4ioai")+chalk.gray(" — live Gemma 3")),console.log(chalk.gray(" All commands: ")+chalk.cyan("help")+chalk.gray(" · leave: ")+chalk.cyan("quit")),console.log()})(),void ue();if("python"===a||"python3"===a)return console.log(chalk.gray(" You're already inside the notebook kernel — no need to run python.")),console.log(chalk.gray(" Type ")+chalk.bold.cyan("run all")+chalk.gray(" to run every cell, or ")+chalk.cyan("guide")+chalk.gray(" for the basics.")),void ue();if("open"===a)return Q&&L(Q)?console.log(chalk.gray(" reopening ")+chalk.cyan(Q)):console.log(chalk.gray(" no figure yet — plot something (e.g. matplotlib) first.")),void ue();if("cells"===a||"ls"===a)return(()=>{if(0===Z.cells.length)return void console.log(chalk.gray(" no cells yet — type Python, or ")+chalk.cyan("add")+chalk.gray(" to write one in your editor."));console.log();const e=Math.max(20,(process.stdout.columns??80)-12);Z.cells.forEach((o,n)=>{const l=T(o),t="✓"===l?chalk.green(l.padEnd(2)):"✗"===l?chalk.red(l.padEnd(2)):"md"===l?chalk.magenta(l.padEnd(2)):chalk.gray(l.padEnd(2));console.log(`${chalk.cyan(String(n+1).padStart(4))} ${t} ${chalk.white(I(o,e))}`)}),console.log(),console.log(chalk.gray(" ")+chalk.cyan("show N")+chalk.gray(" · ")+chalk.cyan("edit N")+chalk.gray(" · ")+chalk.cyan("run N")+chalk.gray(" · ")+chalk.cyan("run all"))})(),void ue();if("show"===a)return(e=>{const o=we(e);if(o){if(console.log(),console.log(chalk.gray(` ── cell ${o.idx} ${"markdown"===o.cell.kind?"(markdown) ":""}──`)),"markdown"===o.cell.kind){for(const e of P(o.cell.source))console.log(e);console.log(chalk.gray(" (rendered — ")+chalk.cyan(`edit ${o.idx}`)+chalk.gray(" for the raw source)"))}else for(const e of o.cell.source.split("\n"))console.log(chalk.white(` ${e}`));o.cell.result&&(console.log(chalk.gray(" ── output ──")),X(o.cell.result,W)),console.log()}})(d),void ue();if("edit"===a)return(e=>{const o=we(e);if(!o)return;ae.pause();const n=m(o.cell.source,"markdown"===o.cell.kind?".md":".py");if(ae.resume(),null===n)return void console.log(chalk.yellow(" editor cancelled or failed — cell unchanged.")+chalk.gray(" (set $EDITOR to choose your editor)"));const l=n.replace(/\s+$/,"");l!==o.cell.source?(o.cell.source=l,o.cell.result=null,o.cell.rawOutputs=void 0,o.cell.rawExecCount=void 0,Z.dirty=!0,console.log(chalk.green(` ✓ cell ${o.idx} updated`)+chalk.gray(" — ")+chalk.cyan(`run ${o.idx}`)+chalk.gray(" to execute."))):console.log(chalk.gray(" no changes."))})(d),void ue();if("add"===a)return(e=>{const o=e.trim().toLowerCase(),n="md"===o||"markdown"===o||"text"===o||"txt"===o;if(o&&!n&&"py"!==o&&"code"!==o&&"python"!==o)return void console.log(chalk.yellow(" usage: ")+chalk.cyan("add")+chalk.yellow(" (Python cell) · ")+chalk.cyan("add md")+chalk.yellow(" (markdown/text cell)"));ae.pause();const l=m(n?"\x3c!-- markdown cell — write notes/headings, save & exit --\x3e\n":"",n?".md":".py");ae.resume();const t=null===l?"":l.replace(/^<!-- markdown cell[^\n]*-->\n?/,"").replace(/\s+$/,"");t?(Z.cells.push({kind:n?"markdown":"code",source:t,result:null}),Z.dirty=!0,console.log(chalk.green(` ✓ added ${n?"markdown ":""}cell ${Z.cells.length}`)+chalk.gray(" — ")+chalk.cyan(`run ${Z.cells.length}`)+chalk.gray(n?" to render it.":" to execute."))):console.log(chalk.gray(" nothing added."))})(d),void ue();if("del"===a||"delete"===a)return(e=>{const o=we(e);o&&(Z.cells.splice(o.idx-1,1),Z.dirty=!0,console.log(chalk.gray(` deleted cell ${o.idx} — ${Z.cells.length} left.`)+(o.idx<=Z.cells.length?chalk.yellow(" ⚠ 后面的 cell 号已顺移")+chalk.gray(" — ")+chalk.cyan("cells")+chalk.gray(" 重看一眼"):"")))})(d),void ue();if(("md"===a||"py"===a)&&/^\d+$/.test(d))return((e,o)=>{const n=we(e);if(!n)return;const l="markdown"===o?"markdown":"Python";n.cell.kind!==o?(n.cell.kind=o,n.cell.result=null,n.cell.rawOutputs=void 0,n.cell.rawExecCount=void 0,Z.dirty=!0,console.log(chalk.green(` ✓ cell ${n.idx} → ${l}`)+chalk.gray(" — ")+chalk.cyan(`run ${n.idx}`)+chalk.gray("markdown"===o?" to render it.":" to execute."))):console.log(chalk.gray(` cell ${n.idx} is already ${l}.`))})(d,"md"===a?"markdown":"code"),void ue();if("gpu"===a)return ye=!0,await me(d),ye=!1,void ue();if("download"===a||"dl"===a)return ye=!0,await(async e=>{if(!oe)return void console.log(chalk.gray(" local kernel — your files are already on this machine (no download needed)."));const n=e.trim()||"submission.csv",t=n.startsWith("/")?n:`${oe.outDir}/${n}`;let r;try{r=await ee.fetchFile(t)}catch(e){return console.log(chalk.yellow(` could not fetch ${t}: `)+chalk.gray(e instanceof Error?e.message:String(e))),void console.log(chalk.gray(" (run the submission cell first — it writes into the GPU workspace)"))}const a=ne?g(process.cwd(),"icoa-kaggle",ne):process.cwd(),c=g(a,s(n));try{o(a,{recursive:!0}),l(c,r)}catch{return void console.log(chalk.yellow(" could not write the file here — check directory permissions."))}console.log(chalk.green(" ✓ downloaded ")+chalk.white(c)+chalk.gray(` (${r.length} bytes)`)),ne&&"submission.csv"===s(n)&&console.log(chalk.gray(" verify it: ")+chalk.cyan(`kaggle check ${ne}`)+chalk.gray(" — then upload on Kaggle yourself."))})(d),ye=!1,void ue();if("check"===a||"submit"===a||"kaggle"===a){const e=await import("./kaggle.js"),o=d.split(/\s+/).filter(Boolean),n="kaggle"===a?(o[0]||"").toLowerCase():a,l=("kaggle"===a?o[1]:o[0])||F(Z.path)||void 0;ye=!0;try{"submit"===n?await e.runKaggleSubmit(l):"check"===n?await e.runKaggleCheck(l):"guide"===n?await e.runKaggleGuide(l):await e.runKaggle("kaggle"===a?o[0]:l)}catch(e){console.log(chalk.yellow(" That command failed: ")+chalk.gray(String(e)))}return ye=!1,void ue()}if("board"===a){const{runBoard:e}=await import("./board.js"),o=d.split(/\s+/).filter(Boolean);ye=!0;try{await e(o[0],o[1])}catch(e){console.log(chalk.yellow(" That command failed: ")+chalk.gray(String(e)))}return ye=!1,void ue()}if("ai"===a||"/ai"===a)return ye=!0,await(async e=>{const o=e.trim().split(/\s+/).filter(Boolean),l=o.length>0&&/^\d+$/.test(o[0]),t=(l?o.slice(1):o).join(" ").trim(),r=l?Number(o[0]):(()=>{for(let e=Z.cells.length-1;e>=0;e--)if((Z.cells[e].result?.outputs??[]).some(e=>"error"===e.kind))return e+1;return null})()??Z.cells.length;if(0===Z.cells.length)return void console.log(chalk.yellow(" 还没有 cell —— 先写点代码,或用 ")+chalk.cyan("add"));const a=we(String(r));if(!a)return;if("markdown"===a.cell.kind)return void console.log(chalk.yellow(` cell ${a.idx} 是 markdown —— 挑一个代码格,比如 `)+chalk.cyan("ai 2"));const s=(e=>{const o=[];for(const n of e.result?.outputs??[])"stream"===n.kind||"result"===n.kind?o.push(n.text):"error"===n.kind&&(o.push(`${n.ename}: ${n.evalue}`),o.push(n.traceback.map(e=>e.replace(R,"")).join("\n")));return o.join("\n").replace(R,"").replace(/\n{3,}/g,"\n\n").trim()})(a.cell),i=(()=>{if(!Z.path||!F(Z.path))return"";try{const e=JSON.parse(n(g(c(Z.path),"meta.json"),"utf-8")),o=[e.title,e.metric?`metric: ${e.metric}`:"",e.description].filter(Boolean).join("\n");return fe(o,350)}catch{return""}})(),y=M(),d=y?"你在帮一个 IOAI 选手调试 notebook。先说清楚这一格为什么会这样,再给出最小改动。用中文回答,简洁,给可直接粘回去的代码。":"You are helping an IOAI contestant debug a notebook cell. Say why it behaves this way, then give the smallest fix. Be concise and give code that can be pasted straight back.",p=d.length+t.length+120,m=Math.max(400,u-p-i.length),w=fe(a.cell.source,Math.floor(.6*m)),f=s?(b=s,k=m-w.length,b.length<=k?b:`…(${b.length-k} chars elided)…\n${b.slice(-k)}`):"";var b,k;const v=[d,i?`\n[task]\n${i}`:"",`\n[cell ${a.idx} code]\n${w}`,f?`\n[cell ${a.idx} output]\n${f}`:"\n(this cell has not been run yet)",t?`\n[question]\n${t}`:""].filter(Boolean).join("\n"),$=v.includes("chars elided"),x=y;console.log(),be||(be=!0,console.log(chalk.gray(" ")+chalk.bold.white("IOAI 2026 ")+chalk.white("Gemma 3n E4B")+chalk.gray(" (32k) · ")+chalk.white(M()?"单轮无记忆":"single-turn, no memory")+chalk.gray(" · ")+chalk.white(`${u} ${M()?"字符/条":"chars/message"}`)+chalk.gray(" · ")+chalk.white(M()?"1000 token/回复":"1000 tokens/reply")),console.log(chalk.gray(M()?" —— 现场就是这个模型和这几条上限,在这里练的手感能原样带过去。":" — the onsite model and the onsite limits, so the habit transfers as-is."))),console.log(chalk.gray(` → cell ${a.idx} · ${v.length}/${u} chars`)+chalk.gray(x?" · 单轮(竞赛档模型)":" · single-turn (contest model)")+(s?chalk.gray(x?" · 含输出":" · output included"):"")+($?chalk.yellow(x?" ⚠ 上下文被裁过,加一句具体问题会更准":" ⚠ context trimmed — a specific question helps"):"")),U(`ipynb: ai ${a.idx}${t?` — ${t}`:""}`);const E=await h(v);if(console.log(),!E.ok)return console.log(chalk.yellow(" AI 现在连不上 —— ")+chalk.gray(E.text)),console.log(chalk.gray(" 过一会儿再试,或用 ")+chalk.cyan("ai4ioai")+chalk.gray(" 单开一个会话。")),void console.log();for(const e of P(E.text))console.log(e);console.log(),console.log(chalk.gray(" ")+chalk.cyan(`ai ${a.idx} ${x?"<你的问题>":"<your question>"}`)+chalk.gray(x?" 追问 · ":" follow up · ")+chalk.cyan(`edit ${a.idx}`)+chalk.gray(x?" 改这一格 · ":" edit it · ")+chalk.cyan(`run ${a.idx}`)+chalk.gray(x?" 再跑一次":" run it again")),console.log(chalk.gray(x?" 这是调试助手(超限自动裁剪);想练现场那堵墙用 ":" This is the debugging aid (it trims to fit). To train against the real wall: ")+chalk.cyan("ai4ioai")),console.log()})(d),ye=!1,void ue();if("run"===a)return ye=!0,await(async e=>{const o=O(e,Z.cells.length);if(o)for(const e of o){const n=Z.cells[e-1];if("markdown"!==n.kind){console.log(chalk.gray(` ── run cell ${e}: `)+chalk.white(I(n,50))+chalk.gray(" ──"));try{n.result=await ee.execute(n.source)}catch(e){return console.log(chalk.red(" Kernel error: ")+chalk.gray(e instanceof Error?e.message:String(e))),void D()}if(Z.dirty=!0,X(n.result,W),ke(n,e),!n.result.ok){const n=e!==o[o.length-1];return console.log(chalk.yellow(` ✗ cell ${e} raised${n?" — stopping here.":"."}`)+chalk.gray(" Fix it (")+chalk.cyan(`edit ${e}`)+chalk.gray(") and re-run.")),console.log(chalk.gray(M()?" 卡住了? ":" Stuck? ")+chalk.bold.cyan(`ai ${e}`)+chalk.gray(M()?" 把这一格的代码和报错一起问 AI(不用离开 notebook)":" sends this cell and its traceback to the AI — without leaving the notebook")),void D()}}else{console.log(chalk.gray(` ── cell ${e} (markdown) ──`));for(const e of P(n.source))console.log(e);console.log()}}else console.log(0===Z.cells.length?chalk.yellow(" no cells yet — type Python or use ")+chalk.cyan("add"):chalk.yellow(" usage: ")+chalk.cyan("run 3")+chalk.yellow(" · ")+chalk.cyan("run 1-5")+chalk.yellow(" · ")+chalk.cyan("run all"))})(d),ye=!1,void ue();if("save"===a)return(e=>{if(0===Z.cells.length)return void console.log(chalk.yellow(" nothing to save — the notebook is empty."));let o;o=e.trim()?i(e.trim())?e.trim():y(process.cwd(),e.trim()):Z.path?Z.path.endsWith(".ipynb")?Z.path:`${Z.path.replace(/\.[^./]+$/,"")}.ipynb`:y(process.cwd(),"notebook.ipynb"),o.endsWith(".ipynb")||(o+=".ipynb");try{l(o,_(Z.cells)),Z.path=o,Z.dirty=!1,console.log(chalk.green(" ✓ saved ")+chalk.cyan(o)+chalk.gray(` (${Z.cells.length} cells)`))}catch(e){console.log(chalk.red(" save failed: ")+chalk.gray(e instanceof Error?e.message:String(e)))}})(d),void ue();if(""===r)return void ue()}if(ie.length>0&&B.has(r.toLowerCase()))return console.log(chalk.gray(" (⏸ you are inside a multi-line block — press Enter on an ")+chalk.bold.white("empty line")+chalk.gray(" to run it; then ")+chalk.cyan(r.toLowerCase().replace(/^\//,""))+chalk.gray(" will work. This line was not added to the block.)")),void ue();if(ie.push(t),!$(ie))return 1!==ie.length||ge||(ge=!0,console.log(chalk.gray(" (multi-line block started — keep typing; an empty line runs it)"))),void ue();const a=ie.join("\n").replace(/\s+$/,"");if(ie.length=0,""!==a.trim()){ye=!0;try{const e=await ee.execute(a);Z.cells.push({kind:"code",source:a,result:e}),Z.dirty=!0,X(e,W);const o=ke(Z.cells[Z.cells.length-1],Z.cells.length);e.ok||o||D()}catch(e){console.log(chalk.red(" Kernel error: ")+chalk.gray(e instanceof Error?e.message:String(e))),D()}ye=!1,ue()}else ue()};ae.on("line",e=>{ve(e)}),te||ae.on("close",async()=>{await ee.shutdown().catch(()=>{}),process.exit(0)}),ue(),await pe}
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{registerViewCommand as t}from"./commands/view.js";import{registerConnectCommand as l}from"./commands/connect.js";import{registerChallengeCommand as i}from"./commands/challenge.js";import{registerDoctorCommand as a}from"./commands/doctor.js";import{registerNoteCommand as c}from"./commands/note.js";import{registerLogCommand as m}from"./commands/log.js";import{registerLangCommand as g}from"./commands/lang.js";import{registerSetupCommand as d}from"./commands/setup.js";import{registerEnvCommand as p}from"./commands/env.js";import{registerAienvCommand as y}from"./commands/aienv.js";import{registerIpynbCommand as h}from"./commands/ipynb.js";import{registerAi4ctfCommand as u}from"./commands/ai4ctf.js";import{registerExamCommand as f}from"./commands/exam.js";import{registerCtf4aiDemoCommand as w}from"./commands/ctf4ai-demo.js";import{registerThemeCommand as b}from"./commands/theme.js";import{registerLearnCommand as j}from"./commands/learn.js";import{registerCtf4VlaCommand as $}from"./commands/ctf4vla.js";import{registerDemo2Command as T}from"./commands/demo2.js";import{registerDemo4Command as v}from"./commands/demo4.js";import{registerDemo5Command as A}from"./commands/demo5.js";import{registerGfssmCommand as I}from"./commands/gfssm.js";import{registerSimCommand as E}from"./commands/sim.js";import{registerArenaCommand as C}from"./commands/arena.js";import{registerKaggleCommand as _}from"./commands/kaggle.js";import{registerSprintCommand as x}from"./commands/sprint.js";import{registerTeleloadCommand as S}from"./commands/teleload.js";import{registerBoardCommand as k}from"./commands/board.js";import{registerTypeCommand as L}from"./commands/type.js";import{registerWmCommand as R}from"./commands/wm.js";import{getConfig as O,saveConfig as F}from"./lib/config.js";import{startRepl as N}from"./repl.js";import{RELAUNCH_CODE as U}from"./lib/menu-nav.js";import{setTerminalTheme as P}from"./lib/theme.js";import{cleanupTerminal as M}from"./lib/terminal-cleanup.js";import{checkForUpdates as D}from"./lib/update-check.js";import{detectIcoaInstalls as G,isWindowsBuildInWslShell as W}from"./lib/platform.js";import{ICOA_BIG as H}from"./lib/banner.js";import{readFileSync as Y}from"node:fs";import{fileURLToPath as J}from"node:url";import{dirname as V,join as X}from"node:path";const q=V(J(import.meta.url)),z=JSON.parse(Y(X(q,"..","package.json"),"utf-8")).version,K=chalk.cyan(" ─────────────────────────────────────────────────────"),B=`\n${K}\n\n${H.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${z}`)}\n\n${K}\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=n?process.argv.slice(1,2):process.argv.slice(1),r=o(process.execPath,s,{stdio:"inherit",env:{...process.env,ICOA_SUPERVISED:"1",...n?{ICOA_RELAUNCH:"1"}:{}}});if(r.signal){const{appendExitLog:o}=await import("./lib/exit-log.js");o("worker-signal",`signal=${r.signal}`),console.log(),console.log(chalk.yellow(` ⚠ icoa was stopped by signal ${r.signal}`)+chalk.gray(" — another process killed it (not a crash). Run icoa --resume to continue.")),e=0;break}if(e=r.status,0!==e&&e!==U){const{appendExitLog:o}=await import("./lib/exit-log.js");o("worker-exit",`code=${e}`);const n=null!==e&&e>128?e-128:0;console.log(),console.log(n?chalk.yellow(` ⚠ icoa was stopped by signal ${n}`)+chalk.gray(" — another process killed it (not a crash). Run icoa --resume to continue."):chalk.yellow(` ⚠ icoa exited unexpectedly (code ${e})`)+chalk.gray(" — see ~/.icoa/exit.log · run icoa --resume to continue."))}n=!0}while(e===U);M(),process.exit(e??0)}}const Q=new o;if(Q.name("icoa").version(z).description("ICOA CLI — CLI-Native CTF Competition Terminal").option("--resume","Resume previous session").option("--continue","Continue where you left off — jump straight to your last learn card / exam").action(async o=>{const e=O();P("high-contrast"===e.themeVariant?"high-contrast":"dark"),D(),function(){const o=G();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();console.log(B);const s=!!o.resume||!!o.continue;if(process.argv.length<=2||s)return o.continue&&(process.env.ICOA_AUTO_RESUME="1"),s||"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 N(Q,s)}),e(Q),n(Q),s(Q),r(Q),t(Q),l(Q),i(Q),a(Q),c(Q),m(Q),g(Q),d(Q),p(Q),y(Q),h(Q),u(Q),f(Q),w(Q),b(Q),j(Q),$(Q),T(Q),v(Q),A(Q),I(Q),E(Q),C(Q),_(Q),S(Q),x(Q),k(Q),R(Q),L(Q),Q.command("model",{hidden:!0}).argument("[name]","model name to switch to").action(o=>{const e=O().geminiModel||"gemini-2.5-flash-lite";o?(F({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)))}Q.hook("preAction",()=>{!function(){if(!W())return;const o="─".repeat(Math.max(24,Math.min(52,(process.stdout.columns||80)-4)));console.log(),console.log(chalk.yellow(` ${o}`)),console.log(chalk.yellow.bold(" ⚠ You ARE inside Ubuntu — but this icoa")),console.log(chalk.yellow.bold(" is the WINDOWS build.")),console.log(),console.log(chalk.gray(" WSL appends the Windows PATH, so ")+chalk.cyan("icoa")),console.log(chalk.gray(" found the Windows copy first. Almost")),console.log(chalk.gray(" nothing will work: no vi, no python3,")),console.log(chalk.gray(" env setup cannot install.")),console.log(),console.log(chalk.gray(" Running: ")+chalk.white(process.execPath)),console.log(),console.log(chalk.white(" Fix — inside Ubuntu, check npm is Linux FIRST:")),console.log(` ${chalk.bold.cyan("which npm")}${chalk.gray(" → must not be /mnt/c/...")}`),console.log(` ${chalk.bold.cyan("sudo npm install -g icoa-cli")}`),console.log(` ${chalk.bold.cyan("hash -r")}${chalk.gray(" → bash caches the old path")}`),console.log(chalk.gray(" Then restart icoa and check ")+chalk.bold.cyan("which icoa")),console.log(chalk.yellow(` ${o}`)),console.log()}()}),Q.parse();
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{registerViewCommand as t}from"./commands/view.js";import{registerConnectCommand as i}from"./commands/connect.js";import{registerChallengeCommand as l}from"./commands/challenge.js";import{registerDoctorCommand as a}from"./commands/doctor.js";import{registerNoteCommand as c}from"./commands/note.js";import{registerLogCommand as m}from"./commands/log.js";import{registerLangCommand as g}from"./commands/lang.js";import{registerSetupCommand as d}from"./commands/setup.js";import{registerEnvCommand as p}from"./commands/env.js";import{registerAienvCommand as y}from"./commands/aienv.js";import{registerIpynbCommand as h}from"./commands/ipynb.js";import{registerAi4ctfCommand as u}from"./commands/ai4ctf.js";import{registerExamCommand as f}from"./commands/exam.js";import{registerCtf4aiDemoCommand as w}from"./commands/ctf4ai-demo.js";import{registerThemeCommand as b}from"./commands/theme.js";import{registerLearnCommand as j}from"./commands/learn.js";import{registerCtf4VlaCommand as $}from"./commands/ctf4vla.js";import{registerDemo2Command as T}from"./commands/demo2.js";import{registerDemo4Command as v}from"./commands/demo4.js";import{registerDemo5Command as A}from"./commands/demo5.js";import{registerGfssmCommand as I}from"./commands/gfssm.js";import{registerGpuForTaskCommand as E}from"./commands/gpufortask.js";import{registerSimCommand as C}from"./commands/sim.js";import{registerArenaCommand as _}from"./commands/arena.js";import{registerKaggleCommand as x}from"./commands/kaggle.js";import{registerSprintCommand as S}from"./commands/sprint.js";import{registerTeleloadCommand as k}from"./commands/teleload.js";import{registerBoardCommand as L}from"./commands/board.js";import{registerTypeCommand as R}from"./commands/type.js";import{registerWmCommand as O}from"./commands/wm.js";import{getConfig as F,saveConfig as N}from"./lib/config.js";import{startRepl as U}from"./repl.js";import{RELAUNCH_CODE as P}from"./lib/menu-nav.js";import{setTerminalTheme as M}from"./lib/theme.js";import{cleanupTerminal as D}from"./lib/terminal-cleanup.js";import{checkForUpdates as G}from"./lib/update-check.js";import{detectIcoaInstalls as W,isWindowsBuildInWslShell as H}from"./lib/platform.js";import{ICOA_BIG as Y}from"./lib/banner.js";import{readFileSync as J}from"node:fs";import{fileURLToPath as V}from"node:url";import{dirname as X,join as q}from"node:path";const z=X(V(import.meta.url)),K=JSON.parse(J(q(z,"..","package.json"),"utf-8")).version,B=chalk.cyan(" ─────────────────────────────────────────────────────"),Q=`\n${B}\n\n${Y.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${B}\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=n?process.argv.slice(1,2):process.argv.slice(1),r=o(process.execPath,s,{stdio:"inherit",env:{...process.env,ICOA_SUPERVISED:"1",...n?{ICOA_RELAUNCH:"1"}:{}}});if(r.signal){const{appendExitLog:o}=await import("./lib/exit-log.js");o("worker-signal",`signal=${r.signal}`),console.log(),console.log(chalk.yellow(` ⚠ icoa was stopped by signal ${r.signal}`)+chalk.gray(" — another process killed it (not a crash). Run icoa --resume to continue.")),e=0;break}if(e=r.status,0!==e&&e!==P){const{appendExitLog:o}=await import("./lib/exit-log.js");o("worker-exit",`code=${e}`);const n=null!==e&&e>128?e-128:0;console.log(),console.log(n?chalk.yellow(` ⚠ icoa was stopped by signal ${n}`)+chalk.gray(" — another process killed it (not a crash). Run icoa --resume to continue."):chalk.yellow(` ⚠ icoa exited unexpectedly (code ${e})`)+chalk.gray(" — see ~/.icoa/exit.log · run icoa --resume to continue."))}n=!0}while(e===P);D(),process.exit(e??0)}}const Z=new o;if(Z.name("icoa").version(K).description("ICOA CLI — CLI-Native CTF Competition Terminal").option("--resume","Resume previous session").option("--continue","Continue where you left off — jump straight to your last learn card / exam").action(async o=>{const e=F();M("high-contrast"===e.themeVariant?"high-contrast":"dark"),G(),function(){const o=W();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();console.log(Q);const s=!!o.resume||!!o.continue;if(process.argv.length<=2||s)return o.continue&&(process.env.ICOA_AUTO_RESUME="1"),s||"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(Z,s)}),e(Z),n(Z),s(Z),r(Z),t(Z),i(Z),l(Z),a(Z),c(Z),m(Z),g(Z),d(Z),p(Z),y(Z),h(Z),u(Z),f(Z),w(Z),b(Z),j(Z),$(Z),T(Z),v(Z),A(Z),I(Z),E(Z),C(Z),_(Z),x(Z),k(Z),S(Z),L(Z),O(Z),R(Z),Z.command("model",{hidden:!0}).argument("[name]","model name to switch to").action(o=>{const e=F().geminiModel||"gemini-2.5-flash-lite";o?(N({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)))}Z.hook("preAction",()=>{!function(){if(!H())return;const o="─".repeat(Math.max(24,Math.min(52,(process.stdout.columns||80)-4)));console.log(),console.log(chalk.yellow(` ${o}`)),console.log(chalk.yellow.bold(" ⚠ You ARE inside Ubuntu — but this icoa")),console.log(chalk.yellow.bold(" is the WINDOWS build.")),console.log(),console.log(chalk.gray(" WSL appends the Windows PATH, so ")+chalk.cyan("icoa")),console.log(chalk.gray(" found the Windows copy first. Almost")),console.log(chalk.gray(" nothing will work: no vi, no python3,")),console.log(chalk.gray(" env setup cannot install.")),console.log(),console.log(chalk.gray(" Running: ")+chalk.white(process.execPath)),console.log(),console.log(chalk.white(" Fix — inside Ubuntu, check npm is Linux FIRST:")),console.log(` ${chalk.bold.cyan("which npm")}${chalk.gray(" → must not be /mnt/c/...")}`),console.log(` ${chalk.bold.cyan("sudo npm install -g icoa-cli")}`),console.log(` ${chalk.bold.cyan("hash -r")}${chalk.gray(" → bash caches the old path")}`),console.log(chalk.gray(" Then restart icoa and check ")+chalk.bold.cyan("which icoa")),console.log(chalk.yellow(` ${o}`)),console.log()}()}),Z.parse();
@@ -1 +1 @@
1
- export const REGISTRY=[{name:"join",mapsTo:["ctf","join"],rest:!0,help:{order:0,section:"Competition",label:"join <url>",text:"Connect to CTFd"}},{name:"activate",mapsTo:["ctf","activate"],rest:!0},{name:"challenges",mapsTo:["ctf","challenges"],help:{order:1,section:"Competition",label:"challenges (ch)",text:"List challenges by category"}},{name:"ch",mapsTo:["ctf","challenges"]},{name:"open",mapsTo:["ctf","open"],rest:!0,help:{order:2,section:"Competition",label:"open <id>",text:"Read challenge + get next steps"}},{name:"submit",mapsTo:["ctf","submit"],rest:!0,help:{order:3,section:"Competition",label:"submit <id> <flag>",text:"Submit a flag"}},{name:"flag",mapsTo:["ctf","submit"],rest:!0},{name:"scoreboard",mapsTo:["ctf","scoreboard"],rest:!0,help:{order:4,section:"Competition",label:"scoreboard (sb)",text:"Live rankings"}},{name:"sb",mapsTo:["ctf","scoreboard"],rest:!0},{name:"status",mapsTo:["ctf","status"],help:{order:5,section:"Competition",label:"status",text:"Your score & timer"}},{name:"time",mapsTo:["ctf","time"],help:{order:6,section:"Competition",label:"time",text:"Countdown timer"}},{name:"ref",help:{order:8,section:"Tools",label:"ref [topic]",text:"Quick reference (linux, web, crypto...)"}},{name:"shell",help:{order:9,section:"Tools",label:"shell",text:"Interactive CTF shell (sandbox if present, else host)"}},{name:"files",help:{order:10,section:"Tools",label:"files <id>",text:"Download challenge files"}},{name:"view"},{name:"connect",help:{order:11,section:"Tools",label:"connect <id>",text:"Connect to remote target"}},{name:"challenge",subs:["start","stop","status"]},{name:"note",help:{order:12,section:"Tools",label:"note [text]",text:"Add a logbook entry (in-exam: tags current Q)"}},{name:"notes"},{name:"logbook",help:{order:13,section:"Tools",label:"logbook [n]",text:"View your logbook (in-exam: filter by Q)"}},{name:"log",help:{order:14,section:"Tools",label:"log",text:"Session history"}},{name:"man"},{name:"manual"},{name:"lang",help:{order:21,section:"System",label:"lang [code]",text:"Switch language (15 supported)"}},{name:"setup",help:{order:20,section:"System",label:"setup",text:"Configure settings"}},{name:"env"},{name:"aienv"},{name:"ipynb"},{name:"kaggle"},{name:"teleload"},{name:"pandahelpme31"},{name:"guide"},{name:"ai4ctf",help:{order:7,section:"AI Teammate",label:"ai4ctf",text:"Free-chat with your AI teammate"}},{name:"model"},{name:"ctf",subs:["challenges","open","submit","scoreboard","status","join","logout","time"]},{name:"exam",subs:["list","start","q","answer","submit","review","result","next","prev","mark","unmark","help"]},{name:"demo",mapsTo:["exam","demo"]},{name:"retry",mapsTo:["exam","demo-retry"]},{name:"nations",mapsTo:["exam","nations"]},{name:"next",mapsTo:["exam","next"]},{name:"prev",mapsTo:["exam","prev"]},{name:"continue"},{name:"logout",mapsTo:["ctf","logout"],help:{order:22,section:"System",label:"logout",text:"Disconnect"}},{name:"ctf4ai"},{name:"mark",mapsTo:["exam","mark"],rest:!0},{name:"unmark",mapsTo:["exam","unmark"],rest:!0},{name:"review",mapsTo:["exam","review"]},{name:"learn",subs:["start","continue"]},{name:"demo2"},{name:"demo4"},{name:"demo5"},{name:"gfssm"},{name:"ctf4eai"},{name:"ctf4vla"},{name:"theme"},{name:"sim"},{name:"arena",subs:["eai"]},{name:"eai"},{name:"wm"},{name:"type",subs:["15","30","60","words","python","shell","history"],help:{order:15,section:"Tools",label:"type [30]",text:"Typing speed trainer — WPM, live colours, no token"}},{name:"board"},{name:"learn-pull"},{name:"doctor",help:{order:19,section:"System",label:"doctor",text:"Self-check: version, Python, server, tools"}},{name:"bg",nav:!0,help:{order:17,section:"System",label:"bg <command>",text:"Run a shell command in the background"}},{name:"tasks",nav:!0,help:{order:18,section:"System",label:"tasks [id]",text:"Background task panel / view output"}},{name:"drafts",nav:!0,help:{order:16,section:"System",label:"drafts",text:"Review staged draft answers"}},{name:"help",nav:!0},{name:"menu",nav:!0},{name:"quit",nav:!0},{name:"q",nav:!0},{name:"exit",nav:!0,help:{order:24,section:"System",label:"exit",text:"Quit (session saved)"}},{name:"back",nav:!0},{name:"resume",nav:!0,help:{order:15,section:"System",label:"resume",text:"Continue your last learn card / exam"}},{name:"clear",nav:!0,help:{order:23,section:"System",label:"clear",text:"Clear screen"}},{name:"cls",nav:!0}];export const surface=()=>REGISTRY.map(e=>e.name);export const dispatchable=()=>REGISTRY.filter(e=>!e.nav).map(e=>e.name);export function shortcutFor(e,t){const a=REGISTRY.find(t=>t.name===e&&t.mapsTo);return a?.mapsTo?a.rest?[...a.mapsTo,...t]:[...a.mapsTo]:null}export const subcommandsOf=e=>REGISTRY.find(t=>t.name===e)?.subs;export const helpRows=e=>REGISTRY.filter(t=>t.help?.section===e).sort((e,t)=>e.help.order-t.help.order).map(e=>({label:e.help.label,text:e.help.text}));
1
+ export const REGISTRY=[{name:"join",mapsTo:["ctf","join"],rest:!0,help:{order:0,section:"Competition",label:"join <url>",text:"Connect to CTFd"}},{name:"activate",mapsTo:["ctf","activate"],rest:!0},{name:"challenges",mapsTo:["ctf","challenges"],help:{order:1,section:"Competition",label:"challenges (ch)",text:"List challenges by category"}},{name:"ch",mapsTo:["ctf","challenges"]},{name:"open",mapsTo:["ctf","open"],rest:!0,help:{order:2,section:"Competition",label:"open <id>",text:"Read challenge + get next steps"}},{name:"submit",mapsTo:["ctf","submit"],rest:!0,help:{order:3,section:"Competition",label:"submit <id> <flag>",text:"Submit a flag"}},{name:"flag",mapsTo:["ctf","submit"],rest:!0},{name:"scoreboard",mapsTo:["ctf","scoreboard"],rest:!0,help:{order:4,section:"Competition",label:"scoreboard (sb)",text:"Live rankings"}},{name:"sb",mapsTo:["ctf","scoreboard"],rest:!0},{name:"status",mapsTo:["ctf","status"],help:{order:5,section:"Competition",label:"status",text:"Your score & timer"}},{name:"time",mapsTo:["ctf","time"],help:{order:6,section:"Competition",label:"time",text:"Countdown timer"}},{name:"ref",help:{order:8,section:"Tools",label:"ref [topic]",text:"Quick reference (linux, web, crypto...)"}},{name:"shell",help:{order:9,section:"Tools",label:"shell",text:"Interactive CTF shell (sandbox if present, else host)"}},{name:"files",help:{order:10,section:"Tools",label:"files <id>",text:"Download challenge files"}},{name:"view"},{name:"connect",help:{order:11,section:"Tools",label:"connect <id>",text:"Connect to remote target"}},{name:"challenge",subs:["start","stop","status"]},{name:"note",help:{order:12,section:"Tools",label:"note [text]",text:"Add a logbook entry (in-exam: tags current Q)"}},{name:"notes"},{name:"logbook",help:{order:13,section:"Tools",label:"logbook [n]",text:"View your logbook (in-exam: filter by Q)"}},{name:"log",help:{order:14,section:"Tools",label:"log",text:"Session history"}},{name:"man"},{name:"manual"},{name:"lang",help:{order:21,section:"System",label:"lang [code]",text:"Switch language (15 supported)"}},{name:"setup",help:{order:20,section:"System",label:"setup",text:"Configure settings"}},{name:"env"},{name:"aienv"},{name:"ipynb"},{name:"kaggle"},{name:"teleload"},{name:"pandahelpme31"},{name:"gpufortask"},{name:"guide"},{name:"ai4ctf",help:{order:7,section:"AI Teammate",label:"ai4ctf",text:"Free-chat with your AI teammate"}},{name:"model"},{name:"ctf",subs:["challenges","open","submit","scoreboard","status","join","logout","time"]},{name:"exam",subs:["list","start","q","answer","submit","review","result","next","prev","mark","unmark","help"]},{name:"demo",mapsTo:["exam","demo"]},{name:"retry",mapsTo:["exam","demo-retry"]},{name:"nations",mapsTo:["exam","nations"]},{name:"next",mapsTo:["exam","next"]},{name:"prev",mapsTo:["exam","prev"]},{name:"continue"},{name:"logout",mapsTo:["ctf","logout"],help:{order:22,section:"System",label:"logout",text:"Disconnect"}},{name:"ctf4ai"},{name:"mark",mapsTo:["exam","mark"],rest:!0},{name:"unmark",mapsTo:["exam","unmark"],rest:!0},{name:"review",mapsTo:["exam","review"]},{name:"learn",subs:["start","continue"]},{name:"demo2"},{name:"demo4"},{name:"demo5"},{name:"gfssm"},{name:"ctf4eai"},{name:"ctf4vla"},{name:"theme"},{name:"sim"},{name:"arena",subs:["eai"]},{name:"eai"},{name:"wm"},{name:"type",subs:["15","30","60","words","python","shell","history"],help:{order:15,section:"Tools",label:"type [30]",text:"Typing speed trainer — WPM, live colours, no token"}},{name:"board"},{name:"learn-pull"},{name:"doctor",help:{order:19,section:"System",label:"doctor",text:"Self-check: version, Python, server, tools"}},{name:"bg",nav:!0,help:{order:17,section:"System",label:"bg <command>",text:"Run a shell command in the background"}},{name:"tasks",nav:!0,help:{order:18,section:"System",label:"tasks [id]",text:"Background task panel / view output"}},{name:"drafts",nav:!0,help:{order:16,section:"System",label:"drafts",text:"Review staged draft answers"}},{name:"help",nav:!0},{name:"menu",nav:!0},{name:"quit",nav:!0},{name:"q",nav:!0},{name:"exit",nav:!0,help:{order:24,section:"System",label:"exit",text:"Quit (session saved)"}},{name:"back",nav:!0},{name:"resume",nav:!0,help:{order:15,section:"System",label:"resume",text:"Continue your last learn card / exam"}},{name:"clear",nav:!0,help:{order:23,section:"System",label:"clear",text:"Clear screen"}},{name:"cls",nav:!0}];export const surface=()=>REGISTRY.map(e=>e.name);export const dispatchable=()=>REGISTRY.filter(e=>!e.nav).map(e=>e.name);export function shortcutFor(e,t){const a=REGISTRY.find(t=>t.name===e&&t.mapsTo);return a?.mapsTo?a.rest?[...a.mapsTo,...t]:[...a.mapsTo]:null}export const subcommandsOf=e=>REGISTRY.find(t=>t.name===e)?.subs;export const helpRows=e=>REGISTRY.filter(t=>t.help?.section===e).sort((e,t)=>e.help.order-t.help.order).map(e=>({label:e.help.label,text:e.help.text}));
package/dist/lib/gpu.js CHANGED
@@ -1 +1 @@
1
- const t=/^[a-zA-Z0-9][a-zA-Z0-9.-]*$/,e=/^[a-z_][a-z0-9_-]*$/,o=/^\/[A-Za-z0-9._/-]*$/;function s(t,e){return"string"==typeof t[e]&&""!==t[e]?t[e]:void 0}function r(t,e){return"number"==typeof t[e]&&Number.isFinite(t[e])?t[e]:void 0}export function parseLeaseResponse(t){const e=t??{},o=e.data??e,a=s(o,"state");return"ready"!==a&&"queued"!==a&&"none"!==a?{state:"error",message:s(o,"error")??s(o,"message")??"unexpected broker response"}:{state:a,transport:"local"===o.transport?"local":"ssh"===o.transport?"ssh":void 0,host:s(o,"host"),port:r(o,"port"),user:s(o,"user"),python:s(o,"python"),dataRoot:s(o,"data_root"),outDir:s(o,"out_dir"),keyPem:s(o,"key_pem"),timeLeftSec:r(o,"time_left_sec"),queuePosition:r(o,"queue_position"),estimatedWaitSec:r(o,"estimated_wait_sec"),message:s(o,"message")}}export function leaseConnectProblems(s){const r=[];return"ready"!==s.state&&r.push("lease is not ready"),s.python&&o.test(s.python)||r.push("bad python path"),s.dataRoot&&o.test(s.dataRoot)||r.push("bad data_root"),s.outDir&&o.test(s.outDir)||r.push("bad out_dir"),"ssh"===s.transport?(s.host&&t.test(s.host)||r.push("bad host"),s.user&&e.test(s.user)||r.push("bad user"),void 0!==s.port&&(!Number.isInteger(s.port)||s.port<1||s.port>65535)&&r.push("bad port"),s.keyPem||r.push("missing ssh key")):"local"!==s.transport&&r.push("unknown transport"),r}export function buildSshArgs(t,e,o){return["-i",e,"-p",String(t.port??22),"-o","StrictHostKeyChecking=accept-new","-o","ConnectTimeout=15","-o","BatchMode=yes","-T",`${t.user}@${t.host}`,o]}export function queueLine(t){return`⏳ GPU queue: #${t.queuePosition??0}${t.estimatedWaitSec?` · ~${Math.max(1,Math.round(t.estimatedWaitSec/60))} min`:""}`}
1
+ const t=/^[a-zA-Z0-9][a-zA-Z0-9.-]*$/,e=/^[a-z_][a-z0-9_-]*$/,o=/^\/[A-Za-z0-9._/-]*$/;function s(t,e){return"string"==typeof t[e]&&""!==t[e]?t[e]:void 0}function r(t,e){return"number"==typeof t[e]&&Number.isFinite(t[e])?t[e]:void 0}export function parseLeaseResponse(t){const e=t??{},o=e.data??e,n=s(o,"state");return"ready"!==n&&"queued"!==n&&"none"!==n?{state:"error",message:s(o,"error")??s(o,"message")??"unexpected broker response"}:{state:n,transport:"local"===o.transport?"local":"ssh"===o.transport?"ssh":void 0,host:s(o,"host"),port:r(o,"port"),user:s(o,"user"),python:s(o,"python"),dataRoot:s(o,"data_root"),outDir:s(o,"out_dir"),keyPem:s(o,"key_pem"),timeLeftSec:r(o,"time_left_sec"),queuePosition:r(o,"queue_position"),estimatedWaitSec:r(o,"estimated_wait_sec"),windowCloseEpoch:r(o,"window_close_epoch"),message:s(o,"message")}}export function leaseConnectProblems(s){const r=[];return"ready"!==s.state&&r.push("lease is not ready"),s.python&&o.test(s.python)||r.push("bad python path"),s.dataRoot&&o.test(s.dataRoot)||r.push("bad data_root"),s.outDir&&o.test(s.outDir)||r.push("bad out_dir"),"ssh"===s.transport?(s.host&&t.test(s.host)||r.push("bad host"),s.user&&e.test(s.user)||r.push("bad user"),void 0!==s.port&&(!Number.isInteger(s.port)||s.port<1||s.port>65535)&&r.push("bad port"),s.keyPem||r.push("missing ssh key")):"local"!==s.transport&&r.push("unknown transport"),r}export function buildSshArgs(t,e,o){return["-i",e,"-p",String(t.port??22),"-o","StrictHostKeyChecking=accept-new","-o","ConnectTimeout=15","-o","BatchMode=yes","-T",`${t.user}@${t.host}`,o]}export function windowLine(t){if(!t)return"";const e=1e3*t-Date.now();if(e<=0)return"";const o=Math.max(1,Math.round(e/6e4));return`open until ${new Intl.DateTimeFormat("en-AU",{hour:"numeric",minute:"2-digit",hour12:!0,timeZone:"Australia/Sydney"}).format(new Date(1e3*t))} Sydney · ${o} min to close`}export function queueLine(t){return`⏳ GPU queue: #${t.queuePosition??0}${t.estimatedWaitSec?` · ~${Math.max(1,Math.round(t.estimatedWaitSec/60))} min`:""}`}