icoa-cli 2.3.1 → 2.3.3

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.
@@ -1,2 +1,4 @@
1
1
  import { Command } from 'commander';
2
+ export declare function isChatActive(): boolean;
3
+ export declare function handleChatMessage(input: string): Promise<'continue' | 'exit'>;
2
4
  export declare function registerAi4ctfCommand(program: Command): void;
@@ -1,4 +1,3 @@
1
- import { createInterface } from 'node:readline';
2
1
  import chalk from 'chalk';
3
2
  import { createChatSession } from '../lib/gemini.js';
4
3
  import { isTokenCapReached, addTokenUsage, getTokenUsage } from '../lib/budget.js';
@@ -12,28 +11,68 @@ function getChallengeContext() {
12
11
  }
13
12
  return undefined;
14
13
  }
14
+ // Chat state — shared with REPL
15
+ let chatActive = false;
16
+ let chatSession = null;
17
+ export function isChatActive() {
18
+ return chatActive;
19
+ }
20
+ export async function handleChatMessage(input) {
21
+ if (!chatSession)
22
+ return 'exit';
23
+ if (input === 'exit' || input === 'back' || input === 'quit') {
24
+ chatActive = false;
25
+ chatSession = null;
26
+ console.log();
27
+ printInfo('Returning to ICOA terminal.');
28
+ console.log();
29
+ return 'exit';
30
+ }
31
+ if (isTokenCapReached()) {
32
+ chatActive = false;
33
+ chatSession = null;
34
+ printError('Token budget exhausted. Exiting chat mode.');
35
+ return 'exit';
36
+ }
37
+ logCommand(`ai4ctf: ${input}`);
38
+ console.log(chalk.gray(' Thinking...'));
39
+ try {
40
+ const response = await chatSession.sendMessage(input);
41
+ process.stdout.write('\x1b[1A\x1b[2K');
42
+ addTokenUsage(response.tokensUsed);
43
+ const updated = getTokenUsage();
44
+ console.log();
45
+ printMarkdown(response.text);
46
+ console.log(chalk.gray(` [${response.tokensUsed.toLocaleString()} tokens | ${updated.used.toLocaleString()}/${updated.cap.toLocaleString()} total]`));
47
+ console.log();
48
+ }
49
+ catch (err) {
50
+ process.stdout.write('\x1b[1A\x1b[2K');
51
+ printError(`AI error: ${err.message}`);
52
+ console.log();
53
+ }
54
+ return 'continue';
55
+ }
15
56
  export function registerAi4ctfCommand(program) {
16
57
  program
17
58
  .command('ai4ctf')
18
59
  .description('Chat with your AI teammate')
19
60
  .action(async () => {
20
61
  logCommand('ai4ctf');
21
- // Check token cap
22
62
  if (isTokenCapReached()) {
23
63
  printError('Token budget exhausted. No more AI interactions available.');
24
64
  return;
25
65
  }
26
66
  const context = getChallengeContext();
27
67
  const tokenState = getTokenUsage();
28
- // Create chat session
29
- let chat;
30
68
  try {
31
- chat = await createChatSession(context);
69
+ chatSession = await createChatSession(context);
32
70
  }
33
71
  catch (err) {
34
72
  printError(err.message);
35
73
  return;
36
74
  }
75
+ chatActive = true;
37
76
  // Welcome banner
38
77
  console.log();
39
78
  console.log(chalk.magenta(' ┌─────────────────────────────────────────┐'));
@@ -47,64 +86,5 @@ export function registerAi4ctfCommand(program) {
47
86
  console.log(chalk.magenta(' │') + chalk.gray(" Type 'exit' to return".padEnd(41)) + chalk.magenta('│'));
48
87
  console.log(chalk.magenta(' └─────────────────────────────────────────┘'));
49
88
  console.log();
50
- // Inner chat REPL
51
- return new Promise((resolve) => {
52
- const chatRl = createInterface({
53
- input: process.stdin,
54
- output: process.stdout,
55
- prompt: chalk.magenta('ai> '),
56
- terminal: true,
57
- });
58
- chatRl.prompt();
59
- chatRl.on('line', async (line) => {
60
- const input = line.trim();
61
- if (!input) {
62
- chatRl.prompt();
63
- return;
64
- }
65
- // Exit commands
66
- if (input === 'exit' || input === 'back' || input === 'quit') {
67
- chatRl.close();
68
- return;
69
- }
70
- // Token cap check
71
- if (isTokenCapReached()) {
72
- console.log();
73
- printError('Token budget exhausted. Exiting chat mode.');
74
- chatRl.close();
75
- return;
76
- }
77
- // Log the chat message
78
- logCommand(`ai4ctf: ${input}`);
79
- // Send message
80
- console.log(chalk.gray(' Thinking...'));
81
- try {
82
- const response = await chat.sendMessage(input);
83
- // Clear "Thinking..." line
84
- process.stdout.write('\x1b[1A\x1b[2K');
85
- // Track tokens
86
- addTokenUsage(response.tokensUsed);
87
- const updated = getTokenUsage();
88
- // Display response
89
- console.log();
90
- printMarkdown(response.text);
91
- console.log(chalk.gray(` [${response.tokensUsed.toLocaleString()} tokens | ${updated.used.toLocaleString()}/${updated.cap.toLocaleString()} total]`));
92
- console.log();
93
- }
94
- catch (err) {
95
- // Clear "Thinking..." line
96
- process.stdout.write('\x1b[1A\x1b[2K');
97
- printError(`AI error: ${err.message}`);
98
- console.log();
99
- }
100
- chatRl.prompt();
101
- });
102
- chatRl.on('close', () => {
103
- console.log();
104
- printInfo('Returning to ICOA terminal.');
105
- console.log();
106
- resolve();
107
- });
108
- });
109
89
  });
110
90
  }
package/dist/index.js CHANGED
@@ -37,7 +37,7 @@ ${LINE}
37
37
  ${chalk.white('Sydney, Australia')} ${chalk.gray('Jun 27 - Jul 2, 2026')}
38
38
  ${chalk.cyan.underline('https://icoa2026.au')}
39
39
 
40
- ${chalk.gray('CLI-Native Competition Terminal v2.3.1')}
40
+ ${chalk.gray('CLI-Native Competition Terminal v2.3.3')}
41
41
 
42
42
  ${LINE}
43
43
  `;
package/dist/repl.js CHANGED
@@ -4,6 +4,7 @@ import chalk from 'chalk';
4
4
  import { isConnected, getConfig } from './lib/config.js';
5
5
  import { isActivated, activateToken, isFreeCommand, isDeviceMatch, recordExit, recordResume, isFirstRunOrUpgrade, markVersionSeen } from './lib/access.js';
6
6
  import { setReplMode } from './lib/ui.js';
7
+ import { isChatActive, handleChatMessage } from './commands/ai4ctf.js';
7
8
  import { resetTerminalTheme } from './lib/theme.js';
8
9
  import { ensureSandbox, runInSandbox, isDockerAvailable } from './lib/sandbox.js';
9
10
  import { logCommand } from './lib/logger.js';
@@ -27,7 +28,7 @@ const BLOCKED_COMMANDS = new Set([
27
28
  'iptables', 'ufw', // firewall
28
29
  ]);
29
30
  const INTERCEPT = '__REPL_NO_EXIT__';
30
- const VERSION = '2.3.1';
31
+ const VERSION = '2.3.3';
31
32
  export async function startRepl(program, resumeMode) {
32
33
  const config = getConfig();
33
34
  const connected = isConnected();
@@ -147,6 +148,18 @@ export async function startRepl(program, resumeMode) {
147
148
  return;
148
149
  const input = line.trim();
149
150
  if (!input) {
151
+ rl.setPrompt(isChatActive() ? chalk.magenta('ai4ctf> ') : chalk.green('icoa> '));
152
+ rl.prompt();
153
+ return;
154
+ }
155
+ // If in AI chat mode, route to chat handler
156
+ if (isChatActive()) {
157
+ processing = true;
158
+ const result = await handleChatMessage(input);
159
+ processing = false;
160
+ if (result === 'exit') {
161
+ rl.setPrompt(chalk.green('icoa> '));
162
+ }
150
163
  rl.prompt();
151
164
  return;
152
165
  }
@@ -287,8 +300,6 @@ export async function startRepl(program, resumeMode) {
287
300
  process.exit = (() => {
288
301
  throw new Error(INTERCEPT);
289
302
  });
290
- // Pause main readline so sub-REPLs (ai4ctf) can use stdin
291
- rl.pause();
292
303
  try {
293
304
  await program.parseAsync(['node', 'icoa', ...args]);
294
305
  }
@@ -310,7 +321,10 @@ export async function startRepl(program, resumeMode) {
310
321
  finally {
311
322
  process.exit = realExit;
312
323
  processing = false;
313
- rl.resume();
324
+ }
325
+ // Switch prompt if entering chat mode
326
+ if (isChatActive()) {
327
+ rl.setPrompt(chalk.magenta('ai4ctf> '));
314
328
  }
315
329
  console.log();
316
330
  rl.prompt();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "2.3.1",
3
+ "version": "2.3.3",
4
4
  "description": "ICOA CLI — The world's first CLI-native CTF competition terminal",
5
5
  "type": "module",
6
6
  "bin": {