icoa-cli 1.4.3 → 1.5.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 +4 -3
- package/dist/lib/sandbox.d.ts +7 -0
- package/dist/lib/sandbox.js +93 -0
- package/dist/lib/theme.d.ts +2 -0
- package/dist/lib/theme.js +52 -0
- package/dist/repl.js +17 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import { registerSetupCommand } from './commands/setup.js';
|
|
|
14
14
|
import { getConfig, saveConfig } from './lib/config.js';
|
|
15
15
|
import { startRepl } from './repl.js';
|
|
16
16
|
import { checkTerminal } from './lib/terminal.js';
|
|
17
|
+
import { setTerminalTheme } from './lib/theme.js';
|
|
17
18
|
// Banner: each line between ║ ║ = exactly 58 visible chars
|
|
18
19
|
const B = chalk.cyan('║');
|
|
19
20
|
const BANNER = `
|
|
@@ -36,7 +37,7 @@ ${B} ${B}
|
|
|
36
37
|
${B} ${chalk.white('Sydney, Australia')} ${chalk.gray('Jun 27 - Jul 2, 2026')} ${B}
|
|
37
38
|
${B} ${chalk.cyan.underline('https://icoa2026.au')} ${B}
|
|
38
39
|
${B} ${B}
|
|
39
|
-
${B} ${chalk.gray('CLI-Native Competition Terminal v1.
|
|
40
|
+
${B} ${chalk.gray('CLI-Native Competition Terminal v1.5.0')} ${B}
|
|
40
41
|
${B} ${B}
|
|
41
42
|
${chalk.cyan('╚══════════════════════════════════════════════════════════╝')}
|
|
42
43
|
`;
|
|
@@ -61,8 +62,8 @@ program
|
|
|
61
62
|
.description('ICOA CLI — CLI-Native CTF Competition Terminal')
|
|
62
63
|
.option('--resume', 'Resume previous session')
|
|
63
64
|
.action((opts) => {
|
|
64
|
-
// Force black background
|
|
65
|
-
|
|
65
|
+
// Force hacker theme: black background + green text
|
|
66
|
+
setTerminalTheme();
|
|
66
67
|
console.log(BANNER);
|
|
67
68
|
checkTerminal();
|
|
68
69
|
// If running interactively (no extra args or --resume), start REPL
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { platform } from 'node:os';
|
|
3
|
+
export function setTerminalTheme() {
|
|
4
|
+
const os = platform();
|
|
5
|
+
if (os === 'darwin') {
|
|
6
|
+
// macOS Terminal.app: use AppleScript to change colors
|
|
7
|
+
try {
|
|
8
|
+
execSync(`osascript -e '
|
|
9
|
+
tell application "Terminal"
|
|
10
|
+
set bg to {0, 0, 0}
|
|
11
|
+
set fg to {0, 65535, 16896}
|
|
12
|
+
set background color of selected tab of front window to bg
|
|
13
|
+
set normal text color of selected tab of front window to fg
|
|
14
|
+
set cursor color of selected tab of front window to fg
|
|
15
|
+
end tell
|
|
16
|
+
'`, { stdio: 'ignore' });
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Fallback to ANSI if AppleScript fails (e.g. not Terminal.app)
|
|
20
|
+
process.stdout.write('\x1b[0m\x1b[40m\x1b[38;2;0;255;65m\x1b[2J\x1b[H');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
// Linux / Windows: ANSI escape codes
|
|
25
|
+
process.stdout.write('\x1b[0m\x1b[40m\x1b[38;2;0;255;65m\x1b[2J\x1b[H');
|
|
26
|
+
}
|
|
27
|
+
// Clear screen
|
|
28
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
29
|
+
}
|
|
30
|
+
export function resetTerminalTheme() {
|
|
31
|
+
const os = platform();
|
|
32
|
+
if (os === 'darwin') {
|
|
33
|
+
try {
|
|
34
|
+
// Reset to macOS Terminal.app default profile colors
|
|
35
|
+
execSync(`osascript -e '
|
|
36
|
+
tell application "Terminal"
|
|
37
|
+
set bg to {65535, 65535, 65535}
|
|
38
|
+
set fg to {0, 0, 0}
|
|
39
|
+
set background color of selected tab of front window to bg
|
|
40
|
+
set normal text color of selected tab of front window to fg
|
|
41
|
+
set cursor color of selected tab of front window to fg
|
|
42
|
+
end tell
|
|
43
|
+
'`, { stdio: 'ignore' });
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
process.stdout.write('\x1b[0m\x1b[2J\x1b[H');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
process.stdout.write('\x1b[0m\x1b[2J\x1b[H');
|
|
51
|
+
}
|
|
52
|
+
}
|
package/dist/repl.js
CHANGED
|
@@ -3,6 +3,8 @@ import { spawn } from 'node:child_process';
|
|
|
3
3
|
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
|
+
import { resetTerminalTheme } from './lib/theme.js';
|
|
7
|
+
import { ensureSandbox, runInSandbox, isDockerAvailable } from './lib/sandbox.js';
|
|
6
8
|
const INTERCEPT = '__REPL_NO_EXIT__';
|
|
7
9
|
export function startRepl(program, resumeMode) {
|
|
8
10
|
const config = getConfig();
|
|
@@ -68,7 +70,7 @@ export function startRepl(program, resumeMode) {
|
|
|
68
70
|
if (input === 'exit' || input === 'quit' || input === 'q') {
|
|
69
71
|
recordExit();
|
|
70
72
|
console.log(chalk.gray(' Session saved. Use ') + chalk.white('icoa --resume') + chalk.gray(' to continue.'));
|
|
71
|
-
|
|
73
|
+
resetTerminalTheme();
|
|
72
74
|
realExit(0);
|
|
73
75
|
return;
|
|
74
76
|
}
|
|
@@ -126,10 +128,21 @@ export function startRepl(program, resumeMode) {
|
|
|
126
128
|
'lang', 'setup', 'model', 'ctf',
|
|
127
129
|
];
|
|
128
130
|
if (!knownCommands.includes(cmd)) {
|
|
129
|
-
//
|
|
131
|
+
// Route to Docker sandbox if available, otherwise system shell
|
|
130
132
|
processing = true;
|
|
131
133
|
try {
|
|
132
|
-
|
|
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
|
+
}
|
|
133
146
|
}
|
|
134
147
|
catch {
|
|
135
148
|
console.log(chalk.yellow(` Command failed: ${cmd}`));
|
|
@@ -168,7 +181,7 @@ export function startRepl(program, resumeMode) {
|
|
|
168
181
|
});
|
|
169
182
|
rl.on('close', () => {
|
|
170
183
|
recordExit();
|
|
171
|
-
|
|
184
|
+
resetTerminalTheme();
|
|
172
185
|
realExit(0);
|
|
173
186
|
});
|
|
174
187
|
}
|