icoa-cli 2.19.8 → 2.19.9
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/ctf.js +73 -9
- package/dist/commands/hint.js +24 -12
- package/dist/repl.js +56 -38
- package/package.json +1 -1
package/dist/commands/ctf.js
CHANGED
|
@@ -151,10 +151,29 @@ export function registerCtfCommands(program) {
|
|
|
151
151
|
console.log();
|
|
152
152
|
printSuccess('Connection saved. You are ready!');
|
|
153
153
|
console.log();
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
154
|
+
const currentMode = getConfig().mode || '';
|
|
155
|
+
if (currentMode === 'olympiad') {
|
|
156
|
+
// Olympiad: guided step-by-step walkthrough
|
|
157
|
+
console.log(chalk.cyan(' ─────────────────────────────────────────────'));
|
|
158
|
+
console.log(chalk.bold.white(' How to compete:'));
|
|
159
|
+
console.log();
|
|
160
|
+
console.log(chalk.white(' Step 1 ') + chalk.bold.cyan('challenges') + chalk.gray(' Browse all challenges'));
|
|
161
|
+
console.log(chalk.white(' Step 2 ') + chalk.bold.cyan('open <id>') + chalk.gray(' Read challenge details'));
|
|
162
|
+
console.log(chalk.white(' Step 3 ') + chalk.bold.cyan('hint "your question"') + chalk.gray(' Ask AI for help'));
|
|
163
|
+
console.log(chalk.white(' Step 4 ') + chalk.bold.cyan('submit <id> <flag>') + chalk.gray(' Submit your answer'));
|
|
164
|
+
console.log();
|
|
165
|
+
console.log(chalk.gray(' More:'));
|
|
166
|
+
console.log(chalk.white(' scoreboard') + chalk.gray(' Live rankings'));
|
|
167
|
+
console.log(chalk.white(' status') + chalk.gray(' Your score & hint budget'));
|
|
168
|
+
console.log(chalk.white(' ai4ctf') + chalk.gray(' Free-chat with AI teammate'));
|
|
169
|
+
console.log(chalk.cyan(' ─────────────────────────────────────────────'));
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
console.log(chalk.gray(' Next:'));
|
|
173
|
+
console.log(chalk.white(' exam list ') + chalk.gray('View available exams'));
|
|
174
|
+
console.log(chalk.white(' challenges ') + chalk.gray('View CTF challenges'));
|
|
175
|
+
console.log(chalk.white(' status ') + chalk.gray('Check score & budget'));
|
|
176
|
+
}
|
|
158
177
|
}
|
|
159
178
|
catch (err) {
|
|
160
179
|
spinner2.fail('Connection test failed');
|
|
@@ -181,6 +200,11 @@ export function registerCtfCommands(program) {
|
|
|
181
200
|
country: '',
|
|
182
201
|
});
|
|
183
202
|
printSuccess('Logged out. Credentials cleared.');
|
|
203
|
+
console.log();
|
|
204
|
+
console.log(chalk.gray(' What now?'));
|
|
205
|
+
console.log(chalk.white(' join <url>') + chalk.gray(' Re-connect to competition'));
|
|
206
|
+
console.log(chalk.white(' setup') + chalk.gray(' Switch mode'));
|
|
207
|
+
console.log(chalk.white(' exit') + chalk.gray(' Quit ICOA CLI'));
|
|
184
208
|
});
|
|
185
209
|
// ─── icoa ctf activate <code> ───
|
|
186
210
|
ctf
|
|
@@ -251,24 +275,54 @@ export function registerCtfCommands(program) {
|
|
|
251
275
|
}
|
|
252
276
|
const solved = challenges.filter((c) => c.solved_by_me).length;
|
|
253
277
|
printHeader(`Challenges (${solved}/${challenges.length} solved)`);
|
|
278
|
+
// Category descriptions for beginners
|
|
279
|
+
const catDesc = {
|
|
280
|
+
'Web': 'Find vulnerabilities in websites',
|
|
281
|
+
'Crypto': 'Break ciphers & encryption',
|
|
282
|
+
'Reversing': 'Analyze compiled programs',
|
|
283
|
+
'Rev': 'Analyze compiled programs',
|
|
284
|
+
'Pwn': 'Exploit binary vulnerabilities',
|
|
285
|
+
'Forensics': 'Investigate digital evidence',
|
|
286
|
+
'Misc': 'Creative & mixed challenges',
|
|
287
|
+
'OSINT': 'Open-source intelligence gathering',
|
|
288
|
+
'AI': 'AI security & adversarial ML',
|
|
289
|
+
};
|
|
290
|
+
// Difficulty based on points
|
|
291
|
+
const difficulty = (pts) => {
|
|
292
|
+
if (pts <= 100)
|
|
293
|
+
return chalk.green('Easy');
|
|
294
|
+
if (pts <= 250)
|
|
295
|
+
return chalk.yellow('Medium');
|
|
296
|
+
if (pts <= 500)
|
|
297
|
+
return chalk.red('Hard');
|
|
298
|
+
return chalk.magenta('Expert');
|
|
299
|
+
};
|
|
254
300
|
// Sort categories and display
|
|
255
301
|
const rows = [...byCategory.entries()]
|
|
256
302
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
257
303
|
.flatMap(([category, challs]) => {
|
|
258
|
-
const
|
|
304
|
+
const desc = catDesc[category] || '';
|
|
305
|
+
const catLabel = desc
|
|
306
|
+
? chalk.cyan.bold(`── ${category} ──`) + ' ' + chalk.gray(desc)
|
|
307
|
+
: chalk.cyan.bold(`── ${category} ──`);
|
|
308
|
+
const catRow = [catLabel, '', '', '', ''];
|
|
259
309
|
const challRows = challs
|
|
260
310
|
.sort((a, b) => a.value - b.value)
|
|
261
311
|
.map((c) => [
|
|
262
312
|
String(c.id),
|
|
263
313
|
c.solved_by_me ? chalk.gray.strikethrough(c.name) : c.name,
|
|
264
|
-
chalk.gray(c.category),
|
|
265
314
|
c.solved_by_me ? chalk.gray(String(c.value)) : chalk.yellow(String(c.value)),
|
|
315
|
+
c.solved_by_me ? chalk.gray('--') : difficulty(c.value),
|
|
266
316
|
c.solved_by_me ? chalk.green('✓') : chalk.gray('○'),
|
|
267
317
|
]);
|
|
268
318
|
return [catRow, ...challRows];
|
|
269
319
|
});
|
|
270
|
-
printTable(['#', 'Name', '
|
|
320
|
+
printTable(['#', 'Name', 'Points', 'Difficulty', 'Solved'], rows);
|
|
271
321
|
console.log(chalk.gray(` ${challenges.length} challenges, ${solved} solved`));
|
|
322
|
+
if (solved === 0) {
|
|
323
|
+
console.log();
|
|
324
|
+
console.log(chalk.gray(' Tip: Start with ') + chalk.green('Easy') + chalk.gray(' challenges! Type ') + chalk.white('open <id>') + chalk.gray(' to read one.'));
|
|
325
|
+
}
|
|
272
326
|
}
|
|
273
327
|
catch (err) {
|
|
274
328
|
spinner.fail('Failed to load challenges');
|
|
@@ -325,9 +379,19 @@ export function registerCtfCommands(program) {
|
|
|
325
379
|
console.log();
|
|
326
380
|
printInfo(`Quick connect: ${chalk.white(`connect ${id}`)}`);
|
|
327
381
|
}
|
|
328
|
-
// Show
|
|
382
|
+
// Show guided next actions
|
|
329
383
|
console.log();
|
|
330
|
-
console.log(chalk.gray(
|
|
384
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
385
|
+
console.log(chalk.bold.white(' What to do next:'));
|
|
386
|
+
console.log(chalk.white(` hint "how to start?"`) + chalk.gray(' Ask AI for guidance (Level A)'));
|
|
387
|
+
if (challenge.files && challenge.files.length > 0) {
|
|
388
|
+
console.log(chalk.white(` files ${id}`) + chalk.gray(' Download challenge files'));
|
|
389
|
+
}
|
|
390
|
+
if (challenge.connection_info) {
|
|
391
|
+
console.log(chalk.white(` connect ${id}`) + chalk.gray(' Connect to target'));
|
|
392
|
+
}
|
|
393
|
+
console.log(chalk.white(` submit ${id} icoa{flag}`) + chalk.gray(' Submit your answer'));
|
|
394
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
331
395
|
}
|
|
332
396
|
catch (err) {
|
|
333
397
|
spinner.fail('Failed to load challenge');
|
package/dist/commands/hint.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { confirm } from '@inquirer/prompts';
|
|
3
2
|
import { generateHint } from '../lib/gemini.js';
|
|
4
3
|
import { checkBudget, deductBudget, getBudgetDisplay, isTokenCapReached } from '../lib/budget.js';
|
|
5
4
|
import { getConfig } from '../lib/config.js';
|
|
6
5
|
import { logHint } from '../lib/logger.js';
|
|
7
|
-
import { printError,
|
|
6
|
+
import { printError, printMarkdown, printHeader, createSpinner } from '../lib/ui.js';
|
|
8
7
|
function getChallengeContext() {
|
|
9
8
|
const config = getConfig();
|
|
10
9
|
if (config.currentChallengeName && config.currentChallengeCategory) {
|
|
@@ -28,16 +27,9 @@ async function handleHint(level, question) {
|
|
|
28
27
|
printError(`Level ${level} hint budget exhausted (0 remaining).`);
|
|
29
28
|
return;
|
|
30
29
|
}
|
|
31
|
-
// Level C
|
|
30
|
+
// Level C warning (no confirm — crashes REPL)
|
|
32
31
|
if (level === 'C') {
|
|
33
|
-
|
|
34
|
-
message: `This will consume 1 of your ${remaining} remaining Critical Assists. Continue?`,
|
|
35
|
-
default: false,
|
|
36
|
-
});
|
|
37
|
-
if (!proceed) {
|
|
38
|
-
printInfo('Cancelled.');
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
32
|
+
console.log(chalk.red.bold(` Warning: Using 1 of ${remaining} Critical Assists remaining.`));
|
|
41
33
|
}
|
|
42
34
|
// Log BEFORE API call
|
|
43
35
|
logHint(level, question, config.currentChallengeId || undefined);
|
|
@@ -56,8 +48,23 @@ async function handleHint(level, question) {
|
|
|
56
48
|
deductBudget(level, result.tokensUsed);
|
|
57
49
|
printHeader(`Level ${level} Hint — ${levelNames[level]}`);
|
|
58
50
|
printMarkdown(result.text);
|
|
51
|
+
// Budget summary after hint
|
|
52
|
+
const afterBudget = checkBudget(level);
|
|
59
53
|
console.log();
|
|
60
|
-
console.log(chalk.gray(
|
|
54
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
55
|
+
console.log(chalk.gray(` Tokens used: ${result.tokensUsed} | Level ${level} remaining: ${afterBudget.remaining}`));
|
|
56
|
+
// Suggest next level if stuck
|
|
57
|
+
if (level === 'A') {
|
|
58
|
+
const bBudget = checkBudget('B');
|
|
59
|
+
console.log(chalk.gray(' Need more detail? ') + chalk.white('hint-b "your question"') + chalk.gray(` (${bBudget.remaining} left)`));
|
|
60
|
+
}
|
|
61
|
+
else if (level === 'B') {
|
|
62
|
+
const cBudget = checkBudget('C');
|
|
63
|
+
if (cBudget.remaining > 0) {
|
|
64
|
+
console.log(chalk.gray(' Still stuck? ') + chalk.white('hint-c "your question"') + chalk.gray(` (${cBudget.remaining} Critical Assists left)`));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
61
68
|
console.log();
|
|
62
69
|
}
|
|
63
70
|
catch (err) {
|
|
@@ -69,6 +76,11 @@ function showBudget() {
|
|
|
69
76
|
printHeader('Hint Budget');
|
|
70
77
|
console.log(getBudgetDisplay());
|
|
71
78
|
console.log();
|
|
79
|
+
console.log(chalk.gray(' How to use hints:'));
|
|
80
|
+
console.log(chalk.white(' hint "what is XSS?"') + chalk.gray(' Level A — General guidance'));
|
|
81
|
+
console.log(chalk.white(' hint-b "analyze this code"') + chalk.gray(' Level B — Deep analysis'));
|
|
82
|
+
console.log(chalk.white(' hint-c "I\'m completely stuck"') + chalk.gray(' Level C — Critical assist'));
|
|
83
|
+
console.log();
|
|
72
84
|
}
|
|
73
85
|
export function registerHintCommands(program) {
|
|
74
86
|
// ─── icoa hint <question> ───
|
package/dist/repl.js
CHANGED
|
@@ -236,34 +236,49 @@ export async function startRepl(program, resumeMode) {
|
|
|
236
236
|
console.log();
|
|
237
237
|
}
|
|
238
238
|
else if (connected) {
|
|
239
|
-
console.log(chalk.green(` Welcome back, ${config.userName}!`));
|
|
239
|
+
console.log(chalk.green.bold(` Welcome back, ${config.userName}!`));
|
|
240
240
|
console.log(chalk.gray(` Connected to ${config.ctfdUrl}`));
|
|
241
|
-
console.log(
|
|
241
|
+
console.log();
|
|
242
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
243
|
+
console.log(chalk.white(' Ready to compete? Start here:'));
|
|
244
|
+
console.log();
|
|
245
|
+
console.log(chalk.bold.cyan(' challenges') + chalk.gray(' Browse challenges by category'));
|
|
246
|
+
console.log(chalk.white(' status') + chalk.gray(' Your score & hint budget'));
|
|
247
|
+
console.log(chalk.white(' scoreboard') + chalk.gray(' Live rankings'));
|
|
248
|
+
console.log(chalk.white(' help') + chalk.gray(' Full command list'));
|
|
249
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
242
250
|
console.log();
|
|
243
251
|
}
|
|
244
252
|
else if (activated) {
|
|
245
253
|
ensureWorkspace();
|
|
246
|
-
console.log(chalk.green(' Welcome, competitor!
|
|
254
|
+
console.log(chalk.green.bold(' Welcome, competitor!'));
|
|
247
255
|
console.log(chalk.gray(` Workspace: ${WORKSPACE}`));
|
|
248
256
|
console.log();
|
|
249
|
-
console.log(chalk.gray('
|
|
250
|
-
console.log(chalk.
|
|
251
|
-
console.log(
|
|
252
|
-
console.log(chalk.white('
|
|
253
|
-
console.log(chalk.white('
|
|
254
|
-
console.log(chalk.white('
|
|
255
|
-
console.log(
|
|
257
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
258
|
+
console.log(chalk.white(' Get started:'));
|
|
259
|
+
console.log();
|
|
260
|
+
console.log(chalk.white(' Step 1 ') + chalk.bold.cyan('join <url>') + chalk.gray(' Connect to competition server'));
|
|
261
|
+
console.log(chalk.white(' Step 2 ') + chalk.bold.cyan('challenges') + chalk.gray(' Browse & solve challenges'));
|
|
262
|
+
console.log(chalk.white(' Step 3 ') + chalk.bold.cyan('hint') + chalk.gray(' Ask AI when stuck'));
|
|
263
|
+
console.log();
|
|
264
|
+
console.log(chalk.gray(' Also: ') + chalk.white('env') + chalk.gray(' check tools ') + chalk.white('help') + chalk.gray(' all commands'));
|
|
265
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
256
266
|
console.log();
|
|
257
267
|
}
|
|
258
268
|
else {
|
|
259
|
-
console.log(chalk.white(' Welcome to ICOA CLI
|
|
269
|
+
console.log(chalk.bold.white(' Welcome to ICOA CLI — International Olympiad'));
|
|
270
|
+
console.log();
|
|
271
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
272
|
+
console.log(chalk.white(' To begin, activate your competition token:'));
|
|
260
273
|
console.log();
|
|
261
|
-
console.log(chalk.
|
|
262
|
-
console.log(
|
|
263
|
-
console.log(chalk.
|
|
264
|
-
console.log(chalk.white('
|
|
265
|
-
console.log(chalk.white('
|
|
266
|
-
console.log(chalk.white('
|
|
274
|
+
console.log(chalk.bold.cyan(' activate <token>'));
|
|
275
|
+
console.log();
|
|
276
|
+
console.log(chalk.gray(' While waiting, explore:'));
|
|
277
|
+
console.log(chalk.white(' ref linux') + chalk.gray(' Quick reference for Linux'));
|
|
278
|
+
console.log(chalk.white(' ref web') + chalk.gray(' Quick reference for Web'));
|
|
279
|
+
console.log(chalk.white(' env') + chalk.gray(' Check your tools'));
|
|
280
|
+
console.log(chalk.white(' help') + chalk.gray(' All available commands'));
|
|
281
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
267
282
|
console.log();
|
|
268
283
|
}
|
|
269
284
|
}
|
|
@@ -729,33 +744,35 @@ function printReplHelp(activated, mode = 'olympiad') {
|
|
|
729
744
|
console.log();
|
|
730
745
|
return;
|
|
731
746
|
}
|
|
747
|
+
// How it works — workflow overview
|
|
748
|
+
console.log(chalk.cyan(' ═══════════════════════════════════════════════'));
|
|
749
|
+
console.log(chalk.bold.white(' How it works'));
|
|
750
|
+
console.log();
|
|
751
|
+
console.log(chalk.gray(' 1. Browse ') + chalk.white('challenges') + chalk.gray(' and pick one'));
|
|
752
|
+
console.log(chalk.gray(' 2. ') + chalk.white('open <id>') + chalk.gray(' to read the challenge'));
|
|
753
|
+
console.log(chalk.gray(' 3. Use ') + chalk.white('hint') + chalk.gray(' / ') + chalk.white('hint-b') + chalk.gray(' / ') + chalk.white('hint-c') + chalk.gray(' when stuck'));
|
|
754
|
+
console.log(chalk.gray(' 4. ') + chalk.white('submit <id> icoa{flag}') + chalk.gray(' to score points'));
|
|
755
|
+
console.log(chalk.gray(' 5. Check ') + chalk.white('scoreboard') + chalk.gray(' to track your rank'));
|
|
756
|
+
console.log(chalk.cyan(' ═══════════════════════════════════════════════'));
|
|
757
|
+
console.log();
|
|
732
758
|
console.log(chalk.bold.white(' Competition'));
|
|
733
759
|
console.log(chalk.white(' join <url> ') + chalk.gray('Connect to CTFd'));
|
|
734
|
-
console.log(chalk.white(' challenges (ch) ') + chalk.gray('List challenges'));
|
|
735
|
-
console.log(chalk.white(' open <id> ') + chalk.gray('
|
|
760
|
+
console.log(chalk.white(' challenges (ch) ') + chalk.gray('List challenges by category'));
|
|
761
|
+
console.log(chalk.white(' open <id> ') + chalk.gray('Read challenge + get next steps'));
|
|
736
762
|
console.log(chalk.white(' submit <id> <flag> ') + chalk.gray('Submit a flag'));
|
|
737
|
-
console.log(chalk.white(' scoreboard (sb) ') + chalk.gray('
|
|
738
|
-
console.log(chalk.white(' status ') + chalk.gray('
|
|
763
|
+
console.log(chalk.white(' scoreboard (sb) ') + chalk.gray('Live rankings'));
|
|
764
|
+
console.log(chalk.white(' status ') + chalk.gray('Your score, budget & timer'));
|
|
739
765
|
console.log(chalk.white(' time ') + chalk.gray('Countdown timer'));
|
|
740
766
|
console.log();
|
|
741
|
-
console.log(chalk.bold.white('
|
|
742
|
-
console.log(chalk.white('
|
|
743
|
-
console.log(chalk.white('
|
|
744
|
-
console.log(chalk.white('
|
|
745
|
-
console.log(chalk.white('
|
|
746
|
-
console.log(chalk.white('
|
|
747
|
-
console.log(chalk.white(' exam submit ') + chalk.gray('Submit for grading'));
|
|
748
|
-
console.log(chalk.white(' exam result ') + chalk.gray('View your score'));
|
|
749
|
-
console.log();
|
|
750
|
-
console.log(chalk.bold.white(' AI'));
|
|
751
|
-
console.log(chalk.white(' ai4ctf ') + chalk.gray('Chat with your AI teammate'));
|
|
752
|
-
console.log(chalk.white(' hint <question> ') + chalk.gray('Level A — General guidance'));
|
|
753
|
-
console.log(chalk.white(' hint-b <question> ') + chalk.gray('Level B — Deep analysis'));
|
|
754
|
-
console.log(chalk.white(' hint-c <question> ') + chalk.gray('Level C — Critical assist'));
|
|
755
|
-
console.log(chalk.white(' hint budget ') + chalk.gray('Check remaining budget'));
|
|
767
|
+
console.log(chalk.bold.white(' AI Teammate') + chalk.gray(' — 3 levels, use wisely'));
|
|
768
|
+
console.log(chalk.white(' hint "question" ') + chalk.gray('Level A — General guidance (50 uses)'));
|
|
769
|
+
console.log(chalk.white(' hint-b "question" ') + chalk.gray('Level B — Deep analysis (10 uses)'));
|
|
770
|
+
console.log(chalk.white(' hint-c "question" ') + chalk.gray('Level C — Critical assist (2 uses)'));
|
|
771
|
+
console.log(chalk.white(' hint budget ') + chalk.gray('Check remaining uses'));
|
|
772
|
+
console.log(chalk.white(' ai4ctf ') + chalk.gray('Free-chat with AI (no limit)'));
|
|
756
773
|
console.log();
|
|
757
774
|
console.log(chalk.bold.white(' Tools'));
|
|
758
|
-
console.log(chalk.white(' ref [topic] ') + chalk.gray('Quick reference'));
|
|
775
|
+
console.log(chalk.white(' ref [topic] ') + chalk.gray('Quick reference (linux, web, crypto...)'));
|
|
759
776
|
console.log(chalk.white(' shell ') + chalk.gray('Docker sandbox'));
|
|
760
777
|
console.log(chalk.white(' files <id> ') + chalk.gray('Download challenge files'));
|
|
761
778
|
console.log(chalk.white(' connect <id> ') + chalk.gray('Connect to remote target'));
|
|
@@ -764,7 +781,8 @@ function printReplHelp(activated, mode = 'olympiad') {
|
|
|
764
781
|
console.log();
|
|
765
782
|
console.log(chalk.bold.white(' System'));
|
|
766
783
|
console.log(chalk.white(' setup ') + chalk.gray('Configure settings'));
|
|
767
|
-
console.log(chalk.white(' lang [code] ') + chalk.gray('Switch language'));
|
|
784
|
+
console.log(chalk.white(' lang [code] ') + chalk.gray('Switch language (15 supported)'));
|
|
785
|
+
console.log(chalk.white(' logout ') + chalk.gray('Disconnect'));
|
|
768
786
|
console.log(chalk.white(' clear ') + chalk.gray('Clear screen'));
|
|
769
787
|
console.log(chalk.white(' exit ') + chalk.gray('Quit (session saved)'));
|
|
770
788
|
console.log();
|