icoa-cli 2.19.34 → 2.19.35

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.
@@ -17,6 +17,10 @@ function getChallengeContext() {
17
17
  let chatActive = false;
18
18
  let chatSession = null;
19
19
  let chatTokensUsed = 0;
20
+ // Set true when the user burns through DEMO_TOKEN_CAP without solving. The
21
+ // chat session stays alive so `!<shell>` and `submit <flag>` still work, but
22
+ // further AI messages are blocked. See the reveal path in handleChatMessage.
23
+ let tokensLocked = false;
20
24
  const DEMO_TOKEN_CAP = 5000;
21
25
  export function isChatActive() {
22
26
  return chatActive;
@@ -53,14 +57,46 @@ const DEMO_HINTS = {
53
57
  'For simple challenges like this one, hint c adds nothing extra:',
54
58
  'hint b already tells you everything you need.',
55
59
  '',
56
- 'If you do not remember how to decode Base64, ask your AI teammate —',
57
- 'just type a question like: how do I decode Base64 on the command line?',
60
+ 'Two ways forward from here:',
58
61
  '',
59
- 'To run a shell command inside ai4ctf, prefix it with "!". For example:',
62
+ ' 1. Chat with your AI teammate in natural language. Examples:',
63
+ ' what is the base64 command?',
64
+ ' how do I decode Base64 on macOS?',
65
+ ' Just type the question — anything without "!" goes to the AI.',
60
66
  '',
61
- ' !echo aWNvYXt3M2xjMG1lXzJfYWk0Y3RmfQ== | base64 -d',
67
+ ' 2. Run a shell command directly. Shell commands inside ai4ctf',
68
+ ' must start with "!", otherwise your text goes to the AI. Example:',
69
+ '',
70
+ ' !echo aWNvYXt3M2xjMG1lXzJfYWk0Y3RmfQ== | base64 -d',
62
71
  ],
63
72
  };
73
+ // Shown when the user hits the 5000-token demo cap without solving. Keeps
74
+ // the session alive (no chatActive=false) so they can still paste the shell
75
+ // command below and then `submit <flag>`.
76
+ function showTokenCapReveal() {
77
+ console.log();
78
+ console.log(chalk.yellow(' ─────────────────────────────────────────────'));
79
+ console.log(chalk.bold.yellow(' 💡 Out of AI tokens — here is the reveal'));
80
+ console.log(chalk.yellow(' ─────────────────────────────────────────────'));
81
+ console.log();
82
+ console.log(chalk.white(' Looks like you have not found the flag yet.'));
83
+ console.log(chalk.white(' No worries — for the demo we will just tell you.'));
84
+ console.log();
85
+ console.log(chalk.white(' Run this command to decode the Base64 string:'));
86
+ console.log();
87
+ console.log(chalk.cyan(' !echo aWNvYXt3M2xjMG1lXzJfYWk0Y3RmfQ== | base64 -d'));
88
+ console.log();
89
+ console.log(chalk.white(' You will see the flag:'));
90
+ console.log();
91
+ console.log(chalk.green(' icoa{w3lc0me_2_ai4ctf}'));
92
+ console.log();
93
+ console.log(chalk.white(' Then submit it:'));
94
+ console.log();
95
+ console.log(chalk.cyan(' submit icoa{w3lc0me_2_ai4ctf}'));
96
+ console.log();
97
+ console.log(chalk.gray(' (AI chat is locked — only shell commands and submit work now.)'));
98
+ console.log();
99
+ }
64
100
  function showDemoHint(tier) {
65
101
  const title = tier === 'a' ? t('ai4ctfHintA') : tier === 'b' ? t('ai4ctfHintB') : t('ai4ctfHintC');
66
102
  const tierLabel = `Hint ${tier.toUpperCase()}`;
@@ -214,12 +250,14 @@ export async function handleChatMessage(input) {
214
250
  console.log();
215
251
  return 'exit';
216
252
  }
217
- if (chatTokensUsed >= DEMO_TOKEN_CAP) {
218
- chatActive = false;
219
- chatSession = null;
220
- // Report the token-cap session so the server can count "hit the wall"
221
- // outcomes. Without this, a user who burned 5000 tokens without solving
222
- // was invisible to the admin dashboard.
253
+ // Token cap: first hit → reveal the answer, lock AI, but keep the session
254
+ // alive so the user can still paste the shell command and then submit.
255
+ // `tokensLocked` prevents any further sendMessage calls.
256
+ if (!tokensLocked && chatTokensUsed >= DEMO_TOKEN_CAP) {
257
+ tokensLocked = true;
258
+ // Report the token-cap session once (solved:false). If the user then
259
+ // submits the revealed flag, the submit-success path fires another POST
260
+ // with solved:true which is the canonical record.
223
261
  fetch('https://practice.icoa2026.au/api/icoa/demo-stats', {
224
262
  method: 'POST',
225
263
  headers: { 'Content-Type': 'application/json' },
@@ -229,16 +267,18 @@ export async function handleChatMessage(input) {
229
267
  console.log();
230
268
  console.log(chalk.yellow(` ${t('tokenLimit')}`));
231
269
  drawTokenBar();
270
+ showTokenCapReveal();
271
+ return 'continue';
272
+ }
273
+ // If AI is locked (post-reveal), bounce any non-shell, non-submit input
274
+ // back to the user with a reminder of what actually works now.
275
+ if (tokensLocked) {
232
276
  console.log();
233
- console.log(chalk.gray(' ─────────────────────────────────────────'));
234
- console.log(chalk.white(` ${t('ai4ctfReport')}`));
235
- console.log(chalk.gray(` ${t('ai4ctfTokens')}: ${chatTokensUsed}/${DEMO_TOKEN_CAP}`));
236
- console.log(chalk.gray(` ${t('ai4ctfModel')}: Google Gemma 4 (gemma-4-31b-it)`));
237
- console.log(chalk.gray(' ─────────────────────────────────────────'));
238
- console.log();
239
- console.log(chalk.white(` ${t('ai4ctfNext')}`));
277
+ console.log(chalk.yellow(' AI chat is locked — out of tokens.'));
278
+ console.log(chalk.gray(' Use: ') + chalk.cyan('!echo aWNvYXt3M2xjMG1lXzJfYWk0Y3RmfQ== | base64 -d'));
279
+ console.log(chalk.gray(' Then: ') + chalk.cyan('submit icoa{w3lc0me_2_ai4ctf}'));
240
280
  console.log();
241
- return 'exit';
281
+ return 'continue';
242
282
  }
243
283
  console.log(chalk.gray(` ${t('ai4ctfThinking')}`));
244
284
  try {
@@ -280,6 +320,7 @@ export function registerAi4ctfCommand(program) {
280
320
  }
281
321
  chatActive = true;
282
322
  chatTokensUsed = 0;
323
+ tokensLocked = false;
283
324
  // Guided welcome
284
325
  console.log();
285
326
  console.log(chalk.green.bold(` ═══ ${t('ai4ctfTitle')} ═══`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "2.19.34",
3
+ "version": "2.19.35",
4
4
  "description": "ICOA CLI — The world's first CLI-native CTF competition terminal",
5
5
  "type": "module",
6
6
  "bin": {