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.
@@ -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
- console.log(chalk.gray(' Next:'));
155
- console.log(chalk.white(' exam list ') + chalk.gray('View available exams'));
156
- console.log(chalk.white(' challenges ') + chalk.gray('View CTF challenges'));
157
- console.log(chalk.white(' status ') + chalk.gray('Check score & budget'));
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 catRow = [chalk.cyan.bold(`── ${category} ──`), '', '', '', ''];
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', 'Category', 'Points', 'Solved'], rows);
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 helpful next actions
382
+ // Show guided next actions
329
383
  console.log();
330
- console.log(chalk.gray(` Next: hint "how to approach this?" | submit ${id} "icoa{flag}"`));
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');
@@ -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, printInfo, printMarkdown, printHeader, createSpinner } from '../lib/ui.js';
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 confirmation
30
+ // Level C warning (no confirm — crashes REPL)
32
31
  if (level === 'C') {
33
- const proceed = await confirm({
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(` Tokens used: ${result.tokensUsed} | Level ${level} remaining: ${checkBudget(level).remaining}`));
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(chalk.gray(' logout to disconnect'));
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! Ready to hack.'));
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(' Quick Start'));
250
- console.log(chalk.gray(' ─────────────'));
251
- console.log(chalk.white(' join <url> ') + chalk.gray('Connect to competition'));
252
- console.log(chalk.white(' challenges ') + chalk.gray('View challenges'));
253
- console.log(chalk.white(' hint <question> ') + chalk.gray('Ask AI for help'));
254
- console.log(chalk.white(' env ') + chalk.gray('Check your tools'));
255
- console.log(chalk.white(' help ') + chalk.gray('All commands'));
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.gray(' Quick Start'));
262
- console.log(chalk.gray(' ─────────────'));
263
- console.log(chalk.white(' activate <token> ') + chalk.gray('Unlock with your access token'));
264
- console.log(chalk.white(' ref <topic> ') + chalk.gray('Browse tool references'));
265
- console.log(chalk.white(' env ') + chalk.gray('Check your tools'));
266
- console.log(chalk.white(' help ') + chalk.gray('All commands'));
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('View challenge details'));
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('View scoreboard'));
738
- console.log(chalk.white(' status ') + chalk.gray('Competition status'));
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(' Exam'));
742
- console.log(chalk.white(' exam list ') + chalk.gray('Available exams'));
743
- console.log(chalk.white(' exam start <id> ') + chalk.gray('Begin an exam'));
744
- console.log(chalk.white(' exam q [n] ') + chalk.gray('View questions'));
745
- console.log(chalk.white(' exam answer <n> <X> ') + chalk.gray('Answer question'));
746
- console.log(chalk.white(' exam review ') + chalk.gray('Review all answers'));
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "2.19.8",
3
+ "version": "2.19.9",
4
4
  "description": "ICOA CLI — The world's first CLI-native CTF competition terminal",
5
5
  "type": "module",
6
6
  "bin": {