icoa-cli 1.4.4 → 1.5.1

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.
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerEnvCommand(program: Command): void;
@@ -0,0 +1,208 @@
1
+ import chalk from 'chalk';
2
+ import { execSync } from 'node:child_process';
3
+ import { platform } from 'node:os';
4
+ const PYTHON_LIBS = [
5
+ { name: 'pwntools', check: 'python3 -c "import pwn"', install: 'pwntools==4.12.0', category: 'Python Libraries' },
6
+ { name: 'pycryptodome', check: 'python3 -c "import Crypto"', install: 'pycryptodome==3.20.0', category: 'Python Libraries' },
7
+ { name: 'requests', check: 'python3 -c "import requests"', install: 'requests==2.31.0', category: 'Python Libraries' },
8
+ { name: 'beautifulsoup4', check: 'python3 -c "import bs4"', install: 'beautifulsoup4==4.12.3', category: 'Python Libraries' },
9
+ { name: 'z3-solver', check: 'python3 -c "import z3"', install: 'z3-solver==4.12.6', category: 'Python Libraries' },
10
+ { name: 'sympy', check: 'python3 -c "import sympy"', install: 'sympy==1.12', category: 'Python Libraries' },
11
+ { name: 'scapy', check: 'python3 -c "import scapy"', install: 'scapy==2.5.0', category: 'Python Libraries' },
12
+ { name: 'pillow', check: 'python3 -c "import PIL"', install: 'pillow==10.2.0', category: 'Python Libraries' },
13
+ { name: 'numpy', check: 'python3 -c "import numpy"', install: 'numpy==1.26.4', category: 'Python Libraries' },
14
+ { name: 'pefile', check: 'python3 -c "import pefile"', install: 'pefile==2023.2.7', category: 'Python Libraries' },
15
+ { name: 'capstone', check: 'python3 -c "import capstone"', install: 'capstone==5.0.1', category: 'Python Libraries' },
16
+ { name: 'ropper', check: 'python3 -c "import ropper"', install: 'ropper==1.13.8', category: 'Python Libraries' },
17
+ { name: 'ROPgadget', check: 'which ROPgadget', install: 'ROPgadget==7.4', category: 'Python Libraries' },
18
+ { name: 'flask', check: 'python3 -c "import flask"', install: 'flask==3.0.0', category: 'Python Libraries' },
19
+ { name: 'cryptography', check: 'python3 -c "import cryptography"', install: 'cryptography==42.0.0', category: 'Python Libraries' },
20
+ { name: 'paramiko', check: 'python3 -c "import paramiko"', install: 'paramiko==3.4.0', category: 'Python Libraries' },
21
+ { name: 'python-magic', check: 'python3 -c "import magic"', install: 'python-magic==0.4.27', category: 'Python Libraries' },
22
+ { name: 'ipython', check: 'which ipython3 || which ipython', install: 'ipython', category: 'Python Libraries' },
23
+ { name: 'angr', check: 'python3 -c "import angr"', install: 'angr', category: 'Python Libraries' },
24
+ { name: 'sqlmap', check: 'which sqlmap', install: 'sqlmap', category: 'Python Libraries' },
25
+ ];
26
+ const SYSTEM_TOOLS = [
27
+ // Editors
28
+ { name: 'vim', check: 'which vim', category: 'Editors' },
29
+ { name: 'nano', check: 'which nano', category: 'Editors' },
30
+ { name: 'tmux', check: 'which tmux', category: 'Editors' },
31
+ // Compilers
32
+ { name: 'gcc', check: 'which gcc', category: 'Compilers' },
33
+ { name: 'g++', check: 'which g++', category: 'Compilers' },
34
+ { name: 'make', check: 'which make', category: 'Compilers' },
35
+ { name: 'nasm', check: 'which nasm', category: 'Compilers' },
36
+ // Python
37
+ { name: 'python3', check: 'python3 --version', category: 'Python' },
38
+ { name: 'pip3', check: 'pip3 --version', category: 'Python' },
39
+ // Network
40
+ { name: 'curl', check: 'which curl', category: 'Networking' },
41
+ { name: 'wget', check: 'which wget', category: 'Networking' },
42
+ { name: 'nc', check: 'which nc', category: 'Networking' },
43
+ { name: 'nmap', check: 'which nmap', category: 'Networking' },
44
+ { name: 'ssh', check: 'which ssh', category: 'Networking' },
45
+ { name: 'socat', check: 'which socat', category: 'Networking' },
46
+ // Debuggers
47
+ { name: 'gdb', check: 'which gdb', category: 'Debuggers' },
48
+ { name: 'ltrace', check: 'which ltrace', category: 'Debuggers' },
49
+ { name: 'strace', check: 'which strace', category: 'Debuggers' },
50
+ // RE
51
+ { name: 'radare2', check: 'which r2', category: 'Reverse Engineering' },
52
+ { name: 'objdump', check: 'which objdump', category: 'Reverse Engineering' },
53
+ // Forensics
54
+ { name: 'binwalk', check: 'which binwalk', category: 'Forensics' },
55
+ { name: 'exiftool', check: 'which exiftool', category: 'Forensics' },
56
+ { name: 'steghide', check: 'which steghide', category: 'Forensics' },
57
+ { name: 'strings', check: 'which strings', category: 'Forensics' },
58
+ { name: 'file', check: 'which file', category: 'Forensics' },
59
+ { name: 'xxd', check: 'which xxd', category: 'Forensics' },
60
+ // Crypto
61
+ { name: 'john', check: 'which john', category: 'Crypto' },
62
+ { name: 'hashcat', check: 'which hashcat', category: 'Crypto' },
63
+ { name: 'openssl', check: 'which openssl', category: 'Crypto' },
64
+ // Data
65
+ { name: 'jq', check: 'which jq', category: 'Data Processing' },
66
+ { name: 'sqlite3', check: 'which sqlite3', category: 'Data Processing' },
67
+ // Core
68
+ { name: 'git', check: 'which git', category: 'Version Control' },
69
+ { name: 'docker', check: 'which docker', category: 'Container' },
70
+ ];
71
+ function isInstalled(check) {
72
+ try {
73
+ execSync(check, { stdio: 'ignore' });
74
+ return true;
75
+ }
76
+ catch {
77
+ return false;
78
+ }
79
+ }
80
+ function getVersion(name) {
81
+ try {
82
+ if (name === 'python3') {
83
+ return execSync('python3 --version', { encoding: 'utf-8' }).trim().replace('Python ', '');
84
+ }
85
+ if (name === 'gcc') {
86
+ const out = execSync('gcc --version', { encoding: 'utf-8' });
87
+ const match = out.match(/(\d+\.\d+\.\d+)/);
88
+ return match ? match[1] : '';
89
+ }
90
+ if (name === 'docker') {
91
+ const out = execSync('docker --version', { encoding: 'utf-8' });
92
+ const match = out.match(/(\d+\.\d+\.\d+)/);
93
+ return match ? match[1] : '';
94
+ }
95
+ }
96
+ catch { /* ignore */ }
97
+ return '';
98
+ }
99
+ export function registerEnvCommand(program) {
100
+ const envCmd = program.command('env').description('Manage competition environment');
101
+ // icoa env — show status
102
+ envCmd
103
+ .command('status')
104
+ .alias('check')
105
+ .description('Check installed tools and libraries')
106
+ .action(() => {
107
+ showStatus();
108
+ });
109
+ // icoa env setup — install everything
110
+ envCmd
111
+ .command('setup')
112
+ .description('Install all Python libraries (locked versions)')
113
+ .action(async () => {
114
+ await installAll();
115
+ });
116
+ // Default action: show status
117
+ envCmd.action(() => {
118
+ showStatus();
119
+ });
120
+ }
121
+ function showStatus() {
122
+ console.log();
123
+ console.log(chalk.bold.white(' ICOA Competition Environment'));
124
+ console.log(chalk.gray(' ─────────────────────────────────'));
125
+ console.log();
126
+ let installed = 0;
127
+ let missing = 0;
128
+ let currentCategory = '';
129
+ // System tools
130
+ for (const tool of SYSTEM_TOOLS) {
131
+ if (tool.category !== currentCategory) {
132
+ currentCategory = tool.category;
133
+ console.log(chalk.bold.gray(` ${currentCategory}`));
134
+ }
135
+ const ok = isInstalled(tool.check);
136
+ const ver = ok ? getVersion(tool.name) : '';
137
+ const verStr = ver ? chalk.gray(` (${ver})`) : '';
138
+ if (ok) {
139
+ console.log(chalk.green(` ✓ ${tool.name}`) + verStr);
140
+ installed++;
141
+ }
142
+ else {
143
+ console.log(chalk.red(` ✗ ${tool.name}`));
144
+ missing++;
145
+ }
146
+ }
147
+ // Python libraries
148
+ currentCategory = '';
149
+ for (const lib of PYTHON_LIBS) {
150
+ if (lib.category !== currentCategory) {
151
+ currentCategory = lib.category;
152
+ console.log(chalk.bold.gray(` ${currentCategory}`));
153
+ }
154
+ const ok = isInstalled(lib.check);
155
+ const verStr = lib.install ? chalk.gray(` (${lib.install})`) : '';
156
+ if (ok) {
157
+ console.log(chalk.green(` ✓ ${lib.name}`) + verStr);
158
+ installed++;
159
+ }
160
+ else {
161
+ console.log(chalk.red(` ✗ ${lib.name}`) + (lib.install ? chalk.gray(` → ${lib.install}`) : ''));
162
+ missing++;
163
+ }
164
+ }
165
+ console.log();
166
+ console.log(chalk.gray(' ─────────────────────────────────'));
167
+ console.log(` ${chalk.green(`✓ ${installed} installed`)} ${missing > 0 ? chalk.red(`✗ ${missing} missing`) : chalk.green('All ready!')}`);
168
+ if (missing > 0) {
169
+ console.log();
170
+ console.log(chalk.gray(' Install missing Python libs: ') + chalk.white('env setup'));
171
+ }
172
+ console.log();
173
+ }
174
+ async function installAll() {
175
+ console.log();
176
+ console.log(chalk.bold.white(' Installing Python libraries (locked versions)...'));
177
+ console.log();
178
+ const os = platform();
179
+ const pipFlag = os === 'darwin' || os === 'linux' ? '--break-system-packages' : '';
180
+ let installed = 0;
181
+ let failed = 0;
182
+ for (const lib of PYTHON_LIBS) {
183
+ if (!lib.install)
184
+ continue;
185
+ const alreadyInstalled = isInstalled(lib.check);
186
+ if (alreadyInstalled) {
187
+ console.log(chalk.green(` ✓ ${lib.name}`) + chalk.gray(` (already installed)`));
188
+ installed++;
189
+ continue;
190
+ }
191
+ process.stdout.write(chalk.gray(` ⏳ Installing ${lib.name}...`));
192
+ try {
193
+ execSync(`pip3 install ${pipFlag} ${lib.install}`, { stdio: 'ignore' });
194
+ process.stdout.write('\r');
195
+ console.log(chalk.green(` ✓ ${lib.name}`) + chalk.gray(` (${lib.install})`));
196
+ installed++;
197
+ }
198
+ catch {
199
+ process.stdout.write('\r');
200
+ console.log(chalk.red(` ✗ ${lib.name}`) + chalk.gray(` (install failed)`));
201
+ failed++;
202
+ }
203
+ }
204
+ console.log();
205
+ console.log(chalk.gray(' ─────────────────────────────────'));
206
+ console.log(` ${chalk.green(`✓ ${installed} installed`)} ${failed > 0 ? chalk.red(`✗ ${failed} failed`) : chalk.green('All ready!')}`);
207
+ console.log();
208
+ }
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ 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 { registerEnvCommand } from './commands/env.js';
14
15
  import { getConfig, saveConfig } from './lib/config.js';
15
16
  import { startRepl } from './repl.js';
16
17
  import { checkTerminal } from './lib/terminal.js';
@@ -37,7 +38,7 @@ ${B} ${B}
37
38
  ${B} ${chalk.white('Sydney, Australia')} ${chalk.gray('Jun 27 - Jul 2, 2026')} ${B}
38
39
  ${B} ${chalk.cyan.underline('https://icoa2026.au')} ${B}
39
40
  ${B} ${B}
40
- ${B} ${chalk.gray('CLI-Native Competition Terminal v1.4.4')} ${B}
41
+ ${B} ${chalk.gray('CLI-Native Competition Terminal v1.5.1')} ${B}
41
42
  ${B} ${B}
42
43
  ${chalk.cyan('╚══════════════════════════════════════════════════════════╝')}
43
44
  `;
@@ -82,6 +83,7 @@ registerNoteCommand(program);
82
83
  registerLogCommand(program);
83
84
  registerLangCommand(program);
84
85
  registerSetupCommand(program);
86
+ registerEnvCommand(program);
85
87
  // Hidden command: switch AI model
86
88
  program
87
89
  .command('model', { hidden: true })
@@ -0,0 +1,7 @@
1
+ export declare function isDockerAvailable(): boolean;
2
+ export declare function isSandboxRunning(): boolean;
3
+ export declare function ensureSandbox(): Promise<boolean>;
4
+ export declare function runInSandbox(command: string, rl: {
5
+ pause: () => void;
6
+ resume: () => void;
7
+ }): Promise<void>;
@@ -0,0 +1,93 @@
1
+ import { execSync, spawn } from 'node:child_process';
2
+ import chalk from 'chalk';
3
+ const IMAGE = 'icoa/sandbox:2026';
4
+ const CONTAINER = 'icoa-sandbox';
5
+ export function isDockerAvailable() {
6
+ try {
7
+ execSync('docker info', { stdio: 'ignore' });
8
+ return true;
9
+ }
10
+ catch {
11
+ return false;
12
+ }
13
+ }
14
+ export function isSandboxRunning() {
15
+ try {
16
+ const out = execSync(`docker inspect -f '{{.State.Running}}' ${CONTAINER} 2>/dev/null`, {
17
+ encoding: 'utf-8',
18
+ });
19
+ return out.trim() === 'true';
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
25
+ export async function ensureSandbox() {
26
+ if (!isDockerAvailable()) {
27
+ console.log(chalk.yellow(' Docker not found. Install Docker Desktop to use sandbox tools.'));
28
+ console.log(chalk.gray(' https://www.docker.com/products/docker-desktop'));
29
+ return false;
30
+ }
31
+ if (isSandboxRunning())
32
+ return true;
33
+ // Check if container exists but stopped
34
+ try {
35
+ execSync(`docker start ${CONTAINER}`, { stdio: 'ignore' });
36
+ return true;
37
+ }
38
+ catch {
39
+ // Container doesn't exist, create it
40
+ }
41
+ // Check if image exists
42
+ try {
43
+ execSync(`docker image inspect ${IMAGE}`, { stdio: 'ignore' });
44
+ }
45
+ catch {
46
+ console.log(chalk.gray(' Pulling sandbox image (first time only)...'));
47
+ try {
48
+ execSync(`docker pull ${IMAGE}`, { stdio: 'inherit' });
49
+ }
50
+ catch {
51
+ // Image not on registry yet, try building locally
52
+ console.log(chalk.gray(' Building sandbox from local Dockerfile...'));
53
+ try {
54
+ const dockerDir = new URL('../../docker', import.meta.url).pathname;
55
+ execSync(`docker build -t ${IMAGE} ${dockerDir}`, { stdio: 'inherit' });
56
+ }
57
+ catch {
58
+ console.log(chalk.red(' Failed to set up sandbox.'));
59
+ return false;
60
+ }
61
+ }
62
+ }
63
+ // Create and start container
64
+ try {
65
+ execSync(`docker run -d --name ${CONTAINER} ` +
66
+ `-v icoa-challenges:/home/competitor/challenges ` +
67
+ `--network host ` +
68
+ `${IMAGE} sleep infinity`, { stdio: 'ignore' });
69
+ return true;
70
+ }
71
+ catch {
72
+ console.log(chalk.red(' Failed to start sandbox container.'));
73
+ return false;
74
+ }
75
+ }
76
+ export function runInSandbox(command, rl) {
77
+ return new Promise((resolve) => {
78
+ rl.pause();
79
+ const opts = {
80
+ stdio: 'inherit',
81
+ shell: true,
82
+ };
83
+ const child = spawn('docker', ['exec', '-it', CONTAINER, 'bash', '-c', command], opts);
84
+ child.on('close', () => {
85
+ rl.resume();
86
+ resolve();
87
+ });
88
+ child.on('error', () => {
89
+ rl.resume();
90
+ resolve();
91
+ });
92
+ });
93
+ }
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 } from './lib/access.js';
6
6
  import { resetTerminalTheme } from './lib/theme.js';
7
+ import { ensureSandbox, runInSandbox, isDockerAvailable } from './lib/sandbox.js';
7
8
  const INTERCEPT = '__REPL_NO_EXIT__';
8
9
  export function startRepl(program, resumeMode) {
9
10
  const config = getConfig();
@@ -124,13 +125,24 @@ export function startRepl(program, resumeMode) {
124
125
  'join', 'activate', 'challenges', 'ch', 'open', 'submit', 'flag',
125
126
  'scoreboard', 'sb', 'status', 'time', 'hint', 'hint-b', 'hint-c',
126
127
  'hint-budget', 'ref', 'shell', 'files', 'connect', 'note', 'log',
127
- 'lang', 'setup', 'model', 'ctf',
128
+ 'lang', 'setup', 'env', 'model', 'ctf',
128
129
  ];
129
130
  if (!knownCommands.includes(cmd)) {
130
- // Pass through to system shell
131
+ // Route to Docker sandbox if available, otherwise system shell
131
132
  processing = true;
132
133
  try {
133
- await runSystemCommand(input, rl);
134
+ if (isDockerAvailable()) {
135
+ const ready = await ensureSandbox();
136
+ if (ready) {
137
+ await runInSandbox(input, rl);
138
+ }
139
+ else {
140
+ await runSystemCommand(input, rl);
141
+ }
142
+ }
143
+ else {
144
+ await runSystemCommand(input, rl);
145
+ }
134
146
  }
135
147
  catch {
136
148
  console.log(chalk.yellow(` Command failed: ${cmd}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "1.4.4",
3
+ "version": "1.5.1",
4
4
  "description": "ICOA CLI — The world's first CLI-native CTF competition terminal",
5
5
  "type": "module",
6
6
  "bin": {