icoa-cli 2.19.102 → 2.19.104

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/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{registerHintCommands as n}from"./commands/hint.js";import{registerRefCommand as r}from"./commands/ref.js";import{registerShellCommand as s}from"./commands/shell.js";import{registerFilesCommand as t}from"./commands/files.js";import{registerConnectCommand as i}from"./commands/connect.js";import{registerNoteCommand as a}from"./commands/note.js";import{registerLogCommand as l}from"./commands/log.js";import{registerLangCommand as m}from"./commands/lang.js";import{registerSetupCommand as c}from"./commands/setup.js";import{registerEnvCommand as g}from"./commands/env.js";import{registerAi4ctfCommand as d}from"./commands/ai4ctf.js";import{registerExamCommand as p}from"./commands/exam.js";import{registerCtf4aiDemoCommand as y}from"./commands/ctf4ai-demo.js";import{registerThemeCommand as h}from"./commands/theme.js";import{getConfig as f,saveConfig as u}from"./lib/config.js";import{startRepl as w}from"./repl.js";import{setTerminalTheme as T}from"./lib/theme.js";import{checkForUpdates as b}from"./lib/update-check.js";import{readFileSync as C}from"node:fs";import{fileURLToPath as $}from"node:url";import{dirname as _,join as A}from"node:path";const j=_($(import.meta.url)),v=JSON.parse(C(A(j,"..","package.json"),"utf-8")).version,E=chalk.cyan(" ─────────────────────────────────────────────────────"),I=`\n${E}\n\n ${chalk.bold.white("██╗ ██████╗ ██████╗ █████╗")}\n ${chalk.bold.white("██║██╔════╝██╔═══██╗██╔══██╗")}\n ${chalk.bold.white("██║██║ ██║ ██║███████║")}\n ${chalk.bold.white("██║██║ ██║ ██║██╔══██║")}\n ${chalk.bold.white("██║╚██████╗╚██████╔╝██║ ██║")}\n ${chalk.bold.white("╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝")}\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.bold.yellow("AI is your ally. AI is your target.")}\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${v}`)}\n\n${E}\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 S=new o;if(S.name("icoa").version(v).description("ICOA CLI — CLI-Native CTF Competition Terminal").option("--resume","Resume previous session").action(async o=>{const e=f();T("high-contrast"===e.themeVariant?"high-contrast":"dark"),b();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();if(console.log(I),process.argv.length<=2||o.resume)return o.resume||await async function(){const o=process.stdin;if(o.isTTY&&"function"==typeof o.setRawMode)return new Promise(e=>{let n=!1;const r=()=>{if(!n){n=!0,o.removeListener("data",s);try{o.setRawMode(!1)}catch{}o.pause(),e()}},s=()=>r();o.setRawMode(!0),o.resume(),o.once("data",s),console.log(chalk.gray(" (press any key to continue...)")),setTimeout(r,3e3)});await new Promise(o=>setTimeout(o,3e3))}(),void w(S,!!o.resume)}),e(S),n(S),r(S),s(S),t(S),i(S),a(S),l(S),m(S),c(S),g(S),d(S),p(S),y(S),h(S),S.command("model",{hidden:!0}).argument("[name]","model name to switch to").action(o=>{const e=f().geminiModel||"gemini-2.5-flash";o?(u({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.1-pro-preview ")+chalk.gray("Most powerful, paid")),console.log(chalk.white(" model gemini-3-flash-preview ")+chalk.gray("Fast, free tier")),console.log(chalk.bold.white(" Gemini 2.5 (Stable)")),console.log(chalk.white(" model gemini-2.5-flash ")+chalk.gray("Fast, free tier (default)")),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.1-pro-preview 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)))}S.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 r}from"./commands/shell.js";import{registerFilesCommand as s}from"./commands/files.js";import{registerConnectCommand as t}from"./commands/connect.js";import{registerNoteCommand as i}from"./commands/note.js";import{registerLogCommand as a}from"./commands/log.js";import{registerLangCommand as l}from"./commands/lang.js";import{registerSetupCommand as m}from"./commands/setup.js";import{registerEnvCommand as c}from"./commands/env.js";import{registerAi4ctfCommand as g}from"./commands/ai4ctf.js";import{registerExamCommand as d}from"./commands/exam.js";import{registerCtf4aiDemoCommand as p}from"./commands/ctf4ai-demo.js";import{registerThemeCommand as y}from"./commands/theme.js";import{getConfig as h,saveConfig as f}from"./lib/config.js";import{startRepl as u}from"./repl.js";import{setTerminalTheme as w}from"./lib/theme.js";import{checkForUpdates as T}from"./lib/update-check.js";import{readFileSync as b}from"node:fs";import{fileURLToPath as C}from"node:url";import{dirname as $,join as _}from"node:path";const A=$(C(import.meta.url)),v=JSON.parse(b(_(A,"..","package.json"),"utf-8")).version,E=chalk.cyan(" ─────────────────────────────────────────────────────"),j=`\n${E}\n\n ${chalk.bold.white("██╗ ██████╗ ██████╗ █████╗")}\n ${chalk.bold.white("██║██╔════╝██╔═══██╗██╔══██╗")}\n ${chalk.bold.white("██║██║ ██║ ██║███████║")}\n ${chalk.bold.white("██║██║ ██║ ██║██╔══██║")}\n ${chalk.bold.white("██║╚██████╗╚██████╔╝██║ ██║")}\n ${chalk.bold.white("╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝")}\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.bold.yellow("AI is your ally. AI is your target.")}\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${v}`)}\n\n${E}\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 I=new o;if(I.name("icoa").version(v).description("ICOA CLI — CLI-Native CTF Competition Terminal").option("--resume","Resume previous session").action(async o=>{const e=h();w("high-contrast"===e.themeVariant?"high-contrast":"dark"),T();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();if(console.log(j),process.argv.length<=2||o.resume)return o.resume||await async function(){const o=process.stdin;if(o.isTTY&&"function"==typeof o.setRawMode)return new Promise(e=>{let n=!1;const r=()=>{if(!n){n=!0,o.removeListener("data",s);try{o.setRawMode(!1)}catch{}o.pause(),e()}},s=()=>r();o.setRawMode(!0),o.resume(),o.once("data",s),console.log(chalk.gray(" (press any key to continue...)")),setTimeout(r,3e3)});await new Promise(o=>setTimeout(o,3e3))}(),void u(I,!!o.resume)}),e(I),n(I),r(I),s(I),t(I),i(I),a(I),l(I),m(I),c(I),g(I),d(I),p(I),y(I),I.command("model",{hidden:!0}).argument("[name]","model name to switch to").action(o=>{const e=h().geminiModel||"gemini-2.5-flash";o?(f({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.1-pro-preview ")+chalk.gray("Most powerful, paid")),console.log(chalk.white(" model gemini-3-flash-preview ")+chalk.gray("Fast, free tier")),console.log(chalk.bold.white(" Gemini 2.5 (Stable)")),console.log(chalk.white(" model gemini-2.5-flash ")+chalk.gray("Fast, free tier (default)")),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.1-pro-preview 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)))}I.parse();
@@ -1,10 +1,20 @@
1
1
  import type { HintLevel, ChallengeContext } from '../types/index.js';
2
2
  export declare function filterFlagPatterns(text: string): string;
3
- export declare function generateHint(level: HintLevel, question: string, context?: ChallengeContext): Promise<{
3
+ /**
4
+ * @deprecated Local hint generation is disabled as of 2026-04-24.
5
+ * All AI interaction goes through the server gateway. Use `ai4ctf` chat
6
+ * or the integrated `help` / `hint` commands inside `exam` mode instead.
7
+ */
8
+ export declare function generateHint(_level: HintLevel, _question: string, _context?: ChallengeContext): Promise<{
4
9
  text: string;
5
10
  tokensUsed: number;
6
11
  }>;
7
- export declare function translateText(text: string, targetLang: string): Promise<string>;
12
+ /**
13
+ * @deprecated Runtime translation is disabled. Translations are pre-baked
14
+ * at build time via `panda/translate-*.js` scripts and shipped in
15
+ * `translations/<lang>/` — CLI never translates at runtime.
16
+ */
17
+ export declare function translateText(_text: string, _targetLang: string): Promise<string>;
8
18
  export declare function setApiKey(key: string): void;
9
19
  export declare function createChatSession(context?: ChallengeContext, customSystemPrompt?: string): Promise<{
10
20
  sendMessage: (msg: string) => Promise<{
@@ -1 +1 @@
1
- import{GoogleGenAI as e}from"@google/genai";import chalk from"chalk";import{readFileSync as t}from"node:fs";import{dirname as n,join as o}from"node:path";import{fileURLToPath as r}from"node:url";import{getConfig as i,saveConfig as a}from"./config.js";import{getRealExamState as s}from"./exam-state.js";const c=n(r(import.meta.url));let l=null;function g(){if(l)return l;try{const e=JSON.parse(t(o(c,"..","..","package.json"),"utf-8"));l=e.version||"unknown"}catch{l="unknown"}return l}const u={A:"You are an AI assistant in a cybersecurity CTF competition called ICOA.\nYou are providing Level A (General Guidance) to a competitor.\n\nSTRICT RULES:\n- Only answer conceptual questions\n- Do NOT mention specific vulnerability names\n- Do NOT provide any code, commands, or tool usage\n- Do NOT mention specific attack techniques\n- Use questions to guide the competitor toward their own discovery\n- If the competitor asks you to solve the challenge, refuse and redirect\n- Never output anything matching flag format: icoa{...}",B:'You are an AI assistant in ICOA CTF, providing Level B (Deep Analysis).\n\nRULES:\n- You MAY identify specific vulnerability types (e.g., "buffer overflow")\n- You MAY suggest which category of tool to use (e.g., "a debugger")\n- Do NOT provide complete commands or working code\n- Do NOT provide exploit code or payloads\n- Do NOT provide flags or flag fragments\n- Never output anything matching: icoa{...}',C:"You are an AI assistant in ICOA CTF, providing Level C (Critical Assist).\n\nRULES:\n- You MAY provide the key conceptual breakthrough\n- You MAY name specific algorithms or approaches\n- Do NOT provide complete exploit code\n- Do NOT provide the flag\n- Never output anything matching: icoa{...}"};function p(e,t){let n=u[e];return t&&(n+=`\n\nThe competitor is currently working on:\nChallenge: ${t.name}\nCategory: ${t.category}`),n}export function filterFlagPatterns(e){return e.replace(/icoa\{[^}]*\}/gi,"[FLAG REDACTED]")}function m(){return process.env.GEMINI_API_KEY||i().geminiApiKey||""}function d(t){return new e({apiKey:t})}export async function generateHint(e,t,n){let o=m();if(!o)try{const{input:e}=await import("@inquirer/prompts");if(console.log(),console.log(chalk.yellow(" Gemini API key not configured.")),console.log(chalk.gray(" Get one free at: ")+chalk.cyan("https://aistudio.google.com/apikey")),console.log(),o=await e({message:"Enter your Gemini API Key:"}),!o.trim())throw new Error("No API key provided.");o=o.trim(),a({geminiApiKey:o}),console.log(chalk.green(" Key saved for future use.")),console.log()}catch{throw new Error("Gemini API key not configured.\nSet GEMINI_API_KEY environment variable, or run: icoa setup")}const r=i().geminiModel||"gemma-4-31b-it",s=d(o),c=await s.models.generateContent({model:r,config:{systemInstruction:p(e,n)},contents:t}),l=filterFlagPatterns(c.text??""),g=c.usageMetadata;return{text:l,tokensUsed:(g?.promptTokenCount||0)+(g?.candidatesTokenCount||0)}}export async function translateText(e,t){const n=m();if(!n)throw new Error("Gemini API key not configured for translation.");const o=d(n),r=`Translate the following CTF challenge description to ${t}.\nKeep all technical terms, code, commands, URLs, and flag formats in English.\nOnly translate the narrative/descriptive text.\n\n${e}`;try{return(await o.models.generateContent({model:"gemini-3.1-pro-preview",contents:r})).text??""}catch{const e=i().geminiModel||"gemma-4-31b-it";return(await o.models.generateContent({model:e,contents:r})).text??""}}export function setApiKey(e){a({geminiApiKey:e})}export async function createChatSession(e,t){const n=i(),o=m();let r=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";if(e&&(r+=`\n\nThe competitor is currently working on:\nChallenge: ${e.name}\nCategory: ${e.category}`),o){const e=n.geminiModel||"gemini-2.5-flash",t=d(o).chats.create({model:e,config:{systemInstruction:r}});return{async sendMessage(e){const n=await t.sendMessage({message:e}),o=filterFlagPatterns(n.text??""),r=n.usageMetadata;return{text:o,tokensUsed:r?.totalTokenCount||(r?.promptTokenCount||0)+(r?.candidatesTokenCount||0)}}}}const a=n.ctfdUrl||"https://practice.icoa2026.au",c=n.deviceFingerprint||"",l=n.geminiModel||"gemini-2.5-flash",u=[];return{async sendMessage(e){u.push({role:"user",text:e});const t=s(),n={systemPrompt:r,messages:u,model:l,maxTokens:2048,deviceFingerprint:c};t?.session?.token&&(n.examToken=t.session.token);const o=await fetch(`${a}/api/icoa/ai/chat`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":`icoa-cli/${g()}`},body:JSON.stringify(n),signal:AbortSignal.timeout(6e4)});if(!o.ok){const e=(await o.json().catch(()=>({message:"AI proxy error"}))).message||`AI proxy returned ${o.status}`;if(401===o.status)throw new Error(chalk.yellow("⚠ ")+"Exam token expired. Re-enter via `exam <token>`.");if(403===o.status)throw new Error(chalk.yellow("⚠ ")+e);if(429===o.status)throw new Error(chalk.yellow("⏳ ")+e);throw new Error(e)}const i=await o.json(),p=filterFlagPatterns(i.data?.text||""),m=i.data?.tokensUsed||0;return u.push({role:"model",text:p}),{text:p,tokensUsed:m}}}}
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 r}from"node:url";import{getConfig as a,saveConfig as s}from"./config.js";import{getRealExamState as i}from"./exam-state.js";const c=o(r(import.meta.url));let l=null;function p(){if(l)return l;try{const e=JSON.parse(t(n(c,"..","..","package.json"),"utf-8"));l=e.version||"unknown"}catch{l="unknown"}return l}export function filterFlagPatterns(e){return e.replace(/icoa\{[^}]*\}/gi,"[FLAG REDACTED]")}const u="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 async function generateHint(e,t,o){throw new Error(u)}export async function translateText(e,t){throw new Error(u)}export function setApiKey(e){s({geminiApiKey:e})}export async function createChatSession(e,t){const o=a();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}`);const r=o.ctfdUrl||"https://practice.icoa2026.au",s=o.deviceFingerprint||"",c=o.geminiModel||"gemini-2.5-flash",l=[];return{async sendMessage(e){l.push({role:"user",text:e});const t=i(),o={systemPrompt:n,messages:l,model:c,maxTokens:2048,deviceFingerprint:s};t?.session?.token&&(o.examToken=t.session.token);const a=await fetch(`${r}/api/icoa/ai/chat`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":`icoa-cli/${p()}`},body:JSON.stringify(o),signal:AbortSignal.timeout(6e4)});if(!a.ok){const e=(await a.json().catch(()=>({message:"AI proxy error"}))).message||`AI proxy returned ${a.status}`;if(401===a.status)throw new Error(chalk.yellow("⚠ ")+"Exam token expired. Re-enter via `exam <token>`.");if(403===a.status)throw new Error(chalk.yellow("⚠ ")+e);if(429===a.status)throw new Error(chalk.yellow("⏳ ")+e);throw new Error(e)}const u=await a.json(),m=filterFlagPatterns(u.data?.text||""),h=u.data?.tokensUsed||0;return l.push({role:"model",text:m}),{text:m,tokensUsed:h}}}}