icoa-cli 2.16.17 → 2.17.1
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 +84 -25
- package/dist/commands/ctf4ai-demo.js +12 -0
- package/dist/commands/exam.js +16 -0
- package/package.json +1 -1
package/dist/commands/ai4ctf.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { createChatSession } from '../lib/gemini.js';
|
|
3
|
-
import {
|
|
3
|
+
import { addTokenUsage } from '../lib/budget.js';
|
|
4
4
|
import { getConfig } from '../lib/config.js';
|
|
5
5
|
import { logCommand } from '../lib/logger.js';
|
|
6
|
-
import { printMarkdown, printError
|
|
6
|
+
import { printMarkdown, printError } from '../lib/ui.js';
|
|
7
7
|
function getChallengeContext() {
|
|
8
8
|
const config = getConfig();
|
|
9
9
|
if (config.currentChallengeName && config.currentChallengeCategory) {
|
|
@@ -14,24 +14,60 @@ function getChallengeContext() {
|
|
|
14
14
|
// Chat state — shared with REPL
|
|
15
15
|
let chatActive = false;
|
|
16
16
|
let chatSession = null;
|
|
17
|
+
let chatTokensUsed = 0;
|
|
18
|
+
const DEMO_TOKEN_CAP = 5000;
|
|
17
19
|
export function isChatActive() {
|
|
18
20
|
return chatActive;
|
|
19
21
|
}
|
|
22
|
+
function drawTokenBar() {
|
|
23
|
+
const cap = DEMO_TOKEN_CAP;
|
|
24
|
+
const used = chatTokensUsed;
|
|
25
|
+
const pct = Math.min(Math.round((used / cap) * 100), 100);
|
|
26
|
+
const width = 20;
|
|
27
|
+
const filled = Math.round((pct / 100) * width);
|
|
28
|
+
const empty = width - filled;
|
|
29
|
+
const color = pct > 80 ? chalk.red : pct > 50 ? chalk.yellow : chalk.green;
|
|
30
|
+
console.log(chalk.gray(' Tokens: ') + color('█'.repeat(filled)) + chalk.gray('░'.repeat(empty)) + chalk.gray(` ${used}/${cap} (${pct}%)`));
|
|
31
|
+
}
|
|
20
32
|
export async function handleChatMessage(input) {
|
|
21
33
|
if (!chatSession)
|
|
22
34
|
return 'exit';
|
|
23
35
|
if (input === 'exit' || input === 'back' || input === 'quit') {
|
|
24
36
|
chatActive = false;
|
|
25
37
|
chatSession = null;
|
|
38
|
+
// Anonymous stats
|
|
39
|
+
fetch('https://practice.icoa2026.au:9090/api/icoa/demo-stats', {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: { 'Content-Type': 'application/json' },
|
|
42
|
+
body: JSON.stringify({ type: 'ai4ctf', tokensUsed: chatTokensUsed, timestamp: new Date().toISOString() }),
|
|
43
|
+
signal: AbortSignal.timeout(5000),
|
|
44
|
+
}).catch(() => { });
|
|
45
|
+
console.log();
|
|
46
|
+
console.log(chalk.gray(' ─────────────────────────────────────────'));
|
|
47
|
+
console.log(chalk.white(' AI4CTF Session Report'));
|
|
48
|
+
console.log(chalk.gray(` Tokens used: ${chatTokensUsed}/${DEMO_TOKEN_CAP}`));
|
|
49
|
+
console.log(chalk.gray(` AI Model: Google Gemma 4 (gemma-4-31b-it)`));
|
|
50
|
+
console.log(chalk.gray(' ─────────────────────────────────────────'));
|
|
26
51
|
console.log();
|
|
27
|
-
|
|
52
|
+
console.log(chalk.white(' Next: try ') + chalk.bold.red('ctf4ai') + chalk.white(' — trick the AI into saying "koala"'));
|
|
28
53
|
console.log();
|
|
29
54
|
return 'exit';
|
|
30
55
|
}
|
|
31
|
-
if (
|
|
56
|
+
if (chatTokensUsed >= DEMO_TOKEN_CAP) {
|
|
32
57
|
chatActive = false;
|
|
33
58
|
chatSession = null;
|
|
34
|
-
|
|
59
|
+
console.log();
|
|
60
|
+
console.log(chalk.yellow(' Token limit reached!'));
|
|
61
|
+
drawTokenBar();
|
|
62
|
+
console.log();
|
|
63
|
+
console.log(chalk.gray(' ─────────────────────────────────────────'));
|
|
64
|
+
console.log(chalk.white(' AI4CTF Session Report'));
|
|
65
|
+
console.log(chalk.gray(` Tokens used: ${chatTokensUsed}/${DEMO_TOKEN_CAP}`));
|
|
66
|
+
console.log(chalk.gray(` AI Model: Google Gemma 4 (gemma-4-31b-it)`));
|
|
67
|
+
console.log(chalk.gray(' ─────────────────────────────────────────'));
|
|
68
|
+
console.log();
|
|
69
|
+
console.log(chalk.white(' Next: try ') + chalk.bold.red('ctf4ai') + chalk.white(' — trick the AI into saying "koala"'));
|
|
70
|
+
console.log();
|
|
35
71
|
return 'exit';
|
|
36
72
|
}
|
|
37
73
|
logCommand(`ai4ctf: ${input}`);
|
|
@@ -39,11 +75,11 @@ export async function handleChatMessage(input) {
|
|
|
39
75
|
try {
|
|
40
76
|
const response = await chatSession.sendMessage(input);
|
|
41
77
|
process.stdout.write('\x1b[1A\x1b[2K');
|
|
78
|
+
chatTokensUsed += response.tokensUsed;
|
|
42
79
|
addTokenUsage(response.tokensUsed);
|
|
43
|
-
const updated = getTokenUsage();
|
|
44
80
|
console.log();
|
|
45
81
|
printMarkdown(response.text);
|
|
46
|
-
|
|
82
|
+
drawTokenBar();
|
|
47
83
|
console.log();
|
|
48
84
|
}
|
|
49
85
|
catch (err) {
|
|
@@ -59,32 +95,55 @@ export function registerAi4ctfCommand(program) {
|
|
|
59
95
|
.description('Chat with your AI teammate')
|
|
60
96
|
.action(async () => {
|
|
61
97
|
logCommand('ai4ctf');
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
98
|
+
const config = getConfig();
|
|
99
|
+
const modelName = config.geminiModel || 'gemma-4-31b-it';
|
|
100
|
+
// Demo challenge context
|
|
101
|
+
const demoContext = {
|
|
102
|
+
name: 'Hidden Message',
|
|
103
|
+
category: 'Cryptography',
|
|
104
|
+
};
|
|
68
105
|
try {
|
|
69
|
-
chatSession = await createChatSession(
|
|
106
|
+
chatSession = await createChatSession(demoContext);
|
|
70
107
|
}
|
|
71
108
|
catch (err) {
|
|
72
109
|
printError(err.message);
|
|
73
110
|
return;
|
|
74
111
|
}
|
|
75
112
|
chatActive = true;
|
|
76
|
-
|
|
113
|
+
chatTokensUsed = 0;
|
|
114
|
+
// Guided welcome
|
|
77
115
|
console.log();
|
|
78
|
-
console.log(chalk.
|
|
79
|
-
console.log(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
console.log(chalk.
|
|
86
|
-
console.log(chalk.
|
|
87
|
-
console.log(chalk.
|
|
116
|
+
console.log(chalk.green.bold(' ═══ AI4CTF Demo — AI as Your Teammate ═══'));
|
|
117
|
+
console.log();
|
|
118
|
+
console.log(chalk.white(' Here\'s a sample CTF challenge:'));
|
|
119
|
+
console.log();
|
|
120
|
+
console.log(chalk.cyan(' ┌─────────────────────────────────────────────────┐'));
|
|
121
|
+
console.log(chalk.cyan(' │') + chalk.bold.white(' Challenge: Hidden Message [Cryptography] ') + chalk.cyan('│'));
|
|
122
|
+
console.log(chalk.cyan(' │') + chalk.white(' ') + chalk.cyan('│'));
|
|
123
|
+
console.log(chalk.cyan(' │') + chalk.white(' You intercepted this encoded text: ') + chalk.cyan('│'));
|
|
124
|
+
console.log(chalk.cyan(' │') + chalk.green(' aWNvYXt3M2xjMG1lXzJfYWk0Y3RmfQ== ') + chalk.cyan('│'));
|
|
125
|
+
console.log(chalk.cyan(' │') + chalk.white(' ') + chalk.cyan('│'));
|
|
126
|
+
console.log(chalk.cyan(' │') + chalk.white(' Can you decode it to find the flag? ') + chalk.cyan('│'));
|
|
127
|
+
console.log(chalk.cyan(' │') + chalk.gray(' Flag format: icoa{...} ') + chalk.cyan('│'));
|
|
128
|
+
console.log(chalk.cyan(' └─────────────────────────────────────────────────┘'));
|
|
129
|
+
console.log();
|
|
130
|
+
console.log(chalk.white(' In competition, you get AI help at 3 levels:'));
|
|
131
|
+
console.log();
|
|
132
|
+
console.log(chalk.yellow(' hint a') + chalk.gray(' General guidance — "What type of encoding is this?"'));
|
|
133
|
+
console.log(chalk.gray(' 50 uses per competition. Safe to use freely.'));
|
|
134
|
+
console.log(chalk.yellow(' hint b') + chalk.gray(' Deep analysis — "How do I decode Base64?"'));
|
|
135
|
+
console.log(chalk.gray(' 10 uses. Use when you\'re stuck.'));
|
|
136
|
+
console.log(chalk.yellow(' hint c') + chalk.gray(' Critical assist — Nearly gives you the answer'));
|
|
137
|
+
console.log(chalk.gray(' 2 uses only. Last resort!'));
|
|
138
|
+
console.log();
|
|
139
|
+
console.log(chalk.gray(' ─────────────────────────────────────────'));
|
|
140
|
+
console.log(chalk.white(' Try now: ask the AI anything about the challenge above.'));
|
|
141
|
+
console.log(chalk.gray(' Example: "What encoding is aWNvYXt3M2xjMG1lXzJfYWk0Y3RmfQ==?"'));
|
|
142
|
+
console.log(chalk.gray(' Or just chat freely! You can also try hint a, hint b, hint c.'));
|
|
143
|
+
console.log();
|
|
144
|
+
drawTokenBar();
|
|
145
|
+
console.log(chalk.gray(` AI Model: Google Gemma 4 (${modelName})`));
|
|
146
|
+
console.log(chalk.gray(' Type "exit" when done.'));
|
|
88
147
|
console.log();
|
|
89
148
|
});
|
|
90
149
|
}
|
|
@@ -27,6 +27,12 @@ export async function handleCtf4aiMessage(input) {
|
|
|
27
27
|
if (input === 'exit' || input === 'back' || input === 'quit') {
|
|
28
28
|
ctf4aiActive = false;
|
|
29
29
|
ctf4aiSession = null;
|
|
30
|
+
fetch('https://practice.icoa2026.au:9090/api/icoa/demo-stats', {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
headers: { 'Content-Type': 'application/json' },
|
|
33
|
+
body: JSON.stringify({ type: 'ctf4ai', solved: false, tokensUsed: ctf4aiTokens, timestamp: new Date().toISOString() }),
|
|
34
|
+
signal: AbortSignal.timeout(5000),
|
|
35
|
+
}).catch(() => { });
|
|
30
36
|
console.log();
|
|
31
37
|
console.log(chalk.gray(' CTF4AI challenge ended.'));
|
|
32
38
|
console.log(chalk.gray(` Tokens used: ${ctf4aiTokens}/${CTF4AI_TOKEN_LIMIT}`));
|
|
@@ -67,6 +73,12 @@ export async function handleCtf4aiMessage(input) {
|
|
|
67
73
|
console.log();
|
|
68
74
|
ctf4aiActive = false;
|
|
69
75
|
ctf4aiSession = null;
|
|
76
|
+
fetch('https://practice.icoa2026.au:9090/api/icoa/demo-stats', {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers: { 'Content-Type': 'application/json' },
|
|
79
|
+
body: JSON.stringify({ type: 'ctf4ai', solved: true, tokensUsed: ctf4aiTokens, timestamp: new Date().toISOString() }),
|
|
80
|
+
signal: AbortSignal.timeout(5000),
|
|
81
|
+
}).catch(() => { });
|
|
70
82
|
return 'solved';
|
|
71
83
|
}
|
|
72
84
|
return 'continue';
|
package/dist/commands/exam.js
CHANGED
|
@@ -581,6 +581,22 @@ export function registerExamCommand(program) {
|
|
|
581
581
|
}
|
|
582
582
|
drawProgress(100, 'Complete!');
|
|
583
583
|
console.log();
|
|
584
|
+
// Anonymous stats (fire-and-forget)
|
|
585
|
+
const helpInfo = getHelpState(state);
|
|
586
|
+
const statsLang = getConfig().language || 'en';
|
|
587
|
+
fetch('https://practice.icoa2026.au:9090/api/icoa/demo-stats', {
|
|
588
|
+
method: 'POST',
|
|
589
|
+
headers: { 'Content-Type': 'application/json' },
|
|
590
|
+
body: JSON.stringify({
|
|
591
|
+
score,
|
|
592
|
+
total: state.questions.length,
|
|
593
|
+
lang: statsLang,
|
|
594
|
+
helpUsed: helpInfo.used,
|
|
595
|
+
helpMax: helpInfo.max,
|
|
596
|
+
timestamp: new Date().toISOString(),
|
|
597
|
+
}),
|
|
598
|
+
signal: AbortSignal.timeout(5000),
|
|
599
|
+
}).catch(() => { });
|
|
584
600
|
clearExamState();
|
|
585
601
|
const pct = Math.round(score / state.questions.length * 100);
|
|
586
602
|
console.log();
|