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.
- package/dist/commands/exam.js +13 -3
- package/dist/commands/lang.js +12 -2
- package/dist/index.js +11 -5
- package/dist/lib/gemini.js +19 -7
- package/dist/lib/translation.js +10 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +1 -1
- package/package.json +1 -1
package/dist/commands/exam.js
CHANGED
|
@@ -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
|
|
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');
|
package/dist/commands/lang.js
CHANGED
|
@@ -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:
|
|
30
|
-
console.log(chalk.gray(' Example:
|
|
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('
|
|
106
|
-
console.log(chalk.white('
|
|
107
|
-
console.log(chalk.white('
|
|
108
|
-
console.log(chalk.white('
|
|
109
|
-
console.log(chalk.white('
|
|
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 {
|
package/dist/lib/gemini.js
CHANGED
|
@@ -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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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 });
|
package/dist/lib/translation.js
CHANGED
|
@@ -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
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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;
|
package/dist/types/index.js
CHANGED