icoa-cli 2.19.623 → 2.19.625

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.
@@ -1 +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))})}
1
+ import chalk from"chalk";import{runUpgrade as o}from"../lib/update-check.js";import{returnToMainMenu as r}from"../lib/menu-nav.js";import{getMainRl as a}from"../lib/main-rl.js";export function registerUpgradeCommand(i){i.command("upgrade").alias("update").description("Upgrade icoa to the newest version and restart into it").action(async()=>{if("upgraded"===await o({ask:!1}))return"1"===process.env.ICOA_SUPERVISED?(console.log(chalk.gray(" restarting icoa …")),void r(a()??void 0)):void console.log(chalk.gray(" run ")+chalk.cyan("icoa")+chalk.gray(" to start it."))})}
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,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();
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,printUpdateHint as B}from"./lib/update-check.js";import{detectIcoaInstalls as Q,isWindowsBuildInWslShell as Z}from"./lib/platform.js";import{ICOA_BIG as oo}from"./lib/banner.js";import{readFileSync as eo}from"node:fs";import{fileURLToPath as no}from"node:url";import{dirname as so,join as ro}from"node:path";import{setDefaultResultOrder as to}from"node:dns";import{setDefaultAutoSelectFamilyAttemptTimeout as io}from"node:net";to("ipv4first"),io(4e3);const lo=so(no(import.meta.url)),ao=JSON.parse(eo(ro(lo,"..","package.json"),"utf-8")).version,mo=chalk.cyan(" ─────────────────────────────────────────────────────"),co=`\n${mo}\n\n${oo.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${ao}`)}\n\n${mo}\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 go=new o;if(go.name("icoa").version(ao).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=Q();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(co),s?"upgraded"===await q({ask:!0})&&(console.log(chalk.gray(" restarting icoa …")),process.exit(Y)):B(),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(go,n)}),e(go),n(go),s(go),r(go),t(go),i(go),l(go),a(go),m(go),c(go),g(go),d(go),p(go),y(go),h(go),f(go),u(go),w(go),b(go),j(go),v(go),$(go),T(go),A(go),I(go),E(go),C(go),x(go),_(go),k(go),S(go),L(go),R(go),F(go),O(go),N(go),M(go),U(go),P(go),W(go),go.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)))}go.hook("preAction",()=>{!function(){if(!Z())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()}()}),go.parse();
@@ -1 +1 @@
1
- import{existsSync as e,readFileSync as t,writeFileSync as s}from"node:fs";import{join as o}from"node:path";import chalk from"chalk";import r from"string-width";import{getIcoaDir as n}from"./config.js";import{detectCaps as a}from"./render-card.js";import{fetchLearnProfile as i}from"./learn-profile.js";const c=()=>o(n(),"profile-cache.json");export async function fetchProfileCached(o){const r=o.toUpperCase();let n={};try{if(e(c())){const e=JSON.parse(t(c(),"utf8"));e&&"object"==typeof e&&!("token"in e)&&(n=e);const s=n[r];if(s&&"number"==typeof s.ts&&Date.now()-s.ts<6e5&&s.profile)return s.profile}}catch{}const a=await i(r,"me");if("ok"!==a.kind)return null;try{n[r]={ts:Date.now(),profile:a.profile},s(c(),JSON.stringify(n))}catch{}return a.profile}export async function fetchProfilesForLocalTokens(e){const t=new Set,s=[];for(const o of e){const e=o.toUpperCase();if(t.has(e)||!/^[A-Z]{2}[A-Z0-9]{8}$/.test(e)||/DEMO01$/.test(e))continue;if(t.add(e),t.size>6)break;const r=await fetchProfileCached(e);r&&s.push(r)}return s}export function dropProfileCache(){try{s(c(),"{}")}catch{}}function u(e){const t=Math.max(48,Math.min(e,72)),s="T0"===a().tier,o=(s?"=":"━").repeat(Math.min(34,t-8)),n=Math.min(28,t-28);return{W:t,ascii:s,rule:o,center:e=>" ".repeat(Math.max(0,Math.floor((t-r(e))/2)))+e,row:(e,t)=>chalk.gray(` ${e} ${".".repeat(Math.max(2,n-r(e)))} `)+t}}export function doorStoryLines(e,t,s){const{ascii:o,rule:r,center:n,row:a}=u(t),i=(e,t)=>o?t:e,c=[];c.push(chalk.cyan(n(r))),c.push(chalk.gray(n("your story so far"))),c.push(chalk.cyan(n(r)));const p=[];e.streak_days>0?p.push(`${i("🔥","streak")} ${e.streak_days}-day streak`):p.push("one card today starts a streak"),e.active_days>0&&p.push(`${e.active_days} active days`),e.linked>0&&p.push(`${i("🔗","linked")} ${e.linked+1} tokens`),c.push(a("Tonight",chalk.white(p.join(" · "))));for(const t of e.ladders){let e=!1;const s=t.steps.map(t=>"done"===t.state?(e=!0,chalk.green(`${t.curriculum_id} ${i("🏁","done")}`)):"in"===t.state?(e=!0,chalk.bold.white(`${t.curriculum_id} ${t.cards_done}/${t.total_cards}`)):e?chalk.gray(`${t.curriculum_id} (next)`):chalk.gray(t.curriculum_id)),o=t.steps.findIndex(e=>"done"===e.state||"in"===e.state),r=t.steps.map(e=>"done"===e.state||"in"===e.state).lastIndexOf(!0),n=Math.max(0,o),u=Math.min(s.length-1,r+1);c.push(a(t.name,s.slice(n,u+1).join(chalk.gray(" → "))))}const h=e.tokens.reduce((e,t)=>e+t.contests.passed,0),l=e.tokens.reduce((e,t)=>e+t.contests.total,0),d=e.tokens.reduce((e,t)=>e+t.contests.carried,0);l>0&&c.push(a("Checkpoints",chalk.white(`${h} / ${l} passed`)+(d>0?chalk.gray(` · ${d} ${i("✅↗","carried")}`):"")));const f=e.medals;if(f.gold+f.silver+f.bronze>0&&c.push(a("Medals",chalk.white(o?`${f.gold} gold · ${f.silver} silver · ${f.bronze} bronze`:`${f.gold} 🥇 ${f.silver} 🥈 ${f.bronze} 🥉`))),e.focus.sessions>0){const t=Math.floor(e.focus.total_min/60),s=e.focus.total_min%60;c.push(a("Focus",chalk.white(`${e.focus.sessions} sessions · ${t>0?`${t}h `:""}${s}m`)))}e.replies_unread>0&&c.push(a("Replies",chalk.bold.cyan(`${e.replies_unread} new`)+chalk.gray(" · open a course and type r")));const y=e.tokens.map(e=>e.last_seen).filter(Boolean).sort().pop()??null;return y&&c.push(a("Last seen",chalk.white(function(e){if(!e)return"";const t=new Date(e);if(Number.isNaN(t.getTime()))return"";const s=e=>new Date(e.getFullYear(),e.getMonth(),e.getDate()).getTime(),o=Math.round((s(new Date)-s(t))/864e5);return o<=0?"today":1===o?"yesterday":o<30?`${o} days ago`:"a while back"}(y)))),c.push(chalk.cyan(n(r))),c.push(...doorMenuLines(e,s)),c}export function doorMenuLines(e,t){const s="T0"===a().tier,o=t??e?.tokens.filter(e=>e.credits?.completed).length??0,r=(e,t,o)=>chalk.white(" "+(s?" ":`${e} `))+chalk.bold.green(t.padEnd(14))+chalk.gray(o),n=[];return n.push(r("📖","story",o?`all your stories · ${o} finished · story <n> for one course`:"all your course stories · story <n> for one course")),n.push(r("👤","me","profile · ladders · medals · replies")),n.push(r("⏱","focus 30","a focus session — any course you open counts")),n.push(r("⏱","focus log",e&&e.focus.sessions>0?`your focus sessions · ${e.focus.sessions} so far · report per session`:"your focus sessions — the report of each one, newest first")),n.push(r("📜","certificate",o?`${o} track${1===o?"":"s"} finished — certificates coming`:"none yet — finish a track first")),n.push(chalk.gray(" ")+chalk.bold.green("c")+chalk.gray(" continue · paste a token · Enter to go back")),n}export function certificateLines(e,t){const s="T0"===a().tier,o=s?"":"📜 ",r=e?.tokens.filter(e=>e.credits?.completed)??[],n=[];if(0===r.length)n.push(chalk.bold.white(` ${o}`+(t?"还没有证书。":"No certificate yet."))),n.push(chalk.gray(t?" 证书随通关而来 — 自测全答、练习全 AC、检查点全过线。":" Certificates come with finished tracks — every check answered, every practice AC, every checkpoint passed.")),n.push(chalk.gray(" ")+chalk.bold.green("story")+chalk.gray(t?" 看每门课离通关还差什么。":" shows how far each course is."));else{n.push(chalk.bold.white(` ${o}`+(t?`已通关 ${r.length} 门 — 证书 PDF 制作中,记录已永久保存。`:`${r.length} track${1===r.length?"":"s"} finished — certificate PDFs are coming; your record is already saved.`)));for(const e of r){const t=e.credits?.completed_at?new Date(e.credits.completed_at).toLocaleDateString("en-AU",{day:"numeric",month:"short",year:"numeric"}):"";n.push(chalk.gray(" ")+chalk.green(s?"[done] ":"🏁 ")+chalk.white(e.curriculum_id)+chalk.gray(t?` · ${t}`:"")+chalk.gray(` · story ${e.curriculum_id}`))}}return n}export function doorWelcomeLines(e,t=!1){const{ascii:s,rule:o,center:r}=u(e),n=[];n.push(chalk.cyan(r(o)));const a=s?">":"▶",i=s?"-":"─";return n.push(chalk.gray(r(`1970 ${i.repeat(3)} algorithms ${i.repeat(8)} 2022 ${i.repeat(3)} AI agents ${i.repeat(3)} you ${a}`))),n.push(chalk.gray(r('"learn to code" "learn to command" '))),n.push(chalk.cyan(r(o))),t?n.push(chalk.gray(" Offline right now — paste your token to continue where you left off.")):(n.push(chalk.gray(" Paste your team token to begin — then these open up:")),n.push(...doorMenuLines(null).slice(0,4))),n}
1
+ import{existsSync as e,readFileSync as t,writeFileSync as s}from"node:fs";import{join as o}from"node:path";import chalk from"chalk";import r from"string-width";import{getIcoaDir as n}from"./config.js";import{detectCaps as a}from"./render-card.js";import{fetchLearnProfile as i}from"./learn-profile.js";const c=()=>o(n(),"profile-cache.json");function u(){try{if(e(c())){const e=JSON.parse(t(c(),"utf8"));if(e&&"object"==typeof e&&!("token"in e))return e}}catch{}return{}}async function p(e){const t=await Promise.all(e.map(e=>i(e,"me"))),o=new Map;if(t.forEach((t,s)=>{"ok"===t.kind&&o.set(e[s],t.profile)}),o.size){const e=u();for(const[t,s]of o)e[t]={ts:Date.now(),profile:s};try{s(c(),JSON.stringify(e))}catch{}}return o}async function l(e,t={}){const s=u(),o=new Map,r=[];for(const n of e){const e=s[n],a=!!e?.profile&&"number"==typeof e.ts&&Date.now()-e.ts<6e5;e?.profile&&(a||t.staleOk)&&o.set(n,e.profile),a||r.push(n)}if(!r.length)return o;if(t.staleOk&&e.length&&o.has(e[0]))return p(r).catch(()=>{}),o;for(const[e,t]of await p(r))o.set(e,t);return o}export async function fetchProfileCached(e){const t=e.toUpperCase();return(await l([t])).get(t)??null}export async function fetchProfilesForLocalTokens(e,t={}){const s=new Set;for(const t of e){const e=t.toUpperCase();if(!s.has(e)&&/^[A-Z]{2}[A-Z0-9]{8}$/.test(e)&&!/DEMO01$/.test(e)&&(s.add(e),s.size>=6))break}const o=[...s],r=await l(o,t);return o.map(e=>r.get(e)).filter(e=>!!e)}export function dropProfileCache(){try{s(c(),"{}")}catch{}}function f(e){const t=Math.max(48,Math.min(e,72)),s="T0"===a().tier,o=(s?"=":"━").repeat(Math.min(34,t-8)),n=Math.min(28,t-28);return{W:t,ascii:s,rule:o,center:e=>" ".repeat(Math.max(0,Math.floor((t-r(e))/2)))+e,row:(e,t)=>chalk.gray(` ${e} ${".".repeat(Math.max(2,n-r(e)))} `)+t}}export function doorStoryLines(e,t,s){const{ascii:o,rule:r,center:n,row:a}=f(t),i=(e,t)=>o?t:e,c=[];c.push(chalk.cyan(n(r))),c.push(chalk.gray(n("your story so far"))),c.push(chalk.cyan(n(r)));const u=[];e.streak_days>0?u.push(`${i("🔥","streak")} ${e.streak_days}-day streak`):u.push("one card today starts a streak"),e.active_days>0&&u.push(`${e.active_days} active days`),e.linked>0&&u.push(`${i("🔗","linked")} ${e.linked+1} tokens`),c.push(a("Tonight",chalk.white(u.join(" · "))));for(const t of e.ladders){let e=!1;const s=t.steps.map(t=>"done"===t.state?(e=!0,chalk.green(`${t.curriculum_id} ${i("🏁","done")}`)):"in"===t.state?(e=!0,chalk.bold.white(`${t.curriculum_id} ${t.cards_done}/${t.total_cards}`)):e?chalk.gray(`${t.curriculum_id} (next)`):chalk.gray(t.curriculum_id)),o=t.steps.findIndex(e=>"done"===e.state||"in"===e.state),r=t.steps.map(e=>"done"===e.state||"in"===e.state).lastIndexOf(!0),n=Math.max(0,o),u=Math.min(s.length-1,r+1);c.push(a(t.name,s.slice(n,u+1).join(chalk.gray(" → "))))}const p=e.tokens.reduce((e,t)=>e+t.contests.passed,0),l=e.tokens.reduce((e,t)=>e+t.contests.total,0),h=e.tokens.reduce((e,t)=>e+t.contests.carried,0);l>0&&c.push(a("Checkpoints",chalk.white(`${p} / ${l} passed`)+(h>0?chalk.gray(` · ${h} ${i("✅↗","carried")}`):"")));const d=e.medals;if(d.gold+d.silver+d.bronze>0&&c.push(a("Medals",chalk.white(o?`${d.gold} gold · ${d.silver} silver · ${d.bronze} bronze`:`${d.gold} 🥇 ${d.silver} 🥈 ${d.bronze} 🥉`))),e.focus.sessions>0){const t=Math.floor(e.focus.total_min/60),s=e.focus.total_min%60;c.push(a("Focus",chalk.white(`${e.focus.sessions} sessions · ${t>0?`${t}h `:""}${s}m`)))}e.replies_unread>0&&c.push(a("Replies",chalk.bold.cyan(`${e.replies_unread} new`)+chalk.gray(" · open a course and type r")));const y=e.tokens.map(e=>e.last_seen).filter(Boolean).sort().pop()??null;return y&&c.push(a("Last seen",chalk.white(function(e){if(!e)return"";const t=new Date(e);if(Number.isNaN(t.getTime()))return"";const s=e=>new Date(e.getFullYear(),e.getMonth(),e.getDate()).getTime(),o=Math.round((s(new Date)-s(t))/864e5);return o<=0?"today":1===o?"yesterday":o<30?`${o} days ago`:"a while back"}(y)))),c.push(chalk.cyan(n(r))),c.push(...doorMenuLines(e,s)),c}export function doorMenuLines(e,t){const s="T0"===a().tier,o=t??e?.tokens.filter(e=>e.credits?.completed).length??0,r=(e,t,o)=>chalk.white(" "+(s?" ":`${e} `))+chalk.bold.green(t.padEnd(14))+chalk.gray(o),n=[];return n.push(r("📖","story",o?`all your stories · ${o} finished · story <n> for one course`:"all your course stories · story <n> for one course")),n.push(r("👤","me","profile · ladders · medals · replies")),n.push(r("⏱","focus 30","a focus session — any course you open counts")),n.push(r("⏱","focus log",e&&e.focus.sessions>0?`your focus sessions · ${e.focus.sessions} so far · report per session`:"your focus sessions — the report of each one, newest first")),n.push(r("📜","certificate",o?`${o} track${1===o?"":"s"} finished — certificates coming`:"none yet — finish a track first")),n.push(chalk.gray(" ")+chalk.bold.green("c")+chalk.gray(" continue · paste a token · Enter to go back")),n}export function certificateLines(e,t){const s="T0"===a().tier,o=s?"":"📜 ",r=e?.tokens.filter(e=>e.credits?.completed)??[],n=[];if(0===r.length)n.push(chalk.bold.white(` ${o}`+(t?"还没有证书。":"No certificate yet."))),n.push(chalk.gray(t?" 证书随通关而来 — 自测全答、练习全 AC、检查点全过线。":" Certificates come with finished tracks — every check answered, every practice AC, every checkpoint passed.")),n.push(chalk.gray(" ")+chalk.bold.green("story")+chalk.gray(t?" 看每门课离通关还差什么。":" shows how far each course is."));else{n.push(chalk.bold.white(` ${o}`+(t?`已通关 ${r.length} 门 — 证书 PDF 制作中,记录已永久保存。`:`${r.length} track${1===r.length?"":"s"} finished — certificate PDFs are coming; your record is already saved.`)));for(const e of r){const t=e.credits?.completed_at?new Date(e.credits.completed_at).toLocaleDateString("en-AU",{day:"numeric",month:"short",year:"numeric"}):"";n.push(chalk.gray(" ")+chalk.green(s?"[done] ":"🏁 ")+chalk.white(e.curriculum_id)+chalk.gray(t?` · ${t}`:"")+chalk.gray(` · story ${e.curriculum_id}`))}}return n}export function doorWelcomeLines(e,t=!1){const{ascii:s,rule:o,center:r}=f(e),n=[];n.push(chalk.cyan(r(o)));const a=s?">":"▶",i=s?"-":"─";return n.push(chalk.gray(r(`1970 ${i.repeat(3)} algorithms ${i.repeat(8)} 2022 ${i.repeat(3)} AI agents ${i.repeat(3)} you ${a}`))),n.push(chalk.gray(r('"learn to code" "learn to command" '))),n.push(chalk.cyan(r(o))),t?n.push(chalk.gray(" Offline right now — paste your token to continue where you left off.")):(n.push(chalk.gray(" Paste your team token to begin — then these open up:")),n.push(...doorMenuLines(null).slice(0,4))),n}
@@ -1 +1 @@
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}
1
+ import{readFileSync as e,writeFileSync as o,existsSync as t,accessSync as n,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 f}from"./config.js";const m=a(u(import.meta.url)),y=i(m,"..",".."),h=m,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 ",k=(e,o)=>`${o??""!==w?"sudo ":""}npm install -g icoa-cli@${e}`,$=["https://registry.npmjs.org","https://registry.npmmirror.com"];function b(){return JSON.parse(e(i(y,"package.json"),"utf-8")).version}function C(e,o){const t=e.split(".").map(Number),n=o.split(".").map(Number);for(let e=0;e<Math.max(t.length,n.length);e++){const o=t[e]??0,r=n[e]??0;if(o>r)return!0;if(o<r)return!1}return!1}function x(){return i(f(),"update-check.json")}function E(){try{return t(x())?JSON.parse(e(x(),"utf-8")):null}catch{return null}}function T(e){try{const t={lastCheck:Date.now(),latestVersion:e.version,registries:e.registries};o(x(),JSON.stringify(t,null,2))}catch{}}async function A(e){const o=await Promise.all($.map(async o=>{const t=Date.now(),n=new AbortController,r=setTimeout(()=>n.abort(),e);try{const e=await fetch(`${o}/icoa-cli/latest`,{signal:n.signal,headers:{Accept:"application/json"}});return e.ok?{reg:o,version:(await e.json()).version??null,ms:Date.now()-t}:{reg:o,version:null,ms:1/0}}catch{return{reg:o,version:null,ms:1/0}}finally{clearTimeout(r)}}));let t=null;for(const e of o)!e.version||t&&!C(e.version,t)||(t=e.version);if(!t)return null;const n=[...o].sort((e,o)=>(e.version===t?0:1)-(o.version===t?0:1)||e.ms-o.ms);return{version:t,registries:n.map(e=>e.reg)}}function N(){const e=E();return e?.latestVersion?{version:e.latestVersion,registries:e.registries?.length?e.registries:$}:null}let O=null;export function startBootCheck(){O??=A(2e3).then(e=>(e&&T(e),e??N()))}export function checkForUpdates(){setTimeout(async()=>{try{await async function(e){const o=E();if(o?.latestVersion&&Date.now()-o.lastCheck<e.maxAgeMs)return{version:o.latestVersion,registries:o.registries?.length?o.registries:$};const t=await A(e.timeoutMs);return t&&T(t),t}({maxAgeMs:6e5,timeoutMs:5e3})}catch{}},0)}export function printUpdateHint(){try{const e=b(),o=N();if(!o||!C(o.version,e))return;console.log(chalk.yellow(` ⬆ v${o.version} is out`)+chalk.gray(` (you have v${e}) — type `)+(U().ok?chalk.bold.cyan("upgrade"):chalk.cyan(k(o.version)))),console.log()}catch{}}let S=null;function U(){return S||(S=(()=>{if("win32"===p())return{ok:!1,reason:"native Windows install"};let e;try{e=r(y)}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 t=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 r of[e,o,i(t,"bin")])try{n(r,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:t,needsSudo:u}})(),S)}function _(e){return e.split("\n").map(e=>e.replace(/^npm (error|ERR!)\s*/,"").trim()).filter(e=>e&&!/^at |^\{|^\}|^A complete log|^errno|^syscall|^path|^dest|^code:/.test(e)).slice(0,3)}function M(e,o,t,n){console.log(chalk.yellow(" ⚠ icoa cannot upgrade itself here")+chalk.gray(` — ${t}.`)),console.log(chalk.white(" Run this in your shell instead: ")+chalk.bold.cyan(k(o,n))),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 t=e.ask?await O:await A(8e3);if(!t)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||T(t),!C(t.version,o))return e.ask||console.log(chalk.green(` ✅ icoa v${o} is the latest version.`)),"current";const n=U();if(!n.ok)return e.ask?function(e,o){const t=function(e,o){const t=e.split(".").map(Number),n=o.split(".").map(Number);return t[0]!==n[0]||t[1]!==n[1]?-1:(t[2]??0)-(n[2]??0)}(o,e),n=t>0?chalk.gray(` (${t} version${1===t?"":"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}`)+n),console.log(),U().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"===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()}(o,t.version):M(o,t.version,n.reason),"skipped";if(e.ask){console.log(),console.log(chalk.bold.yellow(" ⬆ New version: ")+chalk.gray(`v${o}`)+chalk.white(" → ")+chalk.bold.green(`v${t.version}`));const{default:e}=await import("@inquirer/confirm");let n=!0;try{n=await e({message:"Upgrade now? (Enter = yes, n = later)",default:!0})}catch{n=!1}if(!n)return console.log(chalk.gray(" Later — type ")+chalk.cyan("upgrade")+chalk.gray(" at icoa> any time.")),"skipped"}const r=function(e,o,t){const n=process.stdin;try{n.isTTY&&n.setRawMode&&n.setRawMode(!1)}catch{}const r=()=>"win32"!==p()&&!0===process.stdin.isTTY&&0===d("sudo",["-V"],{stdio:"ignore"}).status;let s=e.needsSudo,i=e.needsSudo;const a=()=>console.log(chalk.gray(" icoa is installed with admin rights — sudo will ask for your computer password."));s&&a();for(const n of t){let t=!1;for(;;){console.log(chalk.gray(` ⬇ icoa-cli@${o} from ${n.replace(/^https:\/\//,"")}${t?" (refreshing)":""} …`));const l=[e.npm[1],"install","-g",`icoa-cli@${o}`,`--prefix=${e.prefix}`,`--registry=${n}/`,...t?["--prefer-online"]:[],"--no-fund","--no-progress","--no-audit","--loglevel=error","--fetch-timeout=30000","--fetch-retries=1"],c={stdio:["inherit","inherit","pipe"],encoding:"utf-8"},u=s?d("sudo",[e.npm[0],...l],c):d(e.npm[0],l,c);let g="";try{g=b()}catch{}if(g===o)return{ok:!0,perm:i};const p=u.stderr||"";for(const e of _(p))console.log(chalk.gray(` ${e}`));if(s&&/^sudo: |incorrect password|not in the sudoers/m.test(p))return{ok:!1,perm:!0};if(/\b(EACCES|EPERM)\b/.test(p)){if(i=!0,s||!r())return{ok:!1,perm:i};s=!0,a()}else{if(t||!/\b(ETARGET|notarget)\b|No matching version/i.test(p)){console.log(chalk.yellow(" ⚠ that source did not work")+chalk.gray(0===u.status?` (npm finished but icoa is still v${g||"?"})`:` (npm exit ${u.status})`));break}t=!0}}}return{ok:!1,perm:i}}(n,t.version,t.registries);return r.ok?(console.log(chalk.green(` ✅ upgraded to v${t.version}`)),"upgraded"):(console.log(chalk.red(` ✗ upgrade failed — still v${o}.`)),M(o,t.version,r.perm?"icoa is installed with admin rights":"every source failed",r.perm||void 0),"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}