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.
- package/dist/commands/ai4ctf.d.ts +2 -0
- package/dist/commands/ai4ctf.js +44 -64
- package/dist/index.js +1 -1
- package/dist/repl.js +18 -4
- package/package.json +1 -1
package/dist/commands/ai4ctf.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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();
|