icoa-cli 1.1.0 → 1.2.0
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/index.js +15 -25
- package/dist/repl.d.ts +2 -0
- package/dist/repl.js +153 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,7 +11,8 @@ import { registerNoteCommand } from './commands/note.js';
|
|
|
11
11
|
import { registerLogCommand } from './commands/log.js';
|
|
12
12
|
import { registerLangCommand } from './commands/lang.js';
|
|
13
13
|
import { registerSetupCommand } from './commands/setup.js';
|
|
14
|
-
import {
|
|
14
|
+
import { getConfig, saveConfig } from './lib/config.js';
|
|
15
|
+
import { startRepl } from './repl.js';
|
|
15
16
|
const BANNER = `
|
|
16
17
|
${chalk.cyan('╔══════════════════════════════════════════════════════════╗')}
|
|
17
18
|
${chalk.cyan('║')} ${chalk.cyan('║')}
|
|
@@ -22,23 +23,27 @@ ${chalk.cyan('║')} ${chalk.bold.white('██║██║ ██║
|
|
|
22
23
|
${chalk.cyan('║')} ${chalk.bold.white('██║╚██████╗╚██████╔╝██║ ██║')} ${chalk.cyan('║')}
|
|
23
24
|
${chalk.cyan('║')} ${chalk.bold.white('╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝')} ${chalk.cyan('║')}
|
|
24
25
|
${chalk.cyan('║')} ${chalk.cyan('║')}
|
|
25
|
-
${chalk.cyan('║')} ${chalk.yellow('International Cyber Olympiad in AI 2026')}
|
|
26
|
-
${chalk.cyan('║')} ${chalk.bold.magenta("The World's First AI Security Olympiad")}
|
|
26
|
+
${chalk.cyan('║')} ${chalk.yellow('International Cyber Olympiad in AI 2026')} ${chalk.cyan('║')}
|
|
27
|
+
${chalk.cyan('║')} ${chalk.bold.magenta("The World's First AI Security Olympiad")} ${chalk.cyan('║')}
|
|
27
28
|
${chalk.cyan('║')} ${chalk.cyan('║')}
|
|
28
|
-
${chalk.cyan('║')} ${chalk.green.bold('AI4CTF')} ${chalk.gray('Use AI to solve challenges')}
|
|
29
|
-
${chalk.cyan('║')} ${chalk.red.bold('CTF4AI')} ${chalk.gray('Hack, attack & evaluate AI systems')}
|
|
29
|
+
${chalk.cyan('║')} ${chalk.green.bold('AI4CTF')} ${chalk.gray('Use AI to solve challenges')} ${chalk.cyan('║')}
|
|
30
|
+
${chalk.cyan('║')} ${chalk.red.bold('CTF4AI')} ${chalk.gray('Hack, attack & evaluate AI systems')} ${chalk.cyan('║')}
|
|
30
31
|
${chalk.cyan('║')} ${chalk.cyan('║')}
|
|
31
|
-
${chalk.cyan('║')} ${chalk.gray('CLI-Native Competition Terminal v1.
|
|
32
|
+
${chalk.cyan('║')} ${chalk.gray('CLI-Native Competition Terminal v1.2.0')} ${chalk.cyan('║')}
|
|
32
33
|
${chalk.cyan('║')} ${chalk.cyan('║')}
|
|
33
34
|
${chalk.cyan('╚══════════════════════════════════════════════════════════╝')}
|
|
34
35
|
`;
|
|
35
36
|
// Global error handlers
|
|
36
37
|
process.on('uncaughtException', (err) => {
|
|
38
|
+
if (err.message === '__REPL_NO_EXIT__')
|
|
39
|
+
return;
|
|
37
40
|
console.error(chalk.red('Error:'), err.message);
|
|
38
41
|
process.exit(1);
|
|
39
42
|
});
|
|
40
43
|
process.on('unhandledRejection', (reason) => {
|
|
41
44
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
45
|
+
if (msg === '__REPL_NO_EXIT__')
|
|
46
|
+
return;
|
|
42
47
|
console.error(chalk.red('Error:'), msg);
|
|
43
48
|
process.exit(1);
|
|
44
49
|
});
|
|
@@ -49,26 +54,11 @@ program
|
|
|
49
54
|
.description('ICOA CLI — CLI-Native CTF Competition Terminal')
|
|
50
55
|
.action(() => {
|
|
51
56
|
console.log(BANNER);
|
|
52
|
-
//
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
console.log(chalk.gray(' User: ') + chalk.white(config.userName));
|
|
57
|
-
console.log();
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
console.log(chalk.gray(' Not connected. Start with: ') + chalk.white('icoa ctf join <url>'));
|
|
61
|
-
console.log();
|
|
57
|
+
// If running interactively (no extra args), start REPL
|
|
58
|
+
if (process.argv.length <= 2) {
|
|
59
|
+
startRepl(program);
|
|
60
|
+
return;
|
|
62
61
|
}
|
|
63
|
-
console.log(chalk.gray(' Quick start:'));
|
|
64
|
-
console.log(chalk.white(' icoa ctf join <url> ') + chalk.gray('Connect to competition'));
|
|
65
|
-
console.log(chalk.white(' icoa ctf challenges ') + chalk.gray('View challenges'));
|
|
66
|
-
console.log(chalk.white(' icoa hint <question> ') + chalk.gray('Get AI hint'));
|
|
67
|
-
console.log(chalk.white(' icoa ref <topic> ') + chalk.gray('Quick reference'));
|
|
68
|
-
console.log(chalk.white(' icoa shell ') + chalk.gray('Open sandbox'));
|
|
69
|
-
console.log(chalk.white(' icoa setup ') + chalk.gray('Configure settings'));
|
|
70
|
-
console.log(chalk.white(' icoa --help ') + chalk.gray('All commands'));
|
|
71
|
-
console.log();
|
|
72
62
|
});
|
|
73
63
|
registerCtfCommands(program);
|
|
74
64
|
registerHintCommands(program);
|
package/dist/repl.d.ts
ADDED
package/dist/repl.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { isConnected, getConfig } from './lib/config.js';
|
|
4
|
+
// Sentinel error used to intercept process.exit() calls from commands
|
|
5
|
+
const INTERCEPT = '__REPL_NO_EXIT__';
|
|
6
|
+
export function startRepl(program) {
|
|
7
|
+
const config = getConfig();
|
|
8
|
+
const connected = isConnected();
|
|
9
|
+
const realExit = process.exit.bind(process);
|
|
10
|
+
if (connected) {
|
|
11
|
+
console.log(chalk.gray(' Connected to: ') + chalk.white(config.ctfdUrl));
|
|
12
|
+
console.log(chalk.gray(' User: ') + chalk.white(config.userName));
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
console.log(chalk.gray(' Not connected. Type: ') + chalk.white('join <url>'));
|
|
16
|
+
}
|
|
17
|
+
console.log();
|
|
18
|
+
console.log(chalk.gray(' Type ') + chalk.white('help') + chalk.gray(' for commands, ') + chalk.white('exit') + chalk.gray(' to quit.'));
|
|
19
|
+
console.log();
|
|
20
|
+
// Configure Commander to throw instead of calling process.exit
|
|
21
|
+
program.exitOverride();
|
|
22
|
+
program.configureOutput({
|
|
23
|
+
writeErr: () => { },
|
|
24
|
+
writeOut: (str) => { console.log(str); },
|
|
25
|
+
});
|
|
26
|
+
const rl = createInterface({
|
|
27
|
+
input: process.stdin,
|
|
28
|
+
output: process.stdout,
|
|
29
|
+
prompt: chalk.cyan('icoa> '),
|
|
30
|
+
terminal: true,
|
|
31
|
+
});
|
|
32
|
+
let processing = false;
|
|
33
|
+
rl.prompt();
|
|
34
|
+
rl.on('line', async (line) => {
|
|
35
|
+
if (processing)
|
|
36
|
+
return;
|
|
37
|
+
const input = line.trim();
|
|
38
|
+
if (!input) {
|
|
39
|
+
rl.prompt();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Built-in REPL commands — handled with real process.exit
|
|
43
|
+
if (input === 'exit' || input === 'quit' || input === 'q') {
|
|
44
|
+
console.log(chalk.gray(' Goodbye!'));
|
|
45
|
+
realExit(0);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (input === 'help' || input === '?') {
|
|
49
|
+
printReplHelp();
|
|
50
|
+
rl.prompt();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (input === 'clear' || input === 'cls') {
|
|
54
|
+
console.clear();
|
|
55
|
+
rl.prompt();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
processing = true;
|
|
59
|
+
const args = mapCommand(input);
|
|
60
|
+
// Override process.exit so commands don't kill the REPL
|
|
61
|
+
process.exit = (() => {
|
|
62
|
+
throw new Error(INTERCEPT);
|
|
63
|
+
});
|
|
64
|
+
try {
|
|
65
|
+
await program.parseAsync(['node', 'icoa', ...args]);
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
69
|
+
if (msg === INTERCEPT) {
|
|
70
|
+
// Command tried to exit — that's fine
|
|
71
|
+
}
|
|
72
|
+
else if (msg.includes('commander.unknownCommand')) {
|
|
73
|
+
console.log(chalk.yellow(` Unknown command: ${input.split(' ')[0]}. Type 'help' for commands.`));
|
|
74
|
+
}
|
|
75
|
+
else if (msg.includes('commander.')) {
|
|
76
|
+
// Internal Commander errors — ignore
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
// Always restore real process.exit
|
|
81
|
+
process.exit = realExit;
|
|
82
|
+
processing = false;
|
|
83
|
+
}
|
|
84
|
+
console.log();
|
|
85
|
+
rl.prompt();
|
|
86
|
+
});
|
|
87
|
+
rl.on('close', () => {
|
|
88
|
+
realExit(0);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function mapCommand(input) {
|
|
92
|
+
const parts = input.split(/\s+/);
|
|
93
|
+
const cmd = parts[0].toLowerCase();
|
|
94
|
+
const rest = parts.slice(1);
|
|
95
|
+
const ctfShortcuts = {
|
|
96
|
+
'join': ['ctf', 'join', ...rest],
|
|
97
|
+
'activate': ['ctf', 'activate', ...rest],
|
|
98
|
+
'challenges': ['ctf', 'challenges'],
|
|
99
|
+
'ch': ['ctf', 'challenges'],
|
|
100
|
+
'open': ['ctf', 'open', ...rest],
|
|
101
|
+
'submit': ['ctf', 'submit', ...rest],
|
|
102
|
+
'flag': ['ctf', 'submit', ...rest],
|
|
103
|
+
'scoreboard': ['ctf', 'scoreboard', ...rest],
|
|
104
|
+
'sb': ['ctf', 'scoreboard', ...rest],
|
|
105
|
+
'status': ['ctf', 'status'],
|
|
106
|
+
'time': ['ctf', 'time'],
|
|
107
|
+
};
|
|
108
|
+
if (ctfShortcuts[cmd]) {
|
|
109
|
+
return ctfShortcuts[cmd];
|
|
110
|
+
}
|
|
111
|
+
const directCommands = [
|
|
112
|
+
'hint', 'hint-b', 'hint-c', 'hint-budget',
|
|
113
|
+
'ref', 'shell', 'files', 'connect', 'note',
|
|
114
|
+
'log', 'lang', 'setup', 'model',
|
|
115
|
+
'ctf',
|
|
116
|
+
];
|
|
117
|
+
if (directCommands.includes(cmd)) {
|
|
118
|
+
return [cmd, ...rest];
|
|
119
|
+
}
|
|
120
|
+
return parts;
|
|
121
|
+
}
|
|
122
|
+
function printReplHelp() {
|
|
123
|
+
console.log();
|
|
124
|
+
console.log(chalk.bold.white(' Competition'));
|
|
125
|
+
console.log(chalk.white(' join <url> ') + chalk.gray('Connect to CTFd'));
|
|
126
|
+
console.log(chalk.white(' challenges (ch) ') + chalk.gray('List challenges'));
|
|
127
|
+
console.log(chalk.white(' open <id> ') + chalk.gray('View challenge details'));
|
|
128
|
+
console.log(chalk.white(' submit <id> <flag> ') + chalk.gray('Submit a flag'));
|
|
129
|
+
console.log(chalk.white(' scoreboard (sb) ') + chalk.gray('View scoreboard'));
|
|
130
|
+
console.log(chalk.white(' status ') + chalk.gray('Competition status'));
|
|
131
|
+
console.log(chalk.white(' time ') + chalk.gray('Countdown timer'));
|
|
132
|
+
console.log();
|
|
133
|
+
console.log(chalk.bold.white(' AI Hints'));
|
|
134
|
+
console.log(chalk.white(' hint <question> ') + chalk.gray('Level A — General guidance'));
|
|
135
|
+
console.log(chalk.white(' hint-b <question> ') + chalk.gray('Level B — Deep analysis'));
|
|
136
|
+
console.log(chalk.white(' hint-c <question> ') + chalk.gray('Level C — Critical assist'));
|
|
137
|
+
console.log(chalk.white(' hint budget ') + chalk.gray('Check remaining budget'));
|
|
138
|
+
console.log();
|
|
139
|
+
console.log(chalk.bold.white(' Tools'));
|
|
140
|
+
console.log(chalk.white(' ref [topic] ') + chalk.gray('Quick reference'));
|
|
141
|
+
console.log(chalk.white(' shell ') + chalk.gray('Docker sandbox'));
|
|
142
|
+
console.log(chalk.white(' files <id> ') + chalk.gray('Download challenge files'));
|
|
143
|
+
console.log(chalk.white(' connect <id> ') + chalk.gray('Connect to remote target'));
|
|
144
|
+
console.log(chalk.white(' note [text] ') + chalk.gray('Personal notepad'));
|
|
145
|
+
console.log(chalk.white(' log ') + chalk.gray('Session history'));
|
|
146
|
+
console.log();
|
|
147
|
+
console.log(chalk.bold.white(' System'));
|
|
148
|
+
console.log(chalk.white(' setup ') + chalk.gray('Configure settings'));
|
|
149
|
+
console.log(chalk.white(' lang [code] ') + chalk.gray('Switch language'));
|
|
150
|
+
console.log(chalk.white(' clear ') + chalk.gray('Clear screen'));
|
|
151
|
+
console.log(chalk.white(' exit ') + chalk.gray('Quit ICOA CLI'));
|
|
152
|
+
console.log();
|
|
153
|
+
}
|