icoa-cli 2.19.568 → 2.19.570

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{execFileSync as o}from"node:child_process";import{lookup as e}from"node:dns/promises";import t from"node:tls";import chalk from"chalk";import{getConfig as r,isConnected as n}from"../lib/config.js";import{getCliVersion as s}from"../lib/version.js";import{logCommand as i}from"../lib/logger.js";import{resolveHostPython as a,REQUIRED_PY as c}from"../lib/aienv.js";import{enableSystemTrust as l,systemTrustActive as u,transportAdvice as d,transportCauseCode as p}from"../lib/tls-trust.js";import{httpFetch as h}from"../lib/http.js";function f(o,e,t,r){const n="ok"===o?chalk.green(" ✓"):"warn"===o?chalk.yellow(" ⚠"):chalk.red(" ✗"),s=chalk.white(e.padEnd(16)),i="ok"===o?chalk.gray(t):("warn"===o?chalk.yellow:chalk.red)(t);console.log(`${n} ${s} ${i}`),r&&"ok"!==o&&console.log(chalk.gray(` → ${r}`))}function m(e,t){try{return o(e,t,{stdio:["ignore","pipe","ignore"],timeout:3e3}).toString().trim()}catch{return null}}async function w(o,e){try{const t=await h(o,{headers:{"User-Agent":"icoa-cli",...e??{}},timeoutMs:6e3});return{status:t.status,body:await t.text()}}catch{return null}}const y="practice.icoa2026.au";function g(o){return new Promise(e=>{const r=t.connect({host:o,port:443,servername:o,rejectUnauthorized:!1,timeout:8e3},()=>{const o=r.getPeerCertificate(),t=o?.issuer&&(o.issuer.O||o.issuer.CN)||"unknown";e({authorized:r.authorized,issuer:String(t),error:r.authorized?"":r.authorizationError||""}),r.destroy()});r.setTimeout(8e3,()=>{r.destroy(),e({failure:Object.assign(new Error("TLS connect timed out"),{code:"UND_ERR_CONNECT_TIMEOUT"})})}),r.on("error",o=>e({failure:o}))})}export function registerDoctorCommand(o){o.command("doctor").description("Run a read-only self-check: version, Node, Python, tools, server, network, terminal").action(async()=>{i("doctor"),console.log(),console.log(chalk.bold.white(" ICOA Doctor")+chalk.gray(" — read-only self-check (nothing is installed or changed)")),console.log(chalk.gray(" ─────────────────────────────────────────────────────"));const o=[];o.push(await async function(){const o=s(),e=await w("https://registry.npmjs.org/icoa-cli/latest");if(!e||200!==e.status)return f("warn","CLI version",`v${o} (could not reach npm to compare)`,"check your network"),"warn";let t="";try{t=JSON.parse(e.body).version??""}catch{}return t&&t!==o?(f("warn","CLI version",`v${o} — latest is v${t}`,"npm install -g icoa-cli@latest"),"warn"):(f("ok","CLI version",`v${o} (latest)`),"ok")}()),o.push(Number(process.versions.node.split(".")[0])>=22?(f("ok","Node.js",`v${process.versions.node}`),"ok"):(f("fail","Node.js",`v${process.versions.node} — need 22+`,"icoa env setup (installs the pinned Node 22.x)"),"fail")),o.push(function(){const o=a(o=>m(o,["--version"]));return o?(f("ok","Python 3.12",`${o.cmd} (${o.version.major}.${o.version.minor})`),"ok"):(f("fail","Python 3.12",`no ${c.major}.${c.minor}.x interpreter found`,"python setup (one command, ~20 s, no sudo) · or brew install python@3.12 / apt install python3.12"),"fail")}()),o.push(function(){const o=["git","base64"].filter(o=>{return e=o,!(null!==m("win32"===process.platform?"where":"which",[e]));var e}),e=m("docker",["--version"]);return 0===o.length?(f("ok","Core tools","git, base64 present"+(e?" · docker (sandbox on)":" · docker absent (host-shell fallback)")),"ok"):(f("warn","Core tools",`missing: ${o.join(", ")}`,"icoa env setup (installs the CTF toolkit)"),"warn")}()),o.push(await async function(){const o=r(),e=n(),t=(o.ctfdUrl||"https://practice.icoa2026.au").replace(/\/$/,""),s=await w(`${t}/api/v1/users/me`,o.token?{Authorization:`Token ${o.token}`}:void 0);return s?e?200===s.status?(f("ok","Server",`${t} · signed in as ${o.userName||"you"}`),"ok"):401===s.status||403===s.status?(f("fail","Server",`${t} rejected your session (HTTP ${s.status})`,"your token expired — join <url> again"),"fail"):(f("warn","Server",`${t} returned HTTP ${s.status}`,"retry shortly, or re-join"),"warn"):(f("warn","Server",`${t} reachable — not signed in`,"join <url> to sign in with your account"),"warn"):(f("fail","Server",`${t} unreachable`,"check Wi-Fi / VPN / firewall, then: join <url>"),"fail")}()),o.push(await async function(){try{const o=await e(y,{all:!0});f("ok","DNS",`${y} → ${o.map(o=>o.address).join(", ")}`)}catch(o){return f("fail","DNS",`cannot resolve ${y} (${p(o)||"lookup failed"})`,"set DNS to 1.1.1.1 or 8.8.8.8, or use a phone hotspot"),"fail"}const o=await g(y);if("failure"in o){const e=p(o.failure);return f("fail","HTTPS (443)","cannot open a connection"+(e?` (${e})`:""),"the network is blocking port 443 to us — ask the admin to allow practice.icoa2026.au, or use a phone hotspot"),"fail"}if(o.authorized)f("ok","HTTPS (443)",`certificate valid · issued by ${o.issuer}`);else{const e=u()||l()?await g(y):null;if(null===e||"failure"in e||!e.authorized)return f("fail","HTTPS (443)",`re-signed as "${o.issuer}" and your OS does not trust it either (${o.error})`,"ask the network admin to exempt practice.icoa2026.au from SSL inspection, or use a phone hotspot"),"fail";f("warn","HTTPS (443)",`this network re-signs HTTPS as "${o.issuer}" (SSL inspection)`,"handled automatically — icoa now uses the certificates your OS trusts. Nothing to do.")}try{const e=await h(`https://${y}/api/icoa/learn/validate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:"DOCTOR00",device_fingerprint:"doctor-probe"}),timeoutMs:8e3});return 429===e.status?(f("warn","Learn API","rate-limited (HTTP 429)","too many attempts from this machine — wait a minute and retry"),"warn"):(f("ok","Learn API",`answered (HTTP ${e.status}) — token entry will work here`),o.authorized?"ok":"warn")}catch(o){const e=p(o);return f("fail","Learn API","no answer"+(e?` (${e})`:""),d(o)[1]||d(o)[0]||"retry, or use a phone hotspot"),"fail"}}()),o.push(function(){const o=["HTTPS_PROXY","https_proxy","HTTP_PROXY","http_proxy"].filter(o=>(process.env[o]||"").trim());return 0===o.length?"ok":(f("warn","Proxy",`${o[0]} is set — Node ignores it, so icoa connects directly`,"if direct connections are blocked here, ask the admin to allow practice.icoa2026.au on TCP 443"),"warn")}()),o.push(function(){const o=(process.env.LANG||process.env.LC_ALL||process.env.LC_CTYPE||"").toLowerCase(),e=o.includes("utf-8")||o.includes("utf8"),t=process.stdout.columns||0;return e?t&&t<60?(f("warn","Terminal",`${t} cols — narrow (some charts fall back to text)`,"widen the window to ≥60 columns"),"warn"):(f("ok","Terminal",`UTF-8 · ${t||"?"} cols`),"ok"):(f("warn","Terminal",`locale "${o||"unset"}" is not UTF-8 (charts/QR degrade to ASCII)`,"export LANG=en_US.UTF-8"),"warn")}()),console.log(chalk.gray(" ─────────────────────────────────────────────────────"));const t=o.filter(o=>"fail"===o).length,k=o.filter(o=>"warn"===o).length;0===t&&0===k?console.log(chalk.green(" All clear — you are ready to compete.")):0===t?console.log(chalk.yellow(` ${k} warning${k>1?"s":""} — usable, but see the → fixes above.`)):console.log(chalk.red(` ${t} blocker${t>1?"s":""}`)+(k?chalk.yellow(` + ${k} warning${k>1?"s":""}`):"")+chalk.gray(" — fix the ✗ items first. Most are solved by ")+chalk.cyan("icoa env setup")),console.log()})}
1
+ import{execFileSync as o}from"node:child_process";import{lookup as e}from"node:dns/promises";import t from"node:tls";import chalk from"chalk";import{getConfig as r,isConnected as n}from"../lib/config.js";import{getCliVersion as s}from"../lib/version.js";import{logCommand as i}from"../lib/logger.js";import{resolveHostPython as a,REQUIRED_PY as c}from"../lib/aienv.js";import{enableSystemTrust as l,systemTrustActive as u,transportAdvice as d,transportCauseCode as p}from"../lib/tls-trust.js";import{httpFetch as h}from"../lib/http.js";function f(o,e,t,r){const n="ok"===o?chalk.green(" ✓"):"warn"===o?chalk.yellow(" ⚠"):chalk.red(" ✗"),s=chalk.white(e.padEnd(16)),i="ok"===o?chalk.gray(t):("warn"===o?chalk.yellow:chalk.red)(t);console.log(`${n} ${s} ${i}`),r&&"ok"!==o&&console.log(chalk.gray(` → ${r}`))}function m(e,t){try{return o(e,t,{stdio:["ignore","pipe","ignore"],timeout:3e3}).toString().trim()}catch{return null}}async function w(o,e){try{const t=await h(o,{headers:{"User-Agent":"icoa-cli",...e??{}},timeoutMs:6e3});return{status:t.status,body:await t.text()}}catch{return null}}const y="practice.icoa2026.au";function g(o){return new Promise(e=>{const r=t.connect({host:o,port:443,servername:o,rejectUnauthorized:!1,timeout:8e3},()=>{const o=r.getPeerCertificate(),t=o?.issuer&&(o.issuer.O||o.issuer.CN)||"unknown";e({authorized:r.authorized,issuer:String(t),error:r.authorized?"":r.authorizationError||""}),r.destroy()});r.setTimeout(8e3,()=>{r.destroy(),e({failure:Object.assign(new Error("TLS connect timed out"),{code:"UND_ERR_CONNECT_TIMEOUT"})})}),r.on("error",o=>e({failure:o}))})}export function registerDoctorCommand(o){o.command("doctor").description("Run a read-only self-check: version, Node, Python, tools, server, network, terminal").action(runDoctor)}export async function runDoctor(){i("doctor"),console.log(),console.log(chalk.bold.white(" ICOA Doctor")+chalk.gray(" — read-only self-check (nothing is installed or changed)")),console.log(chalk.gray(" ─────────────────────────────────────────────────────"));const o=[];o.push(await async function(){const o=s(),e=await w("https://registry.npmjs.org/icoa-cli/latest");if(!e||200!==e.status)return f("warn","CLI version",`v${o} (could not reach npm to compare)`,"check your network"),"warn";let t="";try{t=JSON.parse(e.body).version??""}catch{}return t&&t!==o?(f("warn","CLI version",`v${o} — latest is v${t}`,"npm install -g icoa-cli@latest"),"warn"):(f("ok","CLI version",`v${o} (latest)`),"ok")}()),o.push(Number(process.versions.node.split(".")[0])>=22?(f("ok","Node.js",`v${process.versions.node}`),"ok"):(f("fail","Node.js",`v${process.versions.node} — need 22+`,"icoa env setup (installs the pinned Node 22.x)"),"fail")),o.push(function(){const o=a(o=>m(o,["--version"]));return o?(f("ok","Python 3.12",`${o.cmd} (${o.version.major}.${o.version.minor})`),"ok"):(f("fail","Python 3.12",`no ${c.major}.${c.minor}.x interpreter found`,"python setup (one command, ~20 s, no sudo) · or brew install python@3.12 / apt install python3.12"),"fail")}()),o.push(function(){const o=["git","base64"].filter(o=>{return e=o,!(null!==m("win32"===process.platform?"where":"which",[e]));var e}),e=m("docker",["--version"]);return 0===o.length?(f("ok","Core tools","git, base64 present"+(e?" · docker (sandbox on)":" · docker absent (host-shell fallback)")),"ok"):(f("warn","Core tools",`missing: ${o.join(", ")}`,"icoa env setup (installs the CTF toolkit)"),"warn")}()),o.push(await async function(){const o=r(),e=n(),t=(o.ctfdUrl||"https://practice.icoa2026.au").replace(/\/$/,""),s=await w(`${t}/api/v1/users/me`,o.token?{Authorization:`Token ${o.token}`}:void 0);return s?e?200===s.status?(f("ok","Server",`${t} · signed in as ${o.userName||"you"}`),"ok"):401===s.status||403===s.status?(f("fail","Server",`${t} rejected your session (HTTP ${s.status})`,"your token expired — join <url> again"),"fail"):(f("warn","Server",`${t} returned HTTP ${s.status}`,"retry shortly, or re-join"),"warn"):(f("warn","Server",`${t} reachable — not signed in`,"join <url> to sign in with your account"),"warn"):(f("fail","Server",`${t} unreachable`,"check Wi-Fi / VPN / firewall, then: join <url>"),"fail")}()),o.push(await async function(){try{const o=await e(y,{all:!0});f("ok","DNS",`${y} → ${o.map(o=>o.address).join(", ")}`)}catch(o){return f("fail","DNS",`cannot resolve ${y} (${p(o)||"lookup failed"})`,"set DNS to 1.1.1.1 or 8.8.8.8, or use a phone hotspot"),"fail"}const o=await g(y);if("failure"in o){const e=p(o.failure);return f("fail","HTTPS (443)","cannot open a connection"+(e?` (${e})`:""),"the network is blocking port 443 to us — ask the admin to allow practice.icoa2026.au, or use a phone hotspot"),"fail"}if(o.authorized)f("ok","HTTPS (443)",`certificate valid · issued by ${o.issuer}`);else{const e=u()||l()?await g(y):null;if(null===e||"failure"in e||!e.authorized)return f("fail","HTTPS (443)",`re-signed as "${o.issuer}" and your OS does not trust it either (${o.error})`,"ask the network admin to exempt practice.icoa2026.au from SSL inspection, or use a phone hotspot"),"fail";f("warn","HTTPS (443)",`this network re-signs HTTPS as "${o.issuer}" (SSL inspection)`,"handled automatically — icoa now uses the certificates your OS trusts. Nothing to do.")}try{const e=await h(`https://${y}/api/icoa/learn/validate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:"DOCTOR00",device_fingerprint:"doctor-probe"}),timeoutMs:8e3});return 429===e.status?(f("warn","Learn API","rate-limited (HTTP 429)","too many attempts from this machine — wait a minute and retry"),"warn"):(f("ok","Learn API",`answered (HTTP ${e.status}) — token entry will work here`),o.authorized?"ok":"warn")}catch(o){const e=p(o);return f("fail","Learn API","no answer"+(e?` (${e})`:""),d(o)[1]||d(o)[0]||"retry, or use a phone hotspot"),"fail"}}()),o.push(function(){const o=["HTTPS_PROXY","https_proxy","HTTP_PROXY","http_proxy"].filter(o=>(process.env[o]||"").trim());return 0===o.length?"ok":(f("warn","Proxy",`${o[0]} is set — Node ignores it, so icoa connects directly`,"if direct connections are blocked here, ask the admin to allow practice.icoa2026.au on TCP 443"),"warn")}()),o.push(function(){const o=(process.env.LANG||process.env.LC_ALL||process.env.LC_CTYPE||"").toLowerCase(),e=o.includes("utf-8")||o.includes("utf8"),t=process.stdout.columns||0;return e?t&&t<60?(f("warn","Terminal",`${t} cols — narrow (some charts fall back to text)`,"widen the window to ≥60 columns"),"warn"):(f("ok","Terminal",`UTF-8 · ${t||"?"} cols`),"ok"):(f("warn","Terminal",`locale "${o||"unset"}" is not UTF-8 (charts/QR degrade to ASCII)`,"export LANG=en_US.UTF-8"),"warn")}()),console.log(chalk.gray(" ─────────────────────────────────────────────────────"));const t=o.filter(o=>"fail"===o).length,k=o.filter(o=>"warn"===o).length;0===t&&0===k?console.log(chalk.green(" All clear — you are ready to compete.")):0===t?console.log(chalk.yellow(` ${k} warning${k>1?"s":""} — usable, but see the → fixes above.`)):console.log(chalk.red(` ${t} blocker${t>1?"s":""}`)+(k?chalk.yellow(` + ${k} warning${k>1?"s":""}`):"")+chalk.gray(" — fix the ✗ items first. Most are solved by ")+chalk.cyan("icoa env setup")),console.log()}