icoa-cli 2.9.2 → 2.10.0

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.
@@ -91,7 +91,7 @@ function printQuestion(q, answer) {
91
91
  const isEliminated = eliminated.includes(key);
92
92
  const selected = answer === key;
93
93
  if (isEliminated) {
94
- console.log(chalk.gray.strikethrough(` ${key}. ${q.options[key]}`));
94
+ console.log(chalk.gray.strikethrough(` ${key}. ${q.options[key]}`) + chalk.red(' (wrong)'));
95
95
  }
96
96
  else if (selected) {
97
97
  console.log(chalk.green.bold(` ▸ ${key}. ${q.options[key]}`));
@@ -130,7 +130,6 @@ export function registerExamCommand(program) {
130
130
  printInfo('No exams available.');
131
131
  return;
132
132
  }
133
- printHeader('Available Exams');
134
133
  const rows = exams.map((e) => {
135
134
  const statusColor = e.status === 'submitted' ? chalk.green
136
135
  : e.status === 'in_progress' ? chalk.yellow
@@ -144,9 +143,20 @@ export function registerExamCommand(program) {
144
143
  statusColor(e.status),
145
144
  ];
146
145
  });
146
+ // Group by country
147
+ const countries = [...new Set(exams.map((e) => e.country))];
148
+ if (countries.length > 1) {
149
+ printHeader(`Available Exams (${countries.length} countries)`);
150
+ }
151
+ else {
152
+ printHeader('Available Exams');
153
+ }
147
154
  printTable(['ID', 'Name', 'Country', 'Questions', 'Duration', 'Status'], rows);
148
155
  console.log();
149
- console.log(chalk.gray(' Start an exam: exam start <id>'));
156
+ console.log(chalk.gray(' Start: exam start <id>'));
157
+ if (countries.length > 1) {
158
+ console.log(chalk.gray(` Countries: ${countries.join(', ')}`));
159
+ }
150
160
  }
151
161
  catch (err) {
152
162
  spinner.fail('Failed to load exams');
@@ -9,6 +9,16 @@ const LANG_NAMES = {
9
9
  ja: '日本語 (Japanese)',
10
10
  ko: '한국어 (Korean)',
11
11
  es: 'Español (Spanish)',
12
+ ar: 'العربية (Arabic)',
13
+ fr: 'Français (French)',
14
+ pt: 'Português (Portuguese)',
15
+ ru: 'Русский (Russian)',
16
+ hi: 'हिन्दी (Hindi)',
17
+ de: 'Deutsch (German)',
18
+ id: 'Bahasa (Indonesian)',
19
+ th: 'ไทย (Thai)',
20
+ vi: 'Tiếng Việt (Vietnamese)',
21
+ tr: 'Türkçe (Turkish)',
12
22
  };
13
23
  export function registerLangCommand(program) {
14
24
  program
@@ -26,8 +36,8 @@ export function registerLangCommand(program) {
26
36
  console.log(` ${chalk.white(lang)} ${LANG_NAMES[lang]}${current}`);
27
37
  }
28
38
  console.log();
29
- console.log(chalk.gray(' Usage: icoa lang <code>'));
30
- console.log(chalk.gray(' Example: icoa lang zh'));
39
+ console.log(chalk.gray(' Usage: lang <code>'));
40
+ console.log(chalk.gray(' Example: lang zh'));
31
41
  console.log();
32
42
  return;
33
43
  }
package/dist/index.js CHANGED
@@ -102,11 +102,17 @@ program
102
102
  console.log(chalk.gray(' Current model: ') + chalk.white(current));
103
103
  console.log();
104
104
  console.log(chalk.gray(' Available models:'));
105
- console.log(chalk.white(' icoa model gemini-2.5-flash ') + chalk.gray('Fast, free tier'));
106
- console.log(chalk.white(' icoa model gemini-2.5-pro ') + chalk.gray('Strongest reasoning'));
107
- console.log(chalk.white(' icoa model gemma-4-31b-it ') + chalk.gray('Open-source, free'));
108
- console.log(chalk.white(' icoa model gemma-4-26b-a4b-it ') + chalk.gray('Open-source, lightweight'));
109
- console.log(chalk.white(' icoa model <any-model-id> ') + chalk.gray('Custom model'));
105
+ console.log(chalk.bold.white(' Gemini 3.x (Latest)'));
106
+ console.log(chalk.white(' model gemini-3.1-pro-preview ') + chalk.gray('Most powerful, paid'));
107
+ console.log(chalk.white(' model gemini-3-flash-preview ') + chalk.gray('Fast, free tier'));
108
+ console.log(chalk.bold.white(' Gemini 2.5 (Stable)'));
109
+ console.log(chalk.white(' model gemini-2.5-flash ') + chalk.gray('Fast, free tier (default)'));
110
+ console.log(chalk.white(' model gemini-2.5-pro ') + chalk.gray('Strong reasoning, paid'));
111
+ console.log(chalk.bold.white(' Open Source'));
112
+ console.log(chalk.white(' model gemma-4-31b-it ') + chalk.gray('Free, open-source'));
113
+ console.log(chalk.white(' model <any-model-id> ') + chalk.gray('Custom model'));
114
+ console.log();
115
+ console.log(chalk.gray(' Translation uses gemini-3.1-pro-preview for best quality.'));
110
116
  console.log();
111
117
  }
112
118
  else {
@@ -92,24 +92,36 @@ export async function generateHint(level, question, context) {
92
92
  const tokensUsed = (usage?.promptTokenCount || 0) + (usage?.candidatesTokenCount || 0);
93
93
  return { text, tokensUsed };
94
94
  }
95
+ // Use the strongest model for translation quality
96
+ const TRANSLATION_MODEL = 'gemini-3.1-pro-preview';
95
97
  export async function translateText(text, targetLang) {
96
98
  const apiKey = getApiKey();
97
99
  if (!apiKey) {
98
100
  throw new Error('Gemini API key not configured for translation.');
99
101
  }
100
- const config = getConfig();
101
- const modelName = config.geminiModel || 'gemini-2.5-flash';
102
102
  const ai = getClient(apiKey);
103
103
  const prompt = `Translate the following CTF challenge description to ${targetLang}.
104
104
  Keep all technical terms, code, commands, URLs, and flag formats in English.
105
105
  Only translate the narrative/descriptive text.
106
106
 
107
107
  ${text}`;
108
- const response = await ai.models.generateContent({
109
- model: modelName,
110
- contents: prompt,
111
- });
112
- return response.text ?? '';
108
+ try {
109
+ const response = await ai.models.generateContent({
110
+ model: TRANSLATION_MODEL,
111
+ contents: prompt,
112
+ });
113
+ return response.text ?? '';
114
+ }
115
+ catch {
116
+ // Fallback to flash if pro not available
117
+ const config = getConfig();
118
+ const fallback = config.geminiModel || 'gemini-2.5-flash';
119
+ const response = await ai.models.generateContent({
120
+ model: fallback,
121
+ contents: prompt,
122
+ });
123
+ return response.text ?? '';
124
+ }
113
125
  }
114
126
  export function setApiKey(key) {
115
127
  saveConfig({ geminiApiKey: key });
@@ -35,6 +35,16 @@ function getLangName(code) {
35
35
  ja: 'Japanese',
36
36
  ko: 'Korean',
37
37
  es: 'Spanish',
38
+ ar: 'Arabic',
39
+ fr: 'French',
40
+ pt: 'Portuguese (Brazilian)',
41
+ ru: 'Russian',
42
+ hi: 'Hindi',
43
+ de: 'German',
44
+ id: 'Indonesian',
45
+ th: 'Thai',
46
+ vi: 'Vietnamese',
47
+ tr: 'Turkish',
38
48
  };
39
49
  return names[code] || 'English';
40
50
  }
@@ -129,7 +129,7 @@ export interface ChallengeContext {
129
129
  }
130
130
  export declare const DEFAULT_BUDGET: HintBudget;
131
131
  export declare const DEFAULT_CONFIG: IcoaConfig;
132
- export declare const SUPPORTED_LANGUAGES: readonly ["en", "zh", "ja", "ko", "es"];
132
+ export declare const SUPPORTED_LANGUAGES: readonly ["en", "zh", "ja", "ko", "es", "ar", "fr", "pt", "ru", "hi", "de", "id", "th", "vi", "tr"];
133
133
  export type SupportedLanguage = typeof SUPPORTED_LANGUAGES[number];
134
134
  export interface ExamListItem {
135
135
  id: string;
@@ -33,4 +33,4 @@ export const DEFAULT_CONFIG = {
33
33
  country: '',
34
34
  mode: '',
35
35
  };
36
- export const SUPPORTED_LANGUAGES = ['en', 'zh', 'ja', 'ko', 'es'];
36
+ export const SUPPORTED_LANGUAGES = ['en', 'zh', 'ja', 'ko', 'es', 'ar', 'fr', 'pt', 'ru', 'hi', 'de', 'id', 'th', 'vi', 'tr'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "2.9.2",
3
+ "version": "2.10.0",
4
4
  "description": "ICOA CLI — The world's first CLI-native CTF competition terminal",
5
5
  "type": "module",
6
6
  "bin": {