icoa-cli 1.1.1 → 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 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 { isConnected, getConfig, saveConfig } from './lib/config.js';
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('║')}
@@ -28,17 +29,21 @@ ${chalk.cyan('║')} ${
28
29
  ${chalk.cyan('║')} ${chalk.green.bold('AI4CTF')} ${chalk.gray('Use AI to solve challenges')} ${chalk.cyan('║')}
29
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.1.1')} ${chalk.cyan('║')}
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
- // Show quick status
53
- if (isConnected()) {
54
- const config = getConfig();
55
- console.log(chalk.gray(' Connected to: ') + chalk.white(config.ctfdUrl));
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
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function startRepl(program: Command): void;
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "ICOA CLI — The world's first CLI-native CTF competition terminal",
5
5
  "type": "module",
6
6
  "bin": {