icoa-cli 2.19.462 → 2.19.463
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/ai4ctf.js +1 -1
- package/dist/commands/ctf4ai-demo.js +1 -1
- package/dist/commands/ctf4vla.js +1 -1
- package/dist/commands/exam.js +1 -1
- package/dist/commands/lang.js +1 -1
- package/dist/lib/exam-server.js +1 -0
- package/dist/lib/exam-state.js +1 -1
- package/dist/lib/gemini.js +1 -1
- package/package.json +1 -1
package/dist/commands/lang.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import chalk from"chalk";import{getConfig as e,saveConfig as o}from"../lib/config.js";import{COUNTRY_LANG as s}from"../lib/country-lang.js";import{getExamState as a,getRealExamState as
|
|
1
|
+
import chalk from"chalk";import{getConfig as e,saveConfig as o}from"../lib/config.js";import{COUNTRY_LANG as s}from"../lib/country-lang.js";import{getExamState as a,getRealExamState as n}from"../lib/exam-state.js";import{logCommand as t}from"../lib/logger.js";import{ensureLangCache as r}from"../lib/translations-fetcher.js";import{loadUiPack as i}from"../lib/i18n.js";import{printSuccess as l,printError as c,printInfo as g}from"../lib/ui.js";import{SUPPORTED_LANGUAGES as u}from"../types/index.js";const m={en:"English",zh:"中文 (Chinese)",ja:"日本語 (Japanese)",ko:"한국어 (Korean)",es:"Español (Spanish)",ar:"العربية (Arabic)",fr:"Français (French)",pt:"Português (Portuguese)",ru:"Русский (Russian)",hi:"हिन्दी (Hindi)",de:"Deutsch (German)",id:"Bahasa (Indonesian)",th:"ไทย (Thai)",vi:"Tiếng Việt (Vietnamese)",tr:"Türkçe (Turkish)",uk:"Українська (Ukrainian)",ht:"Kreyòl (Haitian Creole)",sw:"Kiswahili (Swahili)",uz:"Oʻzbek (Uzbek)",lo:"ລາວ (Lao)"};export function registerLangCommand(p){p.command("lang [code]").description("Switch display language").action(async p=>{if(t(`lang ${p||""}`),!p){const o=e();g(`Current language: ${chalk.white(m[o.language]||o.language)}`),console.log(),console.log(chalk.gray(" Supported languages:"));for(const e of u){const s=o.language===e?chalk.yellow(" ← current"):"";console.log(` ${chalk.white(e)} ${m[e]}${s}`)}return console.log(),console.log(chalk.gray(" Switch now: ")+chalk.cyan("lang <code>")+chalk.gray(" (e.g. ")+chalk.cyan("lang es")+chalk.gray(")")),console.log(chalk.gray(' No "back" needed — you are still at the ')+chalk.cyan("icoa>")+chalk.gray(" prompt.")),void console.log()}if(!u.includes(p)){c(`Unsupported language: ${p}`);const e=u.map(e=>`${e} (${m[e]?.split(" ")[0]||e})`).join(", ");return void g(`Supported: ${e}`)}const d=n(),y=d?.session?.token;if(y){const e=y.substring(0,2).toUpperCase(),o=s[e];if(o&&p!==o&&"en"!==p)return c("Language is locked during your exam."),g(`Your token (${e}xxx) supports two languages: ${chalk.cyan("en")} (English) or ${chalk.cyan(o)} (${m[o]||o}).`),void g("To change language, finish or quit the current exam first.")}o({language:p}),l(`Language set to: ${m[p]||p}`),await r(p),i(p);const h=a();if(h&&"demo-free"===h.session.examId)try{const{pickDemoQuestions:e,getLocalizedDemoSession:o,getLocalizedDemoQuestions:s,getLocalizedExplanations:a,DEMO_PICK_SIZE:n}=await import("../lib/demo-exam.js"),{saveExamState:t}=await import("../lib/exam-state.js");if(h.questions.every(e=>null!=e.sourceNumber&&Array.isArray(e.sourceOrder)&&4===e.sourceOrder.length)){const e=s(),n=a();h.questions=h.questions.map(o=>{const s=e.find(e=>e.number===o.sourceNumber);return s&&o.sourceOrder?{...o,text:s.text,category:s.category,options:{A:s.options[o.sourceOrder[0]],B:s.options[o.sourceOrder[1]],C:s.options[o.sourceOrder[2]],D:s.options[o.sourceOrder[3]]},explanation:n[o.sourceNumber]}:o}),h.session.examName=o().examName,t(h);const r=h._lastQ||1;console.log(),console.log(chalk.green(` Demo continues in ${m[p]||p}. Your progress is kept.`)),console.log(chalk.white(` Resume: exam q ${r}`))}else h.questions=e(n),h.answers={},h.session.examName=o().examName,h.session.startedAt=(new Date).toISOString(),h._lastQ=1,t(h),console.log(),console.log(chalk.green(` Demo restarted in ${m[p]||p}.`)),console.log(chalk.white(" Type: exam q 1"))}catch{console.log(chalk.gray(" Language changed. Type: demo"))}else if(h&&h.session.token)try{const{saveExamState:e}=await import("../lib/exam-state.js"),{getDeviceFingerprint:o}=await import("../lib/access.js"),{EXAM_SERVER:s}=await import("../lib/exam-server.js"),a=s,n=h.session.token,t=await fetch(`${a}/api/icoa/exam-token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:n,deviceHash:o(),lang:p}),signal:AbortSignal.timeout(1e4)});if(t.ok){const o=(await t.json()).data.questions;h.questions=o,e(h);const s=h._lastQ||1;console.log(),l(`Exam questions updated to ${m[p]||p}. Your answers are kept.`),console.log(chalk.white(` Resume: exam q ${s}`))}else if(429===t.status){const e=await t.json().catch(()=>null),o=e?.message?.match(/(\d+)\s*s/i),s=o?parseInt(o[1],10):60;console.log(),g(`Server rate-limited that token (wait ~${s}s).`),g(`Your exam session is intact — try ${chalk.cyan(`lang ${p}`)} again after the cooldown.`)}else{const{clearExamState:e}=await import("../lib/exam-state.js");e(),console.log(),console.log(chalk.green(" ✓ Old session record cleared — your scores are safe.")),console.log(chalk.gray(" To start (or resume) your exam, type your token:")),console.log(chalk.gray(" → ")+chalk.bold.cyan("exam <your-token>"))}}catch{console.log(chalk.yellow(" Could not reach server. Language changed for UI only."))}else if(h){const e=h._lastQ||1;console.log(),console.log(chalk.gray(` Exam in progress — resuming Q${e}:`)),console.log(chalk.white(` Type: exam q ${e}`))}})}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const EXAM_SERVER=process.env.ICOA_EXAM_SERVER||"https://practice.icoa2026.au";
|
package/dist/lib/exam-state.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{writeFileSync as t,existsSync as e,unlinkSync as n,mkdirSync as a}from"node:fs";import{execFileSync as o}from"node:child_process";import{join as r}from"node:path";import{homedir as i}from"node:os";import{getIcoaDir as s,getInstallSalt as c}from"./config.js";import{deriveFileKey as u,readSignedFile as f,writeSignedFile as m,recordVerdict as l}from"./secure-store.js";export function practicalChatCommand(t,e){return e?"ctf4ai"===t?"ctf4ai":"ai4ctf"===t?"ai4ctf":"vla"===t?"ctf4eai":null:null}export function materializeAttachedData(n,s){if(!s)return null;let c;try{const o=getRealExamState(),u=o?.session?.workspaceDir,f=u&&e(u)?u:r(i(),"icoa-workspace");a(f,{recursive:!0}),c=r(f,`q${n}.txt`),t(c,s,"utf-8")}catch{return null}try{return o("docker",["cp",c,`icoa-sandbox:/home/competitor/challenges/q${n}.txt`],{stdio:"ignore",timeout:3e3}),`challenges/q${n}.txt`}catch{return c}}function d(){return r(s(),"exam-state.json")}function p(){return r(s(),"demo-state.json")}function x(t){return"demo-free"===t?p():d()}export function getRealExamState(){const t=d(),{data:e,verdict:a}=f(t,u(c(),"exam-state"));if(l("exam-state",a),!e)return null;if("demo-free"===e?.session?.examId){try{n(t)}catch{}return null}return e}export function getDemoState(){const t=p(),{data:e,verdict:n}=f(t,u(c(),"demo-state"));return l("demo-state",n),e}export function getExamState(){return getRealExamState()||getDemoState()}export function saveExamState(t){const e=t.session.examId,n=x(e);m(n,t,u(c(),function(t){return"demo-free"===t?"demo-state":"exam-state"}(e)))}export function clearExamState(t){if(t){const a=x(t);e(a)&&n(a)}else{const t=getExamState();if(t){const a=x(t.session.examId);e(a)&&n(a)}}}function g(t){if(!t.session.durationMinutes)return null;const{deadlineServerMs:e,clockOffsetMs:n}=t.session;if("number"==typeof e&&"number"==typeof n)return new Date(e-n);const a=t.session.confirmedAt||t.session.startedAt,o=new Date(a).getTime();return new Date(o+60*t.session.durationMinutes*1e3)}export function getExamDeadline(){const t=getExamState();return t?g(t):null}export function getRealExamDeadline(){const t=getRealExamState();return t?g(t):null}let h=!1;export async function refetchQuestionDataIfStale(){const t=getRealExamState();if(!t)return null;if(h)return t;const e=t.session.token;if(!e)return t;if(!t.questions.some(t=>{if("ai4ctf"!==t.type&&"ctf4ai"!==t.type)return!1;const e=String(t.description||"");return(e.includes("attachedData")||/base64 blob/i.test(e))&&!t.attachedData}))return t;h=!0;try{const{getConfig:n}=await import("./config.js"),a=n(),o=
|
|
1
|
+
import{writeFileSync as t,existsSync as e,unlinkSync as n,mkdirSync as a}from"node:fs";import{execFileSync as o}from"node:child_process";import{join as r}from"node:path";import{homedir as i}from"node:os";import{getIcoaDir as s,getInstallSalt as c}from"./config.js";import{deriveFileKey as u,readSignedFile as f,writeSignedFile as m,recordVerdict as l}from"./secure-store.js";export function practicalChatCommand(t,e){return e?"ctf4ai"===t?"ctf4ai":"ai4ctf"===t?"ai4ctf":"vla"===t?"ctf4eai":null:null}export function materializeAttachedData(n,s){if(!s)return null;let c;try{const o=getRealExamState(),u=o?.session?.workspaceDir,f=u&&e(u)?u:r(i(),"icoa-workspace");a(f,{recursive:!0}),c=r(f,`q${n}.txt`),t(c,s,"utf-8")}catch{return null}try{return o("docker",["cp",c,`icoa-sandbox:/home/competitor/challenges/q${n}.txt`],{stdio:"ignore",timeout:3e3}),`challenges/q${n}.txt`}catch{return c}}function d(){return r(s(),"exam-state.json")}function p(){return r(s(),"demo-state.json")}function x(t){return"demo-free"===t?p():d()}export function getRealExamState(){const t=d(),{data:e,verdict:a}=f(t,u(c(),"exam-state"));if(l("exam-state",a),!e)return null;if("demo-free"===e?.session?.examId){try{n(t)}catch{}return null}return e}export function getDemoState(){const t=p(),{data:e,verdict:n}=f(t,u(c(),"demo-state"));return l("demo-state",n),e}export function getExamState(){return getRealExamState()||getDemoState()}export function saveExamState(t){const e=t.session.examId,n=x(e);m(n,t,u(c(),function(t){return"demo-free"===t?"demo-state":"exam-state"}(e)))}export function clearExamState(t){if(t){const a=x(t);e(a)&&n(a)}else{const t=getExamState();if(t){const a=x(t.session.examId);e(a)&&n(a)}}}function g(t){if(!t.session.durationMinutes)return null;const{deadlineServerMs:e,clockOffsetMs:n}=t.session;if("number"==typeof e&&"number"==typeof n)return new Date(e-n);const a=t.session.confirmedAt||t.session.startedAt,o=new Date(a).getTime();return new Date(o+60*t.session.durationMinutes*1e3)}export function getExamDeadline(){const t=getExamState();return t?g(t):null}export function getRealExamDeadline(){const t=getRealExamState();return t?g(t):null}let h=!1;export async function refetchQuestionDataIfStale(){const t=getRealExamState();if(!t)return null;if(h)return t;const e=t.session.token;if(!e)return t;if(!t.questions.some(t=>{if("ai4ctf"!==t.type&&"ctf4ai"!==t.type)return!1;const e=String(t.description||"");return(e.includes("attachedData")||/base64 blob/i.test(e))&&!t.attachedData}))return t;h=!0;try{const{getConfig:n}=await import("./config.js"),a=n(),{EXAM_SERVER:o}=await import("./exam-server.js"),r=o,i=a.language||"en",s=await fetch(`${r}/api/icoa/exam-token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:e,deviceHash:"",lang:i}),signal:AbortSignal.timeout(8e3)});if(!s.ok)return t;const c=await s.json(),u=c?.data?.questions||[];if(0===u.length)return t;const f=new Map;for(const t of u)f.set(t.number,t);let m=0;for(const e of t.questions){const t=f.get(e.number);t&&(t.attachedData&&!e.attachedData&&(e.attachedData=t.attachedData,m++),t.description&&(!e.description||t.description.length>e.description.length)&&(e.description=t.description))}return m>0&&saveExamState(t),t}catch{return t}}
|
package/dist/lib/gemini.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{GoogleGenAI as e}from"@google/genai";import chalk from"chalk";import{readFileSync as t}from"node:fs";import{dirname as
|
|
1
|
+
import{GoogleGenAI as e}from"@google/genai";import chalk from"chalk";import{readFileSync as t}from"node:fs";import{dirname as o,join as n}from"node:path";import{fileURLToPath as a}from"node:url";import{getConfig as r,saveConfig as s}from"./config.js";import{getRealExamState as i}from"./exam-state.js";import{EXAM_SERVER as c}from"./exam-server.js";const l=o(a(import.meta.url));let u=null;function p(){if(u)return u;try{const e=JSON.parse(t(n(l,"..","..","package.json"),"utf-8"));u=e.version||"unknown"}catch{u="unknown"}return u}export async function translateText(e,t){throw new Error("Local AI model path has been disabled (2026-04-24). ICOA provides all AI server-side — no API key setup needed. Use `ai4ctf` (chat) or `exam` (with integrated help/hint) instead.")}export function setApiKey(e){s({geminiApiKey:e})}export async function createChatSession(e,t){const o=r();let n=t||"You are an AI teammate in the ICOA cybersecurity CTF competition (International Cyber Olympiad in AI 2026, Sydney).\n\nYou're a friendly, knowledgeable cybersecurity partner — like a fellow competitor sitting next to the user. Be conversational, encouraging, and collaborative.\n\nRULES:\n- Help the competitor think through challenges, brainstorm approaches, explain concepts\n- You MAY discuss vulnerability types, tools, techniques, and methodologies\n- You MAY suggest approaches and help debug code\n- Do NOT provide complete working exploits or full solution scripts\n- Do NOT provide flags or flag fragments\n- Never output anything matching flag format: icoa{...}\n- If you don't know something, say so honestly\n- Keep responses concise unless the user asks for detail\n- When the user opens a challenge, use the context to give relevant advice";e&&(n+=`\n\nThe competitor is currently working on:\nChallenge: ${e.name}\nCategory: ${e.category}`),n+='\n\nINTEGRITY — give method, not the produced answer:\n- You may name the tool/command/technique (e.g. "this is base64; run base64 -d on the file").\n- Do NOT perform a solution step for the competitor and hand back its result: never decode,\n decrypt, deobfuscate, run, execute, or compute over data they paste or that is attached,\n and then reveal the produced value. They must run it themselves and read the output.\n- Do NOT reveal the exact final answer, and never output icoa{...} / flag{...} in any case.\n- If asked to "just give the answer" / "solve it" / "decode this and tell me", refuse and\n nudge them to do the step themselves.';const a=o.ctfdUrl||"https://practice.icoa2026.au",s=o.deviceFingerprint||"",l=o.geminiModel||"gemini-2.5-flash-lite",u=[];return{async sendMessage(e){u.push({role:"user",text:e});const t=i(),r={systemPrompt:n,messages:u,model:l,maxTokens:2048,deviceFingerprint:s};t?.session?.token&&(r.examToken=t.session.token);const d={"Content-Type":"application/json","User-Agent":`icoa-cli/${p()}`,"X-Device-Fingerprint":s};!r.examToken&&o.ctfdUrl&&o.token&&o.userName&&(r.account=o.userName,d.Authorization=`Token ${o.token}`);const m=e=>new Promise(t=>setTimeout(t,e)),h=[2e3,5e3],g=r.examToken?c:a;let f=null,y=null;for(let e=0;e<=h.length&&(f=await fetch(`${g}/api/icoa/ai/chat`,{method:"POST",headers:d,body:JSON.stringify(r),signal:AbortSignal.timeout(6e4)}),!f.ok&&429===f.status);e++)e<h.length?await m(h[e]):y=f;if(!f)throw new Error("AI proxy: no response");if(!f.ok){const e=(await f.json().catch(()=>({message:"AI proxy error"}))).message||`AI proxy returned ${f.status}`;if(401===f.status)throw new Error(`${chalk.yellow("⚠ ")}Exam token expired. Re-enter via \`exam <token>\`.`);if(403===f.status)throw new Error(chalk.yellow("⚠ ")+e);if(429===f.status)throw new Error(chalk.yellow("⏳ ")+e+chalk.gray(" (retried twice — quota burst, try again in ~30s)"));throw new Error(e)}const w=await f.json(),v=function(e){return e.replace(/icoa\{[^}]*\}/gi,"[FLAG REDACTED]")}(w.data?.text||""),k=w.data?.tokensUsed||0,x="number"==typeof w.data?.accountSpent?w.data.accountSpent:void 0,b="number"==typeof w.data?.accountCap?w.data.accountCap:void 0;return u.push({role:"model",text:v}),{text:v,tokensUsed:k,accountSpent:x,accountCap:b}}}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "icoa-cli",
|
|
3
|
-
"version": "2.19.
|
|
3
|
+
"version": "2.19.463",
|
|
4
4
|
"description": "ICOA CLI — The world's first CLI-native cyber & AI security olympiad terminal: AI4CTF (Day 1), CTF4AI (Day 2), VLA4CTF (Pioneer Round — embodied AI)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|