icoa-cli 2.6.0 โ 2.7.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 +96 -8
- package/dist/index.js +1 -1
- package/dist/repl.js +19 -9
- package/package.json +1 -1
package/dist/commands/exam.js
CHANGED
|
@@ -44,11 +44,37 @@ function printTimeRemaining() {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
+
// Australian easter eggs every 5 questions
|
|
48
|
+
const EASTER_EGGS = {
|
|
49
|
+
5: { emoji: '๐๏ธ', text: 'Sydney Opera House โ You\'re doing great!' },
|
|
50
|
+
10: { emoji: '๐', text: 'Sydney Harbour Bridge โ Keep going!' },
|
|
51
|
+
15: { emoji: '๐จ', text: 'Halfway there! A koala cheers you on!' },
|
|
52
|
+
20: { emoji: '๐ฆ', text: 'A kangaroo hops by โ almost there!' },
|
|
53
|
+
25: { emoji: '๐๏ธ', text: 'Bondi Beach โ 5 more to go!' },
|
|
54
|
+
30: { emoji: '๐', text: 'G\'day mate! All questions done!' },
|
|
55
|
+
};
|
|
56
|
+
function printQuestionProgress(current, total, answered) {
|
|
57
|
+
const width = 30;
|
|
58
|
+
const filled = Math.round((current / total) * width);
|
|
59
|
+
const empty = width - filled;
|
|
60
|
+
const bar = chalk.cyan('โ'.repeat(filled)) + chalk.gray('โ'.repeat(empty));
|
|
61
|
+
const pct = Math.round((current / total) * 100);
|
|
62
|
+
console.log();
|
|
63
|
+
console.log(` ${bar} ${chalk.white.bold(`${current}`)}${chalk.gray(`/${total}`)} ${chalk.gray(`(${answered} answered)`)} ${chalk.gray(`${pct}%`)}`);
|
|
64
|
+
}
|
|
47
65
|
function printQuestion(q, answer) {
|
|
48
66
|
const state = getExamState();
|
|
49
|
-
const total = state?.session.questionCount ||
|
|
67
|
+
const total = Number(state?.session.questionCount || 30);
|
|
68
|
+
const answered = Object.keys(state?.answers || {}).length;
|
|
69
|
+
// Progress bar
|
|
70
|
+
printQuestionProgress(q.number, total, answered);
|
|
71
|
+
// Easter egg
|
|
72
|
+
const egg = EASTER_EGGS[q.number];
|
|
73
|
+
if (egg && q.number <= total) {
|
|
74
|
+
console.log(chalk.yellow(` ${egg.emoji} ${egg.text}`));
|
|
75
|
+
}
|
|
50
76
|
console.log();
|
|
51
|
-
console.log(chalk.bold.white(` Q${q.number}
|
|
77
|
+
console.log(chalk.bold.white(` Q${q.number}`) + (q.category ? chalk.gray(` [${q.category}]`) : ''));
|
|
52
78
|
console.log(` ${q.text}`);
|
|
53
79
|
console.log();
|
|
54
80
|
for (const key of ['A', 'B', 'C', 'D']) {
|
|
@@ -58,6 +84,14 @@ function printQuestion(q, answer) {
|
|
|
58
84
|
console.log(`${prefix} ${text}`);
|
|
59
85
|
}
|
|
60
86
|
console.log();
|
|
87
|
+
// Navigation hint
|
|
88
|
+
const nav = [];
|
|
89
|
+
if (q.number > 1)
|
|
90
|
+
nav.push('exam prev');
|
|
91
|
+
if (q.number < total)
|
|
92
|
+
nav.push('exam next');
|
|
93
|
+
nav.push(`exam answer ${q.number} <A-D>`);
|
|
94
|
+
console.log(chalk.gray(` ${nav.join(' ยท ')}`));
|
|
61
95
|
}
|
|
62
96
|
export function registerExamCommand(program) {
|
|
63
97
|
const exam = program.command('exam').description('National selection exam');
|
|
@@ -192,6 +226,43 @@ export function registerExamCommand(program) {
|
|
|
192
226
|
console.log();
|
|
193
227
|
}
|
|
194
228
|
});
|
|
229
|
+
// โโโ exam next โโโ
|
|
230
|
+
exam
|
|
231
|
+
.command('next')
|
|
232
|
+
.description('Go to next question')
|
|
233
|
+
.action(() => {
|
|
234
|
+
logCommand('exam next');
|
|
235
|
+
const state = getExamState();
|
|
236
|
+
if (!state) {
|
|
237
|
+
printError('No exam in progress.');
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const current = state.questions.findIndex((q) => !state.answers[q.number]);
|
|
241
|
+
const lastViewed = state._lastQ || 1;
|
|
242
|
+
const next = Math.min(lastViewed + 1, state.questions.length);
|
|
243
|
+
state._lastQ = next;
|
|
244
|
+
saveExamState(state);
|
|
245
|
+
const q = state.questions[next - 1];
|
|
246
|
+
printQuestion(q, state.answers[q.number]);
|
|
247
|
+
});
|
|
248
|
+
// โโโ exam prev โโโ
|
|
249
|
+
exam
|
|
250
|
+
.command('prev')
|
|
251
|
+
.description('Go to previous question')
|
|
252
|
+
.action(() => {
|
|
253
|
+
logCommand('exam prev');
|
|
254
|
+
const state = getExamState();
|
|
255
|
+
if (!state) {
|
|
256
|
+
printError('No exam in progress.');
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const lastViewed = state._lastQ || 1;
|
|
260
|
+
const prev = Math.max(lastViewed - 1, 1);
|
|
261
|
+
state._lastQ = prev;
|
|
262
|
+
saveExamState(state);
|
|
263
|
+
const q = state.questions[prev - 1];
|
|
264
|
+
printQuestion(q, state.answers[q.number]);
|
|
265
|
+
});
|
|
195
266
|
// โโโ exam answer <n> <choice> โโโ
|
|
196
267
|
exam
|
|
197
268
|
.command('answer <n> <choice>')
|
|
@@ -217,15 +288,20 @@ export function registerExamCommand(program) {
|
|
|
217
288
|
return;
|
|
218
289
|
}
|
|
219
290
|
state.answers[num] = c;
|
|
291
|
+
state._lastQ = num;
|
|
220
292
|
saveExamState(state);
|
|
221
293
|
const answered = Object.keys(state.answers).length;
|
|
222
294
|
const total = state.session.questionCount;
|
|
223
295
|
printSuccess(`Q${num}: ${c} saved (${answered}/${total} answered)`);
|
|
296
|
+
// Auto-show next question
|
|
224
297
|
if (num < state.questions.length) {
|
|
225
|
-
|
|
298
|
+
const nextQ = state.questions[num]; // 0-indexed: questions[num] = question num+1
|
|
299
|
+
printQuestion(nextQ, state.answers[nextQ.number]);
|
|
226
300
|
}
|
|
227
301
|
else if (answered === total) {
|
|
228
|
-
console.log(
|
|
302
|
+
console.log();
|
|
303
|
+
console.log(chalk.green.bold(' ๐ All questions answered!'));
|
|
304
|
+
console.log(chalk.white(' Use: exam review ยท exam submit'));
|
|
229
305
|
}
|
|
230
306
|
});
|
|
231
307
|
// โโโ exam review โโโ
|
|
@@ -313,10 +389,22 @@ export function registerExamCommand(program) {
|
|
|
313
389
|
clearExamState();
|
|
314
390
|
const percentage = Math.round(score / total * 100);
|
|
315
391
|
console.log();
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
392
|
+
console.log(chalk.cyan(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
393
|
+
console.log();
|
|
394
|
+
console.log(chalk.bold.white(' โโโ โโโโโโโ โโโโโโโ โโโโโโ'));
|
|
395
|
+
console.log(chalk.bold.white(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
396
|
+
console.log(chalk.bold.white(' โโโโโโ โโโ โโโโโโโโโโโ'));
|
|
397
|
+
console.log(chalk.bold.white(' โโโโโโ โโโ โโโโโโโโโโโ'));
|
|
398
|
+
console.log(chalk.bold.white(' โโโโโโโโโโโโโโโโโโโโโโโ โโโ'));
|
|
399
|
+
console.log(chalk.bold.white(' โโโ โโโโโโโ โโโโโโโ โโโ โโโ'));
|
|
400
|
+
console.log();
|
|
401
|
+
console.log(chalk.bold(` Score: ${score}/${total} (${percentage}%)`));
|
|
402
|
+
console.log(chalk.bold(` ${percentage >= 60 ? chalk.green('โ PASSED') : chalk.red('โ NOT PASSED')}`));
|
|
403
|
+
console.log();
|
|
404
|
+
console.log(chalk.yellow(' International Cyber Olympiad in AI 2026'));
|
|
405
|
+
console.log(chalk.gray(' Sydney, Australia ยท Jun 27 - Jul 2, 2026'));
|
|
406
|
+
console.log();
|
|
407
|
+
console.log(chalk.cyan(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
320
408
|
console.log();
|
|
321
409
|
console.log(chalk.gray(' This was a free practice exam.'));
|
|
322
410
|
console.log(chalk.gray(' For the real exam, contact your national organizer.'));
|
package/dist/index.js
CHANGED
|
@@ -38,7 +38,7 @@ ${LINE}
|
|
|
38
38
|
${chalk.white('Sydney, Australia')} ${chalk.gray('Jun 27 - Jul 2, 2026')}
|
|
39
39
|
${chalk.cyan.underline('https://icoa2026.au')}
|
|
40
40
|
|
|
41
|
-
${chalk.gray('CLI-Native Competition Terminal v2.
|
|
41
|
+
${chalk.gray('CLI-Native Competition Terminal v2.7.0')}
|
|
42
42
|
|
|
43
43
|
${LINE}
|
|
44
44
|
`;
|
package/dist/repl.js
CHANGED
|
@@ -128,19 +128,26 @@ export async function startRepl(program, resumeMode) {
|
|
|
128
128
|
if (connected) {
|
|
129
129
|
console.log(chalk.green(` Welcome back, ${config.userName}!`) + ' ' + modeLabel);
|
|
130
130
|
console.log(chalk.gray(` Connected to ${config.ctfdUrl}`));
|
|
131
|
-
console.log(
|
|
131
|
+
console.log();
|
|
132
|
+
console.log(chalk.gray(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
133
|
+
console.log(chalk.white(' exam list ') + chalk.gray('View available exams'));
|
|
134
|
+
console.log(chalk.white(' exam demo ') + chalk.gray('Free practice (no login)'));
|
|
135
|
+
console.log(chalk.white(' help ') + chalk.gray('All commands'));
|
|
136
|
+
console.log(chalk.gray(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
132
137
|
console.log();
|
|
133
138
|
}
|
|
134
139
|
else {
|
|
135
140
|
console.log(' ' + modeLabel);
|
|
136
141
|
console.log();
|
|
137
|
-
console.log(chalk.
|
|
138
|
-
console.log(chalk.
|
|
142
|
+
console.log(chalk.gray(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
143
|
+
console.log(chalk.white(' Welcome! Type a command to get started:'));
|
|
139
144
|
console.log();
|
|
140
|
-
console.log(chalk.gray('
|
|
141
|
-
console.log(chalk.
|
|
145
|
+
console.log(chalk.bold.cyan(' demo') + chalk.gray(' Free practice exam (30 questions)'));
|
|
146
|
+
console.log(chalk.gray(' No account needed. Try it now!'));
|
|
142
147
|
console.log();
|
|
143
|
-
console.log(chalk.
|
|
148
|
+
console.log(chalk.white(' join <url>') + chalk.gray(' Connect to real exam server'));
|
|
149
|
+
console.log(chalk.gray(' Your proctor will provide credentials.'));
|
|
150
|
+
console.log(chalk.gray(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
144
151
|
console.log();
|
|
145
152
|
}
|
|
146
153
|
}
|
|
@@ -266,8 +273,8 @@ export async function startRepl(program, resumeMode) {
|
|
|
266
273
|
}
|
|
267
274
|
const cmd = input.split(/\s+/)[0].toLowerCase();
|
|
268
275
|
// โโโ Mode-based command filtering โโโ
|
|
269
|
-
const selectionCommands = ['join', 'exam', 'setup', 'lang', 'ref', 'ctf'];
|
|
270
|
-
const organizerCommands = ['join', 'exam', 'setup', 'lang', 'ref', 'ctf'];
|
|
276
|
+
const selectionCommands = ['join', 'exam', 'demo', 'next', 'prev', 'setup', 'lang', 'ref', 'ctf'];
|
|
277
|
+
const organizerCommands = ['join', 'exam', 'demo', 'next', 'prev', 'setup', 'lang', 'ref', 'ctf'];
|
|
271
278
|
if (mode === 'selection' && !selectionCommands.includes(cmd)) {
|
|
272
279
|
console.log(chalk.gray(' Not available in Selection mode. Switch via: setup'));
|
|
273
280
|
console.log();
|
|
@@ -296,7 +303,7 @@ export async function startRepl(program, resumeMode) {
|
|
|
296
303
|
'scoreboard', 'sb', 'status', 'time', 'hint', 'hint-b', 'hint-c',
|
|
297
304
|
'hint-budget', 'ref', 'shell', 'files', 'connect', 'note',
|
|
298
305
|
'log', 'lang', 'setup', 'env', 'ai4ctf', 'model', 'ctf',
|
|
299
|
-
'exam',
|
|
306
|
+
'exam', 'demo', 'next', 'prev',
|
|
300
307
|
];
|
|
301
308
|
if (!knownCommands.includes(cmd)) {
|
|
302
309
|
// Block dangerous commands
|
|
@@ -430,6 +437,9 @@ function mapCommand(input) {
|
|
|
430
437
|
const cmd = parts[0].toLowerCase();
|
|
431
438
|
const rest = parts.slice(1);
|
|
432
439
|
const ctfShortcuts = {
|
|
440
|
+
'demo': ['exam', 'demo'],
|
|
441
|
+
'next': ['exam', 'next'],
|
|
442
|
+
'prev': ['exam', 'prev'],
|
|
433
443
|
'join': ['ctf', 'join', ...rest],
|
|
434
444
|
'activate': ['ctf', 'activate', ...rest],
|
|
435
445
|
'challenges': ['ctf', 'challenges'],
|