icoa-cli 2.19.57 → 2.19.59
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 +27 -0
- package/dist/commands/ctf4ai-demo.js +26 -0
- package/dist/commands/exam.js +26 -9
- package/package.json +1 -1
package/dist/commands/ai4ctf.js
CHANGED
|
@@ -300,6 +300,33 @@ export function registerAi4ctfCommand(program) {
|
|
|
300
300
|
.description('Chat with your AI teammate')
|
|
301
301
|
.action(async () => {
|
|
302
302
|
logCommand('ai4ctf');
|
|
303
|
+
// Block mid-exam: ai4ctf starts a scripted demo challenge, which would
|
|
304
|
+
// interrupt a real exam session. Redirect to the actual exam AI tool.
|
|
305
|
+
const { getRealExamState } = await import('../lib/exam-state.js');
|
|
306
|
+
const realExam = getRealExamState();
|
|
307
|
+
if (realExam) {
|
|
308
|
+
const currentQ = realExam._lastQ || 1;
|
|
309
|
+
const inAi4ctfRange = currentQ >= 31 && currentQ <= 38;
|
|
310
|
+
console.log();
|
|
311
|
+
console.log(chalk.yellow(' ⚠ You are in a real exam — ai4ctf demo is blocked here.'));
|
|
312
|
+
console.log();
|
|
313
|
+
if (inAi4ctfRange) {
|
|
314
|
+
console.log(chalk.white(` You are on Q${currentQ} (AI4CTF section).`));
|
|
315
|
+
console.log(chalk.white(' To ask the AI about this question:'));
|
|
316
|
+
console.log(chalk.gray(' → ') + chalk.bold.cyan(`hint "your question"`));
|
|
317
|
+
console.log(chalk.gray(' Example: ') + chalk.green(`hint "how do I decrypt AES-CBC in Python?"`));
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
console.log(chalk.white(` You are on Q${currentQ}. The AI4CTF section is Q31–38.`));
|
|
321
|
+
console.log(chalk.gray(' Jump there: ') + chalk.bold.cyan('exam q 31'));
|
|
322
|
+
console.log(chalk.gray(' Then ask the AI with: ') + chalk.white('hint "your question"'));
|
|
323
|
+
}
|
|
324
|
+
console.log();
|
|
325
|
+
console.log(chalk.gray(' ai4ctf as a chat command is demo-only. In the real exam,'));
|
|
326
|
+
console.log(chalk.gray(' `hint` is the AI interface — 25K AI4CTF + 25K CTF4AI tokens.'));
|
|
327
|
+
console.log();
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
303
330
|
const config = getConfig();
|
|
304
331
|
const modelName = config.geminiModel || 'gemma-4-31b-it';
|
|
305
332
|
// Demo challenge context
|
|
@@ -170,6 +170,32 @@ export function registerCtf4aiDemoCommand(program) {
|
|
|
170
170
|
.description('CTF4AI Demo — Prompt injection challenge')
|
|
171
171
|
.action(async () => {
|
|
172
172
|
logCommand('ctf4ai');
|
|
173
|
+
// Block mid-exam: ctf4ai starts a scripted koala demo, would derail
|
|
174
|
+
// a real exam session and burn AI tokens against the wrong budget.
|
|
175
|
+
const { getRealExamState } = await import('../lib/exam-state.js');
|
|
176
|
+
const realExam = getRealExamState();
|
|
177
|
+
if (realExam) {
|
|
178
|
+
const currentQ = realExam._lastQ || 1;
|
|
179
|
+
const inCtf4aiRange = currentQ >= 39;
|
|
180
|
+
console.log();
|
|
181
|
+
console.log(chalk.yellow(' ⚠ You are in a real exam — ctf4ai demo is blocked here.'));
|
|
182
|
+
console.log();
|
|
183
|
+
if (inCtf4aiRange) {
|
|
184
|
+
console.log(chalk.white(` You are on Q${currentQ} (CTF4AI section).`));
|
|
185
|
+
console.log(chalk.white(' The question scenario itself is the AI target — read Q39/Q40'));
|
|
186
|
+
console.log(chalk.white(' carefully. To ask the AI assistant for help:'));
|
|
187
|
+
console.log(chalk.gray(' → ') + chalk.bold.cyan(`hint "your question"`));
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
console.log(chalk.white(` You are on Q${currentQ}. The CTF4AI section is Q39–40.`));
|
|
191
|
+
console.log(chalk.gray(' Jump there: ') + chalk.bold.cyan('exam q 39'));
|
|
192
|
+
}
|
|
193
|
+
console.log();
|
|
194
|
+
console.log(chalk.gray(' ctf4ai as a standalone command is demo-only. In the real exam,'));
|
|
195
|
+
console.log(chalk.gray(' each Q39/Q40 scenario contains its own AI target to attack.'));
|
|
196
|
+
console.log();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
173
199
|
if (ctf4aiActive) {
|
|
174
200
|
console.log(chalk.gray(` ${t('ctf4aiAlready')}`));
|
|
175
201
|
return;
|
package/dist/commands/exam.js
CHANGED
|
@@ -327,10 +327,14 @@ function printQuestionProgress(current, total, answered) {
|
|
|
327
327
|
console.log();
|
|
328
328
|
console.log(` ${bar} ${chalk.white.bold(`${current}`)}${chalk.gray(`/${total}`)} ${chalk.gray(`(${answered} answered)`)} ${chalk.gray(`${pct}%`)}`);
|
|
329
329
|
}
|
|
330
|
+
// Help budget per exam.md §3: 10 base + 5 hidden bonus (unlocked via `more help`).
|
|
331
|
+
// Demo still uses the lighter 5 + 3 set via _helpMax overrides at demo start.
|
|
332
|
+
const HELP_BASE = 10;
|
|
333
|
+
const HELP_WITH_BONUS = 15;
|
|
330
334
|
function getHelpState(state) {
|
|
331
335
|
return {
|
|
332
336
|
used: state._helpUsed || 0,
|
|
333
|
-
max: state._helpMax ||
|
|
337
|
+
max: state._helpMax || HELP_BASE,
|
|
334
338
|
perQ: state._helpPerQ || {},
|
|
335
339
|
eliminated: state._eliminated || {},
|
|
336
340
|
};
|
|
@@ -488,9 +492,12 @@ function printQuestion(q, answer) {
|
|
|
488
492
|
console.log(chalk.yellow(' !python3') + chalk.gray(' start Python'));
|
|
489
493
|
}
|
|
490
494
|
else {
|
|
495
|
+
// "used up" prompt shows until the bonus tier is reached; after that
|
|
496
|
+
// (already unlocked), show the full-used final state.
|
|
497
|
+
const bonusUnlocked = help.max >= HELP_WITH_BONUS;
|
|
491
498
|
const helpLabel = remaining > 0
|
|
492
499
|
? chalk.gray(`${t('helpRemove')} `) + chalk.yellow(`(${remaining}/${help.max})`)
|
|
493
|
-
: (
|
|
500
|
+
: (!bonusUnlocked ? chalk.gray(`${t('helpUsedUp')}`) : chalk.gray(`${t('helpAllUsed')} (${help.used}/${help.max})`));
|
|
494
501
|
console.log(chalk.yellow(' A/B/C/D') + chalk.gray(` ${t('answerThis')}`));
|
|
495
502
|
console.log(chalk.yellow(' help') + ' ' + helpLabel);
|
|
496
503
|
}
|
|
@@ -835,10 +842,12 @@ export function registerExamCommand(program) {
|
|
|
835
842
|
}
|
|
836
843
|
// Check budget
|
|
837
844
|
if (help.used >= help.max) {
|
|
838
|
-
|
|
845
|
+
const bonusUnlocked = help.max >= HELP_WITH_BONUS;
|
|
846
|
+
const bonusRemaining = HELP_WITH_BONUS - help.max;
|
|
847
|
+
if (!bonusUnlocked) {
|
|
839
848
|
console.log();
|
|
840
849
|
console.log(chalk.yellow(` Help used: ${help.used}/${help.max}`));
|
|
841
|
-
console.log(chalk.white(' Need more? Type: ') + chalk.bold.yellow('more help') + chalk.gray(
|
|
850
|
+
console.log(chalk.white(' Need more? Type: ') + chalk.bold.yellow('more help') + chalk.gray(` (+${bonusRemaining} bonus uses)`));
|
|
842
851
|
console.log();
|
|
843
852
|
}
|
|
844
853
|
else {
|
|
@@ -904,7 +913,7 @@ export function registerExamCommand(program) {
|
|
|
904
913
|
// ─── exam more-help ───
|
|
905
914
|
exam
|
|
906
915
|
.command('more-help')
|
|
907
|
-
.description('Unlock
|
|
916
|
+
.description('Unlock hidden bonus help uses')
|
|
908
917
|
.action(() => {
|
|
909
918
|
logCommand('exam more-help');
|
|
910
919
|
const state = getExamState();
|
|
@@ -913,7 +922,12 @@ export function registerExamCommand(program) {
|
|
|
913
922
|
return;
|
|
914
923
|
}
|
|
915
924
|
const help = getHelpState(state);
|
|
916
|
-
|
|
925
|
+
// Demo (5→8) and real exam (10→15) each add their own bonus tier. Pick
|
|
926
|
+
// the target based on what's already in state: demo-free exam keeps the
|
|
927
|
+
// smaller step, real exams get the full 15.
|
|
928
|
+
const isDemo = state.session.examId === 'demo-free';
|
|
929
|
+
const bonusTarget = isDemo ? 8 : HELP_WITH_BONUS;
|
|
930
|
+
if (help.max >= bonusTarget) {
|
|
917
931
|
console.log(chalk.gray(' Bonus help already unlocked.'));
|
|
918
932
|
return;
|
|
919
933
|
}
|
|
@@ -921,10 +935,11 @@ export function registerExamCommand(program) {
|
|
|
921
935
|
console.log(chalk.gray(` You still have ${help.max - help.used} helps remaining. Use them first!`));
|
|
922
936
|
return;
|
|
923
937
|
}
|
|
924
|
-
|
|
938
|
+
const bonusDelta = bonusTarget - help.max;
|
|
939
|
+
state._helpMax = bonusTarget;
|
|
925
940
|
saveExamState(state);
|
|
926
941
|
console.log();
|
|
927
|
-
console.log(chalk.green(
|
|
942
|
+
console.log(chalk.green(` 🎁 +${bonusDelta} bonus help unlocked! (${bonusDelta}/${bonusTarget} remaining)`));
|
|
928
943
|
console.log(chalk.gray(' Type "help" on any question to use.'));
|
|
929
944
|
console.log();
|
|
930
945
|
});
|
|
@@ -1741,7 +1756,9 @@ export function registerExamCommand(program) {
|
|
|
1741
1756
|
drawProgress(100, 'Ready!');
|
|
1742
1757
|
console.log();
|
|
1743
1758
|
console.log();
|
|
1744
|
-
|
|
1759
|
+
// Demo keeps the lighter 5 + 3 help budget (10 questions, so 5 base =
|
|
1760
|
+
// half the questions; plenty without making the exam trivial).
|
|
1761
|
+
saveExamState({ session, questions: DEMO_QUESTIONS, answers: {}, _helpMax: 5 });
|
|
1745
1762
|
printKeyValue('Questions', String(DEMO_PICK_SIZE));
|
|
1746
1763
|
printKeyValue('Duration', 'No time limit');
|
|
1747
1764
|
// Show first question
|