icoa-cli 2.19.618 → 2.19.620

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{runUpgrade as a}from"../lib/update-check.js";import{returnToMainMenu as e}from"../lib/menu-nav.js";import{getMainRl as i}from"../lib/main-rl.js";export function registerUpgradeCommand(r){r.command("upgrade").alias("update").description("Upgrade icoa to the newest version and restart into it").action(async()=>{"upgraded"===await a({ask:!1})&&("1"!==process.env.ICOA_SUPERVISED||e(i()??void 0))})}
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{registerNoteCommand as m}from"./commands/note.js";import{registerLogCommand as c}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{registerPythonCommands as h}from"./commands/python.js";import{registerMujocoCommands as f}from"./commands/mujoco.js";import{registerDuckCommand as u}from"./commands/duck.js";import{registerIpynbCommand as w}from"./commands/ipynb.js";import{registerAi4ctfCommand as b}from"./commands/ai4ctf.js";import{registerExamCommand as j}from"./commands/exam.js";import{registerCtf4aiDemoCommand as $}from"./commands/ctf4ai-demo.js";import{registerThemeCommand as v}from"./commands/theme.js";import{registerLearnCommand as T}from"./commands/learn.js";import{registerCtf4VlaCommand as A}from"./commands/ctf4vla.js";import{registerDemo2Command as I}from"./commands/demo2.js";import{registerDemo4Command as E}from"./commands/demo4.js";import{registerDemo5Command as C}from"./commands/demo5.js";import{registerGfssmCommand as _}from"./commands/gfssm.js";import{registerGpuForTaskCommand as x}from"./commands/gpufortask.js";import{registerSimCommand as S}from"./commands/sim.js";import{registerArenaCommand as k}from"./commands/arena.js";import{registerKaggleCommand as L}from"./commands/kaggle.js";import{registerSprintCommand as R}from"./commands/sprint.js";import{registerTeleloadCommand as O}from"./commands/teleload.js";import{registerBoardCommand as F}from"./commands/board.js";import{registerTypeCommand as N}from"./commands/type.js";import{registerBattleCommand as U}from"./commands/battle.js";import{registerWmCommand as P}from"./commands/wm.js";import{getConfig as M,saveConfig as D}from"./lib/config.js";import{registerMicroduckCommand as G}from"./commands/microduck.js";import{startRepl as W}from"./repl.js";import{RELAUNCH_CODE as H}from"./lib/menu-nav.js";import{setTerminalTheme as Y}from"./lib/theme.js";import{cleanupTerminal as J}from"./lib/terminal-cleanup.js";import{checkForUpdates as V}from"./lib/update-check.js";import{detectIcoaInstalls as X,isWindowsBuildInWslShell as q}from"./lib/platform.js";import{ICOA_BIG as z}from"./lib/banner.js";import{readFileSync as K}from"node:fs";import{fileURLToPath as B}from"node:url";import{dirname as Q,join as Z}from"node:path";import{setDefaultResultOrder as oo}from"node:dns";import{setDefaultAutoSelectFamilyAttemptTimeout as eo}from"node:net";oo("ipv4first"),eo(4e3);const no=Q(B(import.meta.url)),so=JSON.parse(K(Z(no,"..","package.json"),"utf-8")).version,ro=chalk.cyan(" ─────────────────────────────────────────────────────"),to=`\n${ro}\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${so}`)}\n\n${ro}\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!==H){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===H);J(),process.exit(e??0)}}const io=new o;if(io.name("icoa").version(so).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=M();Y("high-contrast"===e.themeVariant?"high-contrast":"dark"),V(),function(){const o=X();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(to);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 W(io,s)}),e(io),n(io),s(io),r(io),t(io),i(io),l(io),a(io),m(io),c(io),g(io),d(io),p(io),y(io),h(io),f(io),u(io),w(io),b(io),j(io),$(io),v(io),T(io),A(io),I(io),E(io),C(io),_(io),x(io),S(io),k(io),L(io),O(io),R(io),F(io),P(io),N(io),U(io),G(io),io.command("model",{hidden:!0}).argument("[name]","model name to switch to").action(o=>{const e=M().geminiModel||"gemini-2.5-flash-lite";o?(D({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)))}io.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()}()}),io.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}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();
@@ -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"},{name:"env"},{name:"aienv"},{name:"python",subs:["setup","status"]},{name:"mujoco",subs:["setup","status"]},{name:"mojoco",mapsTo:["mujoco"],rest:!0},{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:"microduck"},{name:"battle",subs:["edit","syntax","play","replay","submit","status","bots"]},{name:"duck",subs:["edit","syntax","run","replay","submit","board","scenarios","status"]},{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, network/firewall, server"}},{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"},{name:"env"},{name:"aienv"},{name:"python",subs:["setup","status"]},{name:"mujoco",subs:["setup","status"]},{name:"mojoco",mapsTo:["mujoco"],rest:!0},{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:"microduck"},{name:"battle",subs:["edit","syntax","play","replay","submit","status","bots"]},{name:"duck",subs:["edit","syntax","run","replay","submit","board","scenarios","status"]},{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, network/firewall, server"}},{name:"upgrade",help:{order:20,section:"System",label:"upgrade",text:"Install the newest icoa and restart into it"}},{name:"update"},{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 +1 @@
1
- import chalk from"chalk";import{execSync as o}from"node:child_process";import{createHash as t}from"node:crypto";import{createWriteStream as e,existsSync as r,mkdirSync as n,mkdtempSync as s,renameSync as i,rmSync as a}from"node:fs";import{homedir as c}from"node:os";import{join as l}from"node:path";import{Readable as h,Transform as p}from"node:stream";import{pipeline as u}from"node:stream/promises";import{icoaStandalonePython as d,parsePyVersion as y,REQUIRED_PY as m,resolveHostPython as f,resolveRunPython as g}from"./aienv.js";import{httpFetch as $}from"./http.js";import{lazyJson as w}from"./lazy-assets.js";function b(t){try{return o(`${t} --version 2>&1`,{encoding:"utf-8",timeout:4e3}).trim()}catch{return null}}let v,P;export function probePython(o=!1,t={}){const e=t.hostOnly?P:v;if(e&&!o)return e;const r=c(),n=t.hostOnly?(()=>{const o=f(b,process.platform,r);return o?{...o,source:"host"}:null})():g(b,process.platform,r);if(n){const o=(b(n.cmd.includes(" ")?n.cmd:`"${n.cmd}"`)??"").replace(/^Python\s+/i,"")||`${n.version.major}.${n.version.minor}`,e=d(r,process.platform),s="aienv"===n.source?"aienv":n.cmd===e?"icoa":"system",i={ok:!0,version:o,cmd:n.cmd,source:s};return t.hostOnly?P=i:v=i,i}let s;for(const o of["python3","python"]){const t=b(o);if(y(t)){s=(t??"").replace(/^Python\s+/i,"").trim();break}}const i={ok:!1,otherVersion:s};return t.hostOnly?P=i:v=i,i}export function resetPythonProbe(){v=void 0,P=void 0}const x=`${m.major}.${m.minor}`;export function pythonDoorLine(o,t=!1,e){const r=e??probePython(),n=t?"":"🐍 ";if(r.ok){const t="aienv"===r.source?o?"aienv 环境":"aienv venv":"icoa"===r.source?o?"icoa 装的":"installed by icoa":o?"系统自带":"system";return chalk.gray(` ${n}Python ${r.version} · ${t} · `)+chalk.gray(o?"本机可跑代码":"code runs locally")}return r.otherVersion?chalk.gray(` ${n}Python ${r.otherVersion} — `)+chalk.gray(o?`课程按 ${x} 校准 · `:`courses are calibrated on ${x} · `)+chalk.bold.green("python setup")+chalk.gray(o?` 装并存的 ${x}(免 sudo,不动系统 python3)`:" adds one side-by-side (no sudo, system python3 untouched)"):chalk.gray(` ${n}`+(o?"没有 Python — 读卡答题不需要它 · ":"No Python — reading and answering cards works without it · "))+chalk.bold.green("python setup")+chalk.gray(o?" 让 /py 里能跑代码":" when you want to run code in /py")}export function selectionSetupLine(o,t=!1,e){const r=e??probePython(!1,{hostOnly:!0}),n=t?"":"🐍 ",s=chalk.gray(" (Paper C · SIMOC are MCQ-only — neither is needed)");return r.ok?o?chalk.gray(` ${n}Python ${r.version} · `)+chalk.green("exam toolkit ready"):chalk.gray(` ${n}Python ${r.version} · toolkit not installed · `)+chalk.bold.green("exam setup")+chalk.gray(" (13 packages, ~150 MB)")+s:chalk.gray(` ${n}`+(r.otherVersion?`Python ${r.otherVersion} — the exam runs on ${x} · `:`No Python ${x} yet · `))+chalk.bold.green("exam setup")+chalk.gray(" installs it (~20 s) plus the toolkit (~150 MB)")+s}export function printNoPython312(o,t={}){const e=probePython();console.log(chalk.yellow(o?e.otherVersion?` 这台机器只有 Python ${e.otherVersion} — 课程和判分机都在 ${x} 线上。`:` 找不到 Python ${x} — 不是你的代码有问题。`:e.otherVersion?` This machine has Python ${e.otherVersion} only — courses and the judge both run on the ${x} line.`:` Python ${x} not found — this is not your code.`)),console.log(chalk.gray(o?" 装一份并存的(免 sudo,二十秒,不动系统 python3): ":" Add one side-by-side (no sudo, ~20 s, system python3 untouched): ")+chalk.bold.cyan("python setup")),t.judgeIsServerSide&&console.log(chalk.gray(o?" 判分在服务器上跑,所以 /submit 照样能用 — 只是没法先本地自测。":" Judging is server-side — /submit still works, only self-check is off.")),console.log()}export function standaloneTriple(o=process.platform,t=""){const e=t.toLowerCase(),r="aarch64"===e||"arm64"===e,n="x86_64"===e||"amd64"===e||"x64"===e;return"darwin"===o?r?"aarch64-apple-darwin":n?"x86_64-apple-darwin":"":"win32"===o?r?"aarch64-pc-windows-msvc":n?"x86_64-pc-windows-msvc":"":r?"aarch64-unknown-linux-gnu":n?"x86_64-unknown-linux-gnu":""}export function pickStandaloneAsset(o,t){const e=`${m.major}\\.${m.minor}`,r=new RegExp(`^cpython-${e}\\.\\d+\\+\\d+-${t}-install_only_stripped\\.tar\\.gz$`),n=new RegExp(`^cpython-${e}\\.\\d+\\+\\d+-${t}-install_only\\.tar\\.gz$`);return o.find(o=>r.test(o.name))??o.find(o=>n.test(o.name))}export function isAllowedAssetUrl(o){return/^https:\/\/github\.com\/astral-sh\/python-build-standalone\/releases\/download\/[\w.%+-]+\/cpython-3\.12\.[\w.%+-]+\.tar\.gz$/.test(o)}const k=/^[0-9a-f]{64}$/;export function candidatesFromManifest(o,t){const e=o?.assets?.[t];if(!e||"string"!=typeof e.path||!function(o,t){const e=`${m.major}\\.${m.minor}`;return new RegExp(`^\\d{8}/cpython-${e}\\.\\d+\\+\\d+-${t}-install_only(_stripped)?\\.tar\\.gz$`).test(o)}(e.path,t))return[];const r="string"==typeof e.sha256&&k.test(e.sha256)?e.sha256:void 0,n=e.path.slice(e.path.indexOf("/")+1),s=[];for(const t of Array.isArray(o.mirrors)?o.mirrors:[]){if("string"!=typeof t||!/^https:\/\/[\w.-]+(\/[\w./%-]*)?$/.test(t))continue;const o=`${t.replace(/\/+$/,"")}/${e.path.replace(/\+/g,"%2B")}`;s.push({url:o,name:n,via:new URL(o).host,sha256:r})}return"string"==typeof e.github&&isAllowedAssetUrl(e.github)&&s.push({url:e.github,name:n,via:"github.com",sha256:r}),s}const _="win32"===process.platform?"tar.exe":"tar";async function j(o,r,n){const s=await $(o,{timeoutMs:3e4,retries:0,headers:{"User-Agent":"icoa-cli"}});if(!s.ok)throw new Error(`HTTP ${s.status}`);if(!s.body)throw new Error("empty body");const i=t("sha256"),a=new p({transform(o,t,e){i.update(o),e(null,o)}});if(await u(h.fromWeb(s.body),a,e(r)),n&&i.digest("hex")!==n)throw new Error("sha256 mismatch")}export async function provisionPython312(t={}){const e=c(),h=l(e,".icoa","python312"),p=d(e,process.platform),u=o=>{t.quiet||console.log(o)};if(!t.force&&r(p)){const o=b(`"${p}"`);if(o&&new RegExp(`^Python ${m.major}\\.${m.minor}\\.`).test(o))return u(chalk.green(` ✓ ${o}`)+chalk.gray(` → ${p}`)),resetPythonProbe(),!0}const y=function(){if("win32"===process.platform)return"arm64"===process.arch?"aarch64":"x86_64";try{return o("uname -m",{encoding:"utf-8"}).trim()}catch{return"arm64"===process.arch?"aarch64":"x86_64"}}(),f=standaloneTriple(process.platform,y);if(!f)return u(chalk.yellow(` ~ No prebuilt Python ${x} for '${y||"unknown"}' on this platform.`)),!1;if(!function(){try{return o(`${_} --version`,{stdio:"ignore",timeout:4e3}),!0}catch{return!1}}())return u(chalk.red(" ✗ `tar` is not installed")+chalk.gray(" — needed to unpack the download.")),"linux"===process.platform&&u(chalk.gray(" Install it: ")+chalk.cyan("sudo apt install -y tar")),!1;let g=candidatesFromManifest(await w("python312/manifest.json",8e3,{force:!0}),f);if(0===g.length)try{const o=await $("https://api.github.com/repos/astral-sh/python-build-standalone/releases/latest",{headers:{"User-Agent":"icoa-cli",Accept:"application/vnd.github+json"}});if(!o.ok)throw new Error(`GitHub API ${o.status}`);const t=pickStandaloneAsset((await o.json()).assets??[],f);if(!t)throw new Error(`no CPython ${x} build for ${f} in the latest release`);if(!isAllowedAssetUrl(t.browser_download_url))throw new Error("asset URL failed the allowlist check");g=[{url:t.browser_download_url,name:t.name,via:"github.com"}]}catch(o){return u(chalk.red(` ✗ Could not resolve a standalone Python ${x}`)+chalk.gray(` (mirror list unreachable · ${o.message})`)),!1}const v=g[0].name.match(/^cpython-(\d+\.\d+\.\d+)/)?.[1]??x;let P="";try{n(l(e,".icoa"),{recursive:!0}),P=s(l(e,".icoa",".py312-"));const t=l(P,"py.tgz"),r=[];let c=!1;for(const o of g){u(chalk.gray(` Fetching CPython ${v} for ${f} via ${o.via} …`));try{await j(o.url,t,o.sha256),c=!0;break}catch(t){const e=t.message.split("\n")[0].slice(0,80);r.push(`${o.via}: ${e}`),u(chalk.gray(` ~ ${o.via} did not work (${e}) — trying the next source`))}}if(!c)throw new Error(`no source reachable — ${r.join(" · ")}`);o(`${_} -xzf "${t}" -C "${P}"`,{stdio:["ignore","ignore","pipe"]}),a(h,{recursive:!0,force:!0}),i(l(P,"python"),h);const d=o(`"${p}" --version`,{encoding:"utf-8"}).trim();if(!new RegExp(`^Python ${m.major}\\.${m.minor}\\.`).test(d))throw new Error(`unexpected version: ${d}`);return resetPythonProbe(),u(chalk.green(` ✓ ${d} installed`)+chalk.gray(` → ${p} (system python3 untouched)`)),!0}catch(o){const t=String(o.stderr??o.message).trim().split("\n").pop();return u(chalk.red(` ✗ Python ${x} install failed`)+chalk.gray(t?` (${t})`:"")),!1}finally{P&&a(P,{recursive:!0,force:!0})}}
1
+ import chalk from"chalk";import{execSync as t}from"node:child_process";import{createHash as o}from"node:crypto";import{createWriteStream as e,existsSync as r,mkdirSync as n,mkdtempSync as s,renameSync as a,rmSync as i}from"node:fs";import{homedir as c}from"node:os";import{join as l}from"node:path";import{Readable as h,Transform as u}from"node:stream";import{pipeline as d}from"node:stream/promises";import{icoaStandalonePython as p,parsePyVersion as y,REQUIRED_PY as m,resolveHostPython as f,resolveRunPython as g}from"./aienv.js";import{httpFetch as $}from"./http.js";import{lazyJson as w}from"./lazy-assets.js";function b(o){try{return t(`${o} --version 2>&1`,{encoding:"utf-8",timeout:4e3}).trim()}catch{return null}}let v,x;export function probePython(t=!1,o={}){const e=o.hostOnly?x:v;if(e&&!t)return e;const r=c(),n=o.hostOnly?(()=>{const t=f(b,process.platform,r);return t?{...t,source:"host"}:null})():g(b,process.platform,r);if(n){const t=(b(n.cmd.includes(" ")?n.cmd:`"${n.cmd}"`)??"").replace(/^Python\s+/i,"")||`${n.version.major}.${n.version.minor}`,e=p(r,process.platform),s="aienv"===n.source?"aienv":n.cmd===e?"icoa":"system",a={ok:!0,version:t,cmd:n.cmd,source:s};return o.hostOnly?x=a:v=a,a}let s;for(const t of["python3","python"]){const o=b(t);if(y(o)){s=(o??"").replace(/^Python\s+/i,"").trim();break}}const a={ok:!1,otherVersion:s};return o.hostOnly?x=a:v=a,a}export function resetPythonProbe(){v=void 0,x=void 0}const P=`${m.major}.${m.minor}`;export function pythonDoorLine(t,o=!1,e){const r=e??probePython(),n=o?"":"🐍 ";if(r.ok){const o="aienv"===r.source?t?"aienv 环境":"aienv venv":"icoa"===r.source?t?"icoa 装的":"installed by icoa":t?"系统自带":"system";return chalk.gray(` ${n}Python ${r.version} · ${o} · `)+chalk.gray(t?"本机可跑代码":"code runs locally")}return r.otherVersion?chalk.gray(` ${n}Python ${r.otherVersion} — `)+chalk.gray(t?`课程按 ${P} 校准 · `:`courses are calibrated on ${P} · `)+chalk.bold.green("python setup")+chalk.gray(t?` 装并存的 ${P}(免 sudo,不动系统 python3)`:" adds one side-by-side (no sudo, system python3 untouched)"):chalk.gray(` ${n}`+(t?"没有 Python — 读卡答题不需要它 · ":"No Python — reading and answering cards works without it · "))+chalk.bold.green("python setup")+chalk.gray(t?" 让 /py 里能跑代码":" when you want to run code in /py")}export function selectionSetupLine(t,o=!1,e){const r=e??probePython(!1,{hostOnly:!0}),n=o?"":"🐍 ",s=chalk.gray(" (Paper C · SIMOC are MCQ-only — neither is needed)");return r.ok?t?chalk.gray(` ${n}Python ${r.version} · `)+chalk.green("exam toolkit ready"):chalk.gray(` ${n}Python ${r.version} · toolkit not installed · `)+chalk.bold.green("exam setup")+chalk.gray(" (13 packages, ~150 MB)")+s:chalk.gray(` ${n}`+(r.otherVersion?`Python ${r.otherVersion} — the exam runs on ${P} · `:`No Python ${P} yet · `))+chalk.bold.green("exam setup")+chalk.gray(" installs it (~20 s) plus the toolkit (~150 MB)")+s}export function printNoPython312(t,o={}){const e=probePython();console.log(chalk.yellow(t?e.otherVersion?` 这台机器只有 Python ${e.otherVersion} — 课程和判分机都在 ${P} 线上。`:` 找不到 Python ${P} — 不是你的代码有问题。`:e.otherVersion?` This machine has Python ${e.otherVersion} only — courses and the judge both run on the ${P} line.`:` Python ${P} not found — this is not your code.`)),console.log(chalk.gray(t?" 装一份并存的(免 sudo,二十秒,不动系统 python3): ":" Add one side-by-side (no sudo, ~20 s, system python3 untouched): ")+chalk.bold.cyan("python setup")),o.judgeIsServerSide&&console.log(chalk.gray(t?" 判分在服务器上跑,所以 /submit 照样能用 — 只是没法先本地自测。":" Judging is server-side — /submit still works, only self-check is off.")),console.log()}export function standaloneTriple(t=process.platform,o=""){const e=o.toLowerCase(),r="aarch64"===e||"arm64"===e,n="x86_64"===e||"amd64"===e||"x64"===e;return"darwin"===t?r?"aarch64-apple-darwin":n?"x86_64-apple-darwin":"":"win32"===t?r?"aarch64-pc-windows-msvc":n?"x86_64-pc-windows-msvc":"":r?"aarch64-unknown-linux-gnu":n?"x86_64-unknown-linux-gnu":""}export function pickStandaloneAsset(t,o){const e=`${m.major}\\.${m.minor}`,r=new RegExp(`^cpython-${e}\\.\\d+\\+\\d+-${o}-install_only_stripped\\.tar\\.gz$`),n=new RegExp(`^cpython-${e}\\.\\d+\\+\\d+-${o}-install_only\\.tar\\.gz$`);return t.find(t=>r.test(t.name))??t.find(t=>n.test(t.name))}export function isAllowedAssetUrl(t){return/^https:\/\/github\.com\/astral-sh\/python-build-standalone\/releases\/download\/[\w.%+-]+\/cpython-3\.12\.[\w.%+-]+\.tar\.gz$/.test(t)}const k=/^[0-9a-f]{64}$/;export function candidatesFromManifest(t,o){const e=t?.assets?.[o];if(!e||"string"!=typeof e.path||!function(t,o){const e=`${m.major}\\.${m.minor}`;return new RegExp(`^\\d{8}/cpython-${e}\\.\\d+\\+\\d+-${o}-install_only(_stripped)?\\.tar\\.gz$`).test(t)}(e.path,o))return[];const r="string"==typeof e.sha256&&k.test(e.sha256)?e.sha256:void 0,n=e.path.slice(e.path.indexOf("/")+1),s=[];for(const o of Array.isArray(t.mirrors)?t.mirrors:[]){if("string"!=typeof o||!/^https:\/\/[\w.-]+(\/[\w./%-]*)?$/.test(o))continue;const t=`${o.replace(/\/+$/,"")}/${e.path.replace(/\+/g,"%2B")}`;s.push({url:t,name:n,via:new URL(t).host,sha256:r})}return"string"==typeof e.github&&isAllowedAssetUrl(e.github)&&s.push({url:e.github,name:n,via:"github.com",sha256:r}),s}const A="win32"===process.platform?"tar.exe":"tar";async function _(t,r,n,s){const a=new AbortController;let i=Date.now();const c=setInterval(()=>{Date.now()-i>6e4&&a.abort(new Error("no data for 60 s"))},5e3);try{const c=await $(t,{timeoutMs:3e4,retries:0,signal:a.signal,headers:{"User-Agent":"icoa-cli"}});if(!c.ok)throw new Error(`HTTP ${c.status}`);if(!c.body)throw new Error("empty body");const l=Number(c.headers.get("content-length")??0),p=o("sha256");let y=0;const m=new u({transform(t,o,e){p.update(t),y+=t.length,i=Date.now(),s?.(y,l),e(null,t)}});if(await d(h.fromWeb(c.body),m,e(r)),n&&p.digest("hex")!==n)throw new Error("sha256 mismatch")}catch(t){const o=t.cause;if(a.signal.aborted&&o instanceof Error)throw o;throw t}finally{clearInterval(c)}}async function j(t){const o=new AbortController,e=setTimeout(()=>o.abort(),6e3),r=Date.now();let n=0;try{const e=await $(t,{timeoutMs:6e3,retries:0,signal:o.signal,headers:{"User-Agent":"icoa-cli",Range:"bytes=0-524287"}});if(!e.ok||!e.body)return 0;const r=e.body.getReader();for(;;){const{done:t,value:o}=await r.read();if(t)break;if(n+=o.length,n>=524288)break}}catch{}finally{clearTimeout(e)}const s=Math.max(1,Date.now()-r);return n>0?1e3*n/s:0}export function orderBySpeed(t,o){const e=t.filter(t=>"github.com"===t.via),r=t.filter(t=>"github.com"!==t.via);return[...r.filter(t=>(o.get(t.url)??0)>0).sort((t,e)=>(o.get(e.url)??0)-(o.get(t.url)??0)),...r.filter(t=>!((o.get(t.url)??0)>0)),...e]}function E(t){return`${(t/1e6).toFixed(t>=1e6?1:2)} MB/s`}export async function provisionPython312(o={}){const e=c(),h=l(e,".icoa","python312"),u=p(e,process.platform),d=t=>{o.quiet||console.log(t)};if(!o.force&&r(u)){const t=b(`"${u}"`);if(t&&new RegExp(`^Python ${m.major}\\.${m.minor}\\.`).test(t))return d(chalk.green(` ✓ ${t}`)+chalk.gray(` → ${u}`)),resetPythonProbe(),!0}const y=function(){if("win32"===process.platform)return"arm64"===process.arch?"aarch64":"x86_64";try{return t("uname -m",{encoding:"utf-8"}).trim()}catch{return"arm64"===process.arch?"aarch64":"x86_64"}}(),f=standaloneTriple(process.platform,y);if(!f)return d(chalk.yellow(` ~ No prebuilt Python ${P} for '${y||"unknown"}' on this platform.`)),!1;if(!function(){try{return t(`${A} --version`,{stdio:"ignore",timeout:4e3}),!0}catch{return!1}}())return d(chalk.red(" ✗ `tar` is not installed")+chalk.gray(" — needed to unpack the download.")),"linux"===process.platform&&d(chalk.gray(" Install it: ")+chalk.cyan("sudo apt install -y tar")),!1;let g=candidatesFromManifest(await w("python312/manifest.json",8e3,{force:!0}),f);if(0===g.length)try{const t=await $("https://api.github.com/repos/astral-sh/python-build-standalone/releases/latest",{headers:{"User-Agent":"icoa-cli",Accept:"application/vnd.github+json"}});if(!t.ok)throw new Error(`GitHub API ${t.status}`);const o=pickStandaloneAsset((await t.json()).assets??[],f);if(!o)throw new Error(`no CPython ${P} build for ${f} in the latest release`);if(!isAllowedAssetUrl(o.browser_download_url))throw new Error("asset URL failed the allowlist check");g=[{url:o.browser_download_url,name:o.name,via:"github.com"}]}catch(t){return d(chalk.red(` ✗ Could not resolve a standalone Python ${P}`)+chalk.gray(` (mirror list unreachable · ${t.message})`)),!1}const v=g[0].name.match(/^cpython-(\d+\.\d+\.\d+)/)?.[1]??P,x=g.filter(t=>"github.com"!==t.via);if(x.length>=2){d(chalk.gray(` Testing ${x.length} download sources …`));const t=await Promise.all(x.map(async t=>[t.url,await j(t.url)])),o=new Map(t);g=orderBySpeed(g,o);const e=g[0],r=o.get(e.url)??0;d(r>0?chalk.gray(` Fastest: ${e.via} (${E(r)})`):chalk.gray(" No source answered the speed test — trying each in turn."))}const k=!o.quiet&&!0===process.stdout.isTTY;let M=0,C=0;const T=(t,o)=>{if(!k)return;const e=Date.now();if(e-M<500&&t<o)return;M=e;const r=t/Math.max(1,e-C)*1e3,n=o?` / ${(o/1e6).toFixed(1)}`:"";process.stdout.write(chalk.gray(`\r ↓ ${(t/1e6).toFixed(1)}${n} MB · ${E(r)} `))},R=()=>{k&&M&&process.stdout.write("\r"+" ".repeat(60)+"\r"),M=0};let S="";try{n(l(e,".icoa"),{recursive:!0}),S=s(l(e,".icoa",".py312-"));const o=l(S,"py.tgz"),r=[];let c=!1;for(const t of g){d(chalk.gray(` Fetching CPython ${v} for ${f} via ${t.via} …`)),C=Date.now();try{await _(t.url,o,t.sha256,T),R(),c=!0;break}catch(o){R();const e=o.message.split("\n")[0].slice(0,80);r.push(`${t.via}: ${e}`),d(chalk.gray(` ~ ${t.via} did not work (${e}) — trying the next source`))}}if(!c)throw new Error(`no source reachable — ${r.join(" · ")}`);t(`${A} -xzf "${o}" -C "${S}"`,{stdio:["ignore","ignore","pipe"]}),i(h,{recursive:!0,force:!0}),a(l(S,"python"),h);const p=t(`"${u}" --version`,{encoding:"utf-8"}).trim();if(!new RegExp(`^Python ${m.major}\\.${m.minor}\\.`).test(p))throw new Error(`unexpected version: ${p}`);return resetPythonProbe(),d(chalk.green(` ✓ ${p} installed`)+chalk.gray(` → ${u} (system python3 untouched)`)),!0}catch(t){const o=String(t.stderr??t.message).trim().split("\n").pop();return d(chalk.red(` ✗ Python ${P} install failed`)+chalk.gray(o?` (${o})`:"")),!1}finally{S&&i(S,{recursive:!0,force:!0})}}
@@ -1 +1 @@
1
- import{readFileSync as o,writeFileSync as t,existsSync as e}from"node:fs";import{join as n,dirname as s,sep as l}from"node:path";import{fileURLToPath as r}from"node:url";import{homedir as i,platform as a}from"node:os";import chalk from"chalk";import{getIcoaDir as c}from"./config.js";const m=s(r(import.meta.url)),p=m,u=p.startsWith(i()+l)||p.includes(`${l}.nvm${l}`)||p.includes(`${l}.fnm${l}`)||p.includes(`${l}.volta${l}`),g="win32"===a()||"darwin"===a()||u?"npm install -g icoa-cli@latest":"sudo npm install -g icoa-cli@latest";function f(o,t){const e=function(o,t){const e=o.split(".").map(Number),n=t.split(".").map(Number);return e[0]!==n[0]||e[1]!==n[1]?-1:(e[2]??0)-(n[2]??0)}(t,o),n=e>0?chalk.gray(` (${e} version${1===e?"":"s"} behind)`):"",s=chalk.yellow(" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");console.log(),console.log(s),console.log(chalk.bold.yellow(" ⬆ New version available!")),console.log(),console.log(chalk.white(" Current: ")+chalk.gray(`v${o}`)),console.log(chalk.white(" Latest: ")+chalk.bold.green(`v${t}`)+n),console.log(),console.log(chalk.white(" Update: ")+chalk.bold.cyan(g));const l="darwin"===a()?"Cmd+C":"win32"===a()?"Ctrl+C":"Ctrl+Shift+C";console.log(chalk.gray(` ↑ select & ${l} to copy — typos break it`)),console.log(s),console.log()}function d(o,t){const e=o.split(".").map(Number),n=t.split(".").map(Number);for(let o=0;o<Math.max(e.length,n.length);o++){const t=e[o]??0,s=n[o]??0;if(t>s)return!0;if(t<s)return!1}return!1}export function checkForUpdates(){setTimeout(async()=>{try{const s=n(m,"..","..","package.json"),l=JSON.parse(o(s,"utf-8")).version,r=n(c(),"update-check.json");if(e(r))try{const t=JSON.parse(o(r,"utf-8"));if(Date.now()-t.lastCheck<216e5)return void(t.latestVersion&&d(t.latestVersion,l)&&f(l,t.latestVersion))}catch{}const i=new AbortController,a=setTimeout(()=>i.abort(),5e3),p=await fetch("https://registry.npmjs.org/icoa-cli/latest",{signal:i.signal,headers:{Accept:"application/json"}});if(clearTimeout(a),!p.ok)return;const u=(await p.json()).version;if(!u)return;const g={lastCheck:Date.now(),latestVersion:u};t(r,JSON.stringify(g,null,2)),d(u,l)&&f(l,u)}catch{}},0)}
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 p}from"node:os";import{spawnSync as d}from"node:child_process";import chalk from"chalk";import{getIcoaDir as y}from"./config.js";const f=a(u(import.meta.url)),m=i(f,"..",".."),h=f,v=h.startsWith(g()+c)||h.includes(`${c}.nvm${c}`)||h.includes(`${c}.fnm${c}`)||h.includes(`${c}.volta${c}`),w="win32"===p()||"darwin"===p()||v?"":"sudo ",$=e=>`${w}npm install -g icoa-cli@${e}`,k=["https://registry.npmjs.org","https://registry.npmmirror.com"],b=36e5;function C(){return JSON.parse(e(i(m,"package.json"),"utf-8")).version}function x(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 O(){return i(y(),"update-check.json")}function A(e){try{const n={lastCheck:Date.now(),latestVersion:e.version,registries:e.registries};o(O(),JSON.stringify(n,null,2))}catch{}}async function S(e){const o=await Promise.all(k.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&&!x(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)}}async function N(o){const t=function(){try{return n(O())?JSON.parse(e(O(),"utf-8")):null}catch{return null}}();if(t?.latestVersion&&Date.now()-t.lastCheck<o.maxAgeMs)return{version:t.latestVersion,registries:t.registries?.length?t.registries:k};const r=await S(o.timeoutMs);return r&&A(r),r}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($(o)));else{console.log(chalk.white(" Update: ")+chalk.bold.cyan($(o)));const e="darwin"===p()?"Cmd+C":"win32"===p()?"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=C(),o=await N({maxAgeMs:b,timeoutMs:5e3});o&&x(o.version,e)&&T(e,o.version)}catch{}},0)}let _=null;function E(){return _||(_=(()=>{if("win32"===p())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!==d("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 M(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($(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=C(),n=e.ask?await N({maxAgeMs:b,timeoutMs:2e3}):await S(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||A(n),!x(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):M(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{confirm:e}=await import("@inquirer/prompts");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){console.log(chalk.gray(` ⬇ icoa-cli@${o} from ${t.replace(/^https:\/\//,"")} …`));const n=[e.npm[1],"install","-g",`icoa-cli@${o}`,`--prefix=${e.prefix}`,`--registry=${t}/`,"--prefer-online","--no-fund","--no-progress","--no-audit","--loglevel=error","--fetch-timeout=30000","--fetch-retries=1"],r=e.needsSudo?d("sudo",[e.npm[0],...n],{stdio:"inherit"}):d(e.npm[0],n,{stdio:"inherit"});let s="";try{s=C()}catch{}if(s===o)return!0;console.log(chalk.yellow(" ⚠ that source did not work")+chalk.gray(0===r.status?` (npm finished but icoa is still v${s||"?"})`:` (npm exit ${r.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}.`)),M(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}