icoa-cli 2.19.621 → 2.19.623
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD_PARTY_LICENSES +3 -7
- package/dist/commands/ai4ctf.js +1 -1
- package/dist/commands/ctf4ai-demo.js +1 -1
- package/dist/commands/ctf4vla.js +1 -1
- package/dist/commands/exam.js +1 -1
- package/dist/commands/ioailab.js +1 -1
- package/dist/commands/setup.js +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/term-markdown.js +1 -0
- package/dist/lib/ui.js +1 -1
- package/dist/lib/update-check.js +1 -1
- package/dist/repl.js +1 -1
- package/package.json +4 -3
package/dist/commands/ioailab.js
CHANGED
|
@@ -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,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
|
+
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{default:e}=await import("@inquirer/input"),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()})}
|
package/dist/commands/setup.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import chalk from"chalk";import
|
|
1
|
+
import chalk from"chalk";import e from"@inquirer/input";import o from"@inquirer/select";import{getConfig as n,saveConfig as i}from"../lib/config.js";import{setApiKey as t}from"../lib/gemini.js";import{printSuccess as a,printError as r,printInfo as s,printHeader as c,printKeyValue as l}from"../lib/ui.js";import{logCommand as g}from"../lib/logger.js";export function registerSetupCommand(e){e.command("setup").description("Configure ICOA CLI settings").action(async()=>{await runSetupWizard()})}export async function runSetupWizard(){{g("setup");const m=n();c("ICOA CLI Setup"),console.log(),console.log(chalk.gray(" Tip: press ")+chalk.cyan("Ctrl+C")+chalk.gray(" any time to cancel setup and return to the ")+chalk.cyan("icoa>")+chalk.gray(" prompt — nothing is saved until you confirm.")),console.log(),l("CTFd URL",m.ctfdUrl||chalk.gray("Not configured")),l("CTFd Token",m.token?chalk.green("Configured"):chalk.gray("Not configured")),l("Gemini API Key",m.geminiApiKey||process.env.GEMINI_API_KEY?chalk.green("Configured"):chalk.gray("Not configured")),l("Language",m.language),l("Mode",m.mode||chalk.gray("Not set")),l("Session ID",`${m.sessionId.substring(0,8)}...`),console.log();try{switch(await o({message:"What would you like to configure?",choices:[{name:"Switch Mode",value:"mode"},{name:"Gemini API Key",value:"gemini"},{name:"CTFd Connection",value:"ctfd"},{name:"View All Settings",value:"view"},{name:"Exit",value:"exit"}]})){case"mode":{const e=await o({message:"Select mode:",choices:[{name:"National Selection — demo, exam, lightweight",value:"selection"},{name:"International Olympiad — Full CTF with AI assistance",value:"olympiad"},{name:"National/Regional Partner — Organizer management",value:"organizer"}]});i({mode:e}),a(`Mode switched to: ${e}. Restart ICOA CLI to apply.`);break}case"gemini":{console.log(),s("Get your API key from: https://aistudio.google.com/apikey"),console.log();const o=await e({message:"Enter Gemini API Key:"});o.trim()?(t(o.trim()),a("Gemini API key saved.")):r("No key provided.");break}case"ctfd":s('Use "join <url>" to connect to a CTFd instance.');break;case"view":{console.log(),c("Full Configuration");const e=n();for(const[o,n]of Object.entries(e))l(o,"token"===o&&n||"geminiApiKey"===o&&n?`${n.toString().substring(0,8)}...`:String(n??"null"));console.log();break}}console.log(),console.log(chalk.gray(" Setup complete. You are back at the ")+chalk.cyan("icoa>")+chalk.gray(" prompt.")),console.log()}catch(e){if("ExitPromptError"===e?.name)return console.log(),console.log(chalk.gray(" Setup cancelled. You are back at the ")+chalk.cyan("icoa>")+chalk.gray(" prompt. Nothing was saved.")),void console.log();throw e}}}
|
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 i}from"./commands/connect.js";import{registerChallengeCommand as l}from"./commands/challenge.js";import{registerDoctorCommand as a}from"./commands/doctor.js";import{registerUpgradeCommand as m}from"./commands/upgrade.js";import{registerNoteCommand as c}from"./commands/note.js";import{registerLogCommand as g}from"./commands/log.js";import{registerLangCommand as d}from"./commands/lang.js";import{registerSetupCommand as p}from"./commands/setup.js";import{registerEnvCommand as y}from"./commands/env.js";import{registerAienvCommand as h}from"./commands/aienv.js";import{registerPythonCommands as f}from"./commands/python.js";import{registerMujocoCommands as u}from"./commands/mujoco.js";import{registerDuckCommand as w}from"./commands/duck.js";import{registerIpynbCommand as b}from"./commands/ipynb.js";import{registerAi4ctfCommand as j}from"./commands/ai4ctf.js";import{registerExamCommand as v}from"./commands/exam.js";import{registerCtf4aiDemoCommand as $}from"./commands/ctf4ai-demo.js";import{registerThemeCommand as T}from"./commands/theme.js";import{registerLearnCommand as A}from"./commands/learn.js";import{registerCtf4VlaCommand as I}from"./commands/ctf4vla.js";import{registerDemo2Command as E}from"./commands/demo2.js";import{registerDemo4Command as C}from"./commands/demo4.js";import{registerDemo5Command as x}from"./commands/demo5.js";import{registerGfssmCommand as _}from"./commands/gfssm.js";import{registerGpuForTaskCommand as k}from"./commands/gpufortask.js";import{registerSimCommand as S}from"./commands/sim.js";import{registerArenaCommand as L}from"./commands/arena.js";import{registerKaggleCommand as R}from"./commands/kaggle.js";import{registerSprintCommand as O}from"./commands/sprint.js";import{registerTeleloadCommand as F}from"./commands/teleload.js";import{registerBoardCommand as N}from"./commands/board.js";import{registerTypeCommand as U}from"./commands/type.js";import{registerBattleCommand as P}from"./commands/battle.js";import{registerWmCommand as M}from"./commands/wm.js";import{getConfig as D,saveConfig as G}from"./lib/config.js";import{registerMicroduckCommand as W}from"./commands/microduck.js";import{startRepl as H}from"./repl.js";import{RELAUNCH_CODE as Y}from"./lib/menu-nav.js";import{setTerminalTheme as J}from"./lib/theme.js";import{cleanupTerminal as V}from"./lib/terminal-cleanup.js";import{checkForUpdates as X,runUpgrade as q,bootUpgradeEligible as z}from"./lib/update-check.js";import{detectIcoaInstalls as K,isWindowsBuildInWslShell as B}from"./lib/platform.js";import{ICOA_BIG as Q}from"./lib/banner.js";import{readFileSync as Z}from"node:fs";import{fileURLToPath as oo}from"node:url";import{dirname as eo,join as no}from"node:path";import{setDefaultResultOrder as so}from"node:dns";import{setDefaultAutoSelectFamilyAttemptTimeout as ro}from"node:net";so("ipv4first"),ro(4e3);const to=eo(oo(import.meta.url)),io=JSON.parse(Z(no(to,"..","package.json"),"utf-8")).version,lo=chalk.cyan(" ─────────────────────────────────────────────────────"),ao=`\n${lo}\n\n${Q.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${io}`)}\n\n${lo}\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!==Y){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===Y);V(),process.exit(e??0)}}const mo=new o;if(mo.name("icoa").version(io).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=D();J("high-contrast"===e.themeVariant?"high-contrast":"dark");const n=!!o.resume||!!o.continue,s=z(n)&&process.argv.length<=2;s||X(),function(){const o=K();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 r=process.env.LANG||process.env.LC_ALL||process.env.LC_CTYPE||"";if(!/UTF-?8/i.test(r))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=${r||"(unset)"}).`)),console.log(chalk.gray(' Non-English text and box characters may display as "?" or garbled glyphs.')),console.log(chalk.gray(" Fix: ")+chalk.cyan("export LANG=en_US.UTF-8")+chalk.gray(" (or your locale, e.g. ")+chalk.cyan("zh_CN.UTF-8")+chalk.gray(", ")+chalk.cyan("uk_UA.UTF-8")+chalk.gray(")")),console.log();if(console.log(ao),s&&"upgraded"===await q({ask:!0})&&process.exit(Y),process.argv.length<=2||n)return o.continue&&(process.env.ICOA_AUTO_RESUME="1"),n||"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 H(mo,n)}),e(mo),n(mo),s(mo),r(mo),t(mo),i(mo),l(mo),a(mo),m(mo),c(mo),g(mo),d(mo),p(mo),y(mo),h(mo),f(mo),u(mo),w(mo),b(mo),j(mo),v(mo),$(mo),T(mo),A(mo),I(mo),E(mo),C(mo),x(mo),_(mo),k(mo),S(mo),L(mo),R(mo),F(mo),O(mo),N(mo),M(mo),U(mo),P(mo),W(mo),mo.command("model",{hidden:!0}).argument("[name]","model name to switch to").action(o=>{const e=D().geminiModel||"gemini-2.5-flash-lite";o?(G({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)))}mo.hook("preAction",()=>{!function(){if(!B())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()}()}),mo.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{registerUpgradeCommand as m}from"./commands/upgrade.js";import{registerNoteCommand as c}from"./commands/note.js";import{registerLogCommand as g}from"./commands/log.js";import{registerLangCommand as d}from"./commands/lang.js";import{registerSetupCommand as p}from"./commands/setup.js";import{registerEnvCommand as y}from"./commands/env.js";import{registerAienvCommand as h}from"./commands/aienv.js";import{registerPythonCommands as f}from"./commands/python.js";import{registerMujocoCommands as u}from"./commands/mujoco.js";import{registerDuckCommand as w}from"./commands/duck.js";import{registerIpynbCommand as b}from"./commands/ipynb.js";import{registerAi4ctfCommand as j}from"./commands/ai4ctf.js";import{registerExamCommand as v}from"./commands/exam.js";import{registerCtf4aiDemoCommand as $}from"./commands/ctf4ai-demo.js";import{registerThemeCommand as T}from"./commands/theme.js";import{registerLearnCommand as A}from"./commands/learn.js";import{registerCtf4VlaCommand as I}from"./commands/ctf4vla.js";import{registerDemo2Command as E}from"./commands/demo2.js";import{registerDemo4Command as C}from"./commands/demo4.js";import{registerDemo5Command as x}from"./commands/demo5.js";import{registerGfssmCommand as _}from"./commands/gfssm.js";import{registerGpuForTaskCommand as k}from"./commands/gpufortask.js";import{registerSimCommand as S}from"./commands/sim.js";import{registerArenaCommand as L}from"./commands/arena.js";import{registerKaggleCommand as R}from"./commands/kaggle.js";import{registerSprintCommand as O}from"./commands/sprint.js";import{registerTeleloadCommand as F}from"./commands/teleload.js";import{registerBoardCommand as N}from"./commands/board.js";import{registerTypeCommand as U}from"./commands/type.js";import{registerBattleCommand as P}from"./commands/battle.js";import{registerWmCommand as M}from"./commands/wm.js";import{getConfig as D,saveConfig as G}from"./lib/config.js";import{registerMicroduckCommand as W}from"./commands/microduck.js";import{startRepl as H}from"./repl.js";import{RELAUNCH_CODE as Y}from"./lib/menu-nav.js";import{setTerminalTheme as J}from"./lib/theme.js";import{cleanupTerminal as V}from"./lib/terminal-cleanup.js";import{checkForUpdates as X,runUpgrade as q,bootUpgradeEligible as z,startBootCheck as K}from"./lib/update-check.js";import{detectIcoaInstalls as B,isWindowsBuildInWslShell as Q}from"./lib/platform.js";import{ICOA_BIG as Z}from"./lib/banner.js";import{readFileSync as oo}from"node:fs";import{fileURLToPath as eo}from"node:url";import{dirname as no,join as so}from"node:path";import{setDefaultResultOrder as ro}from"node:dns";import{setDefaultAutoSelectFamilyAttemptTimeout as to}from"node:net";ro("ipv4first"),to(4e3);const io=no(eo(import.meta.url)),lo=JSON.parse(oo(so(io,"..","package.json"),"utf-8")).version,ao=chalk.cyan(" ─────────────────────────────────────────────────────"),mo=`\n${ao}\n\n${Z.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${lo}`)}\n\n${ao}\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!==Y){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===Y);V(),process.exit(e??0)}}const co=new o;if(co.name("icoa").version(lo).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=D();J("high-contrast"===e.themeVariant?"high-contrast":"dark");const n=!!o.resume||!!o.continue,s=z(n)&&process.argv.length<=2;s?K():X(),function(){const o=B();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 r=process.env.LANG||process.env.LC_ALL||process.env.LC_CTYPE||"";if(!/UTF-?8/i.test(r))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=${r||"(unset)"}).`)),console.log(chalk.gray(' Non-English text and box characters may display as "?" or garbled glyphs.')),console.log(chalk.gray(" Fix: ")+chalk.cyan("export LANG=en_US.UTF-8")+chalk.gray(" (or your locale, e.g. ")+chalk.cyan("zh_CN.UTF-8")+chalk.gray(", ")+chalk.cyan("uk_UA.UTF-8")+chalk.gray(")")),console.log();if(console.log(mo),s&&"upgraded"===await q({ask:!0})&&process.exit(Y),process.argv.length<=2||n)return o.continue&&(process.env.ICOA_AUTO_RESUME="1"),n||"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 H(co,n)}),e(co),n(co),s(co),r(co),t(co),i(co),l(co),a(co),m(co),c(co),g(co),d(co),p(co),y(co),h(co),f(co),u(co),w(co),b(co),j(co),v(co),$(co),T(co),A(co),I(co),E(co),C(co),x(co),_(co),k(co),S(co),L(co),R(co),F(co),O(co),N(co),M(co),U(co),P(co),W(co),co.command("model",{hidden:!0}).argument("[name]","model name to switch to").action(o=>{const e=D().geminiModel||"gemini-2.5-flash-lite";o?(G({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)))}co.hook("preAction",()=>{!function(){if(!Q())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()}()}),co.parse();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import chalk from"chalk";import e from"cli-table3";import{Lexer as t}from"marked";const r=e=>e;export const DEFAULT_MD_STYLE={text:r,paragraph:r,listitem:r,blockquote:chalk.gray.italic,strong:chalk.bold,em:chalk.italic,del:chalk.dim.gray.strikethrough,codespan:chalk.yellow,code:chalk.yellow,heading:chalk.green.bold,firstHeading:chalk.magenta.underline.bold,link:chalk.blue,href:chalk.blue.underline,html:chalk.gray};const n=" ";function a(e){return e.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/�?39;/g,"'").replace(/&/g,"&")}function o(e,t){return e.split("\n").map(e=>e?t+e:e).join("\n")}function s(e,t){if(!e)return"";let r="";for(const n of e)switch(n.type){case"text":{const e=n;r+=e.tokens?s(e.tokens,t):t.text(a(e.text));break}case"escape":r+=t.text(n.text);break;case"strong":r+=t.strong(s(n.tokens,t));break;case"em":r+=t.em(s(n.tokens,t));break;case"del":r+=t.del(s(n.tokens,t));break;case"codespan":r+=t.codespan(a(n.text));break;case"br":r+="\n";break;case"link":{const e=n,a=s(e.tokens,t);r+=e.text===e.href?t.href(e.href):`${t.link(a)} (${t.href(e.href)})`;break}case"image":{const e=n;r+=`${t.link(`[image${e.text?`: ${e.text}`:""}]`)} (${t.href(e.href)})`;break}case"html":r+=t.html(n.text);break;default:r+=t.text(a(n.raw??""))}return r}function c(e,t){const r="number"==typeof e.start?e.start:1;return e.items.map((a,p)=>{const l=e.ordered?`${r+p}. `:"* ",u=a.task?a.checked?"[X] ":"[ ] ":"",h=[];for(const e of a.tokens)"list"===e.type?h.push(o(c(e,t),n)):"text"===e.type||"paragraph"===e.type?h.push(t.listitem(s(e.tokens??[e],t))):"checkbox"===e.type||"space"===e.type||h.push(i(e,t,!1));const[k="",...d]=h;return[t.listitem(l+u)+k,...d].join("\n")}).join("\n")}function i(t,r,i){switch(t.type){case"heading":{const e=t,n=`${"#".repeat(e.depth)} ${s(e.tokens,r)}`;return i&&1===e.depth?r.firstHeading(n):r.heading(n)}case"paragraph":return r.paragraph(s(t.tokens,r));case"text":{const e=t;return r.paragraph(e.tokens?s(e.tokens,r):r.text(a(e.text)))}case"code":return o(r.code(t.text),n);case"blockquote":return o(r.blockquote(p(t.tokens,r)),n);case"list":return o(c(t,r),n);case"table":return function(t,r){const n=new e({head:t.header.map(e=>s(e.tokens,r)),style:{head:[],border:[]}});for(const e of t.rows)n.push(e.map(e=>s(e.tokens,r)));return n.toString()}(t,r);case"hr":return"-".repeat(Math.max(10,Math.min(process.stdout.columns||80,80)-2));case"html":return r.html(t.text.replace(/\n+$/,""));default:return a(t.raw??"").replace(/\n+$/,"")}}function p(e,t){const r=[];let n=!0;for(const a of e)"space"!==a.type&&"def"!==a.type&&(r.push(i(a,t,n)),n=!1);return r.join("\n\n")}function l(e){return t=>t.includes("\n")?t.split("\n").map(t=>t?e(t):t).join("\n"):e(t)}export function renderMarkdown(e,r={}){const n={...DEFAULT_MD_STYLE,...r},a=Object.fromEntries(Object.entries(n).map(([e,t])=>[e,l(t)]));return`${p(new t({gfm:!0}).lex(e),a)}\n`}
|
package/dist/lib/ui.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import chalk from"chalk";import o from"cli-table3";import ora from"ora";import{
|
|
1
|
+
import chalk from"chalk";import o from"cli-table3";import ora from"ora";import{renderMarkdown as t}from"./term-markdown.js";import{c as e}from"./colors.js";export function printSuccess(o){console.log(chalk.green("✓ ")+e.fg(o))}export function printError(o){console.log(chalk.red("✗ ")+e.fg(o))}export function printWarning(o){console.log(chalk.yellow("⚠ ")+e.fg(o))}export function printInfo(o){console.log(chalk.blue("ℹ ")+e.fg(o))}export function printTable(t,e){const n=new o({head:t.map(o=>chalk.cyan.bold(o)),style:{head:[],border:[]}});for(const o of e)n.push(o);console.log(n.toString())}const n={text:e.fg,paragraph:e.fg,listitem:e.fg,blockquote:e.fg};export function printMarkdown(o){console.log(t(o,n))}const r={text:chalk.yellowBright,paragraph:chalk.yellowBright,listitem:chalk.yellowBright,strong:chalk.yellowBright.bold,em:chalk.yellowBright.italic,blockquote:chalk.yellowBright.italic};export function printMarkdownYellow(o){console.log(t(o,r))}let l=!1;export function setReplMode(o){l=o}export function createSpinner(o){if(l){const t={text:o,start:()=>(console.log(chalk.cyan(` ${o}`)),t),stop:()=>t,succeed:o=>(console.log(chalk.green(` ✓ ${o}`)),t),fail:o=>(console.log(chalk.red(` ✗ ${o}`)),t),info:o=>(console.log(chalk.blue(` ℹ ${o}`)),t),warn:o=>(console.log(chalk.yellow(` ⚠ ${o}`)),t)};return t}return ora({text:o,color:"cyan"})}export function formatCountdown(o){const t=new Date,e=o.getTime()-t.getTime();if(e<=0)return"00:00:00";const n=Math.floor(e/36e5),r=Math.floor(e%36e5/6e4),l=Math.floor(e%6e4/1e3);return[n.toString().padStart(2,"0"),r.toString().padStart(2,"0"),l.toString().padStart(2,"0")].join(":")}export function printHeader(o){console.log(),console.log(chalk.cyan.bold(` ${o}`)),console.log(chalk.cyan(` ${"─".repeat(o.length+4)}`))}export function printKeyValue(o,t){console.log(` ${chalk.gray(`${o}:`)} ${e.fg(t)}`)}
|
package/dist/lib/update-check.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readFileSync as
|
|
1
|
+
import{readFileSync as e,writeFileSync as o,existsSync as n,accessSync as t,realpathSync as r,constants as s}from"node:fs";import{join as i,dirname as a,basename as l,sep as c}from"node:path";import{fileURLToPath as u}from"node:url";import{homedir as g,platform as d}from"node:os";import{spawnSync as p}from"node:child_process";import chalk from"chalk";import{getIcoaDir as f}from"./config.js";const y=a(u(import.meta.url)),m=i(y,"..",".."),h=y,v=h.startsWith(g()+c)||h.includes(`${c}.nvm${c}`)||h.includes(`${c}.fnm${c}`)||h.includes(`${c}.volta${c}`),w="win32"===d()||"darwin"===d()||v?"":"sudo ",k=e=>`${w}npm install -g icoa-cli@${e}`,$=["https://registry.npmjs.org","https://registry.npmmirror.com"];function b(){return JSON.parse(e(i(m,"package.json"),"utf-8")).version}function C(e,o){const n=e.split(".").map(Number),t=o.split(".").map(Number);for(let e=0;e<Math.max(n.length,t.length);e++){const o=n[e]??0,r=t[e]??0;if(o>r)return!0;if(o<r)return!1}return!1}function x(){return i(f(),"update-check.json")}function O(){try{return n(x())?JSON.parse(e(x(),"utf-8")):null}catch{return null}}function S(e){try{const n={lastCheck:Date.now(),latestVersion:e.version,registries:e.registries};o(x(),JSON.stringify(n,null,2))}catch{}}async function A(e){const o=await Promise.all($.map(async o=>{const n=Date.now(),t=new AbortController,r=setTimeout(()=>t.abort(),e);try{const e=await fetch(`${o}/icoa-cli/latest`,{signal:t.signal,headers:{Accept:"application/json"}});return e.ok?{reg:o,version:(await e.json()).version??null,ms:Date.now()-n}:{reg:o,version:null,ms:1/0}}catch{return{reg:o,version:null,ms:1/0}}finally{clearTimeout(r)}}));let n=null;for(const e of o)!e.version||n&&!C(e.version,n)||(n=e.version);if(!n)return null;const t=[...o].sort((e,o)=>(e.version===n?0:1)-(o.version===n?0:1)||e.ms-o.ms);return{version:n,registries:t.map(e=>e.reg)}}let N=null;export function startBootCheck(){N??=A(2e3).then(e=>(e&&S(e),e??function(){const e=O();return e?.latestVersion?{version:e.latestVersion,registries:e.registries?.length?e.registries:$}:null}()))}function T(e,o){const n=function(e,o){const n=e.split(".").map(Number),t=o.split(".").map(Number);return n[0]!==t[0]||n[1]!==t[1]?-1:(n[2]??0)-(t[2]??0)}(o,e),t=n>0?chalk.gray(` (${n} version${1===n?"":"s"} behind)`):"",r=chalk.yellow(" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");if(console.log(),console.log(r),console.log(chalk.bold.yellow(" ⬆ New version available!")),console.log(),console.log(chalk.white(" Current: ")+chalk.gray(`v${e}`)),console.log(chalk.white(" Latest: ")+chalk.bold.green(`v${o}`)+t),console.log(),E().ok)console.log(chalk.white(" Update: ")+chalk.gray("type ")+chalk.bold.cyan("upgrade")+chalk.gray(" at icoa>")),console.log(chalk.gray(" or in your shell: ")+chalk.cyan(k(o)));else{console.log(chalk.white(" Update: ")+chalk.bold.cyan(k(o)));const e="darwin"===d()?"Cmd+C":"win32"===d()?"Ctrl+C":"Ctrl+Shift+C";console.log(chalk.gray(` ↑ select & ${e} to copy — typos break it`))}console.log(r),console.log()}export function checkForUpdates(){setTimeout(async()=>{try{const e=b(),o=await async function(e){const o=O();if(o?.latestVersion&&Date.now()-o.lastCheck<e.maxAgeMs)return{version:o.latestVersion,registries:o.registries?.length?o.registries:$};const n=await A(e.timeoutMs);return n&&S(n),n}({maxAgeMs:6e5,timeoutMs:5e3});o&&C(o.version,e)&&T(e,o.version)}catch{}},0)}let _=null;function E(){return _||(_=(()=>{if("win32"===d())return{ok:!1,reason:"native Windows install"};let e;try{e=r(m)}catch{return{ok:!1,reason:"cannot resolve install directory"}}const o=a(e);if("node_modules"!==l(o)||"lib"!==l(a(o)))return{ok:!1,reason:"not an npm global install (dev tree?)"};const n=a(a(o));let c;try{c=r(i(a(process.execPath),"npm"))}catch{return{ok:!1,reason:"npm not found next to node"}}let u=!1;for(const e of[o,i(n,"bin")])try{t(e,s.W_OK)}catch{u=!0}return u&&0!==p("sudo",["-V"],{stdio:"ignore"}).status?{ok:!1,reason:"needs sudo, and sudo is not available"}:{ok:!0,npm:[process.execPath,c],prefix:n,needsSudo:u}})(),_)}function U(e,o,n){console.log(chalk.yellow(" ⚠ icoa cannot upgrade itself here")+chalk.gray(` — ${n}.`)),console.log(chalk.white(" Run this in your shell instead: ")+chalk.bold.cyan(k(o))),console.log(chalk.gray(" then ")+chalk.cyan("hash -r")+chalk.gray(" and ")+chalk.cyan("icoa --version")+chalk.gray(` (now v${e})`))}export async function runUpgrade(e){const o=b();e.ask&&startBootCheck();const n=e.ask?await N:await A(8e3);if(!n)return e.ask||(console.log(chalk.yellow(" ⚠ could not reach the npm registry")+chalk.gray(` — you are on v${o}.`)),console.log(chalk.gray(" Check the network (")+chalk.cyan("doctor")+chalk.gray(") and try again."))),"offline";if(e.ask||S(n),!C(n.version,o))return e.ask||console.log(chalk.green(` ✅ icoa v${o} is the latest version.`)),"current";const t=E();if(!t.ok)return e.ask?T(o,n.version):U(o,n.version,t.reason),"skipped";if(e.ask){console.log(),console.log(chalk.bold.yellow(" ⬆ New version: ")+chalk.gray(`v${o}`)+chalk.white(" → ")+chalk.bold.green(`v${n.version}`));const{default:e}=await import("@inquirer/confirm");let t=!0;try{t=await e({message:"Upgrade now? (Enter = yes, n = later)",default:!0})}catch{t=!1}if(!t)return console.log(chalk.gray(" Later — type ")+chalk.cyan("upgrade")+chalk.gray(" at icoa> any time.")),"skipped"}return function(e,o,n){const t=process.stdin;try{t.isTTY&&t.setRawMode&&t.setRawMode(!1)}catch{}e.needsSudo&&console.log(chalk.gray(" icoa is installed system-wide — sudo will ask for your computer password."));for(const t of n)for(const n of[!1,!0]){console.log(chalk.gray(` ⬇ icoa-cli@${o} from ${t.replace(/^https:\/\//,"")}${n?" (refreshing)":""} …`));const r=[e.npm[1],"install","-g",`icoa-cli@${o}`,`--prefix=${e.prefix}`,`--registry=${t}/`,...n?["--prefer-online"]:[],"--no-fund","--no-progress","--no-audit","--loglevel=error","--fetch-timeout=30000","--fetch-retries=1"],s=e.needsSudo?p("sudo",[e.npm[0],...r],{stdio:"inherit"}):p(e.npm[0],r,{stdio:"inherit"});let i="";try{i=b()}catch{}if(i===o)return!0;console.log(chalk.yellow(" ⚠ that did not work")+chalk.gray(0===s.status?` (npm finished but icoa is still v${i||"?"})`:` (npm exit ${s.status})`))}return!1}(t,n.version,n.registries)?(console.log(chalk.green(` ✅ upgraded to v${n.version}`)+("1"===process.env.ICOA_SUPERVISED?chalk.gray(" — restarting icoa …"):chalk.gray(" — run ")+chalk.cyan("icoa")+chalk.gray(" to start it."))),console.log(),"upgraded"):(console.log(chalk.red(` ✗ upgrade failed — still v${o}.`)),U(o,n.version,"every source failed"),"failed")}export function bootUpgradeEligible(e){return!0===process.stdin.isTTY&&"1"===process.env.ICOA_SUPERVISED&&"1"!==process.env.ICOA_RELAUNCH&&"1"!==process.env.ICOA_NO_UPDATE&&!process.env.ICOA_TEST_BOOT_MODE&&!e}
|