icoa-cli 1.1.1 → 1.3.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 +14 -23
- package/dist/lib/access.d.ts +19 -0
- package/dist/lib/access.js +174 -0
- package/dist/repl.d.ts +2 -0
- package/dist/repl.js +211 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -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('║')}
|
|
@@ -28,47 +29,37 @@ ${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.
|
|
32
|
+
${chalk.cyan('║')} ${chalk.gray('CLI-Native Competition Terminal v1.3.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
|
});
|
|
45
50
|
const program = new Command();
|
|
46
51
|
program
|
|
47
52
|
.name('icoa')
|
|
48
|
-
.version('1.
|
|
53
|
+
.version('1.2.0')
|
|
49
54
|
.description('ICOA CLI — CLI-Native CTF Competition Terminal')
|
|
50
|
-
.
|
|
55
|
+
.option('--resume', 'Resume previous session')
|
|
56
|
+
.action((opts) => {
|
|
51
57
|
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();
|
|
58
|
+
// If running interactively (no extra args or --resume), start REPL
|
|
59
|
+
if (process.argv.length <= 2 || opts.resume) {
|
|
60
|
+
startRepl(program, !!opts.resume);
|
|
61
|
+
return;
|
|
62
62
|
}
|
|
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
63
|
});
|
|
73
64
|
registerCtfCommands(program);
|
|
74
65
|
registerHintCommands(program);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare function validateToken(token: string): boolean;
|
|
2
|
+
export declare function isActivated(): boolean;
|
|
3
|
+
export declare function activateToken(token: string): boolean;
|
|
4
|
+
export declare function isFreeCommand(cmd: string): boolean;
|
|
5
|
+
interface SessionState {
|
|
6
|
+
startedAt: string;
|
|
7
|
+
lastExitAt: string | null;
|
|
8
|
+
lastResumeAt: string | null;
|
|
9
|
+
exitCount: number;
|
|
10
|
+
totalAwaySeconds: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function getSession(): SessionState;
|
|
13
|
+
export declare function saveSession(session: Partial<SessionState>): void;
|
|
14
|
+
export declare function recordExit(): void;
|
|
15
|
+
export declare function recordResume(): {
|
|
16
|
+
awaySeconds: number;
|
|
17
|
+
exitCount: number;
|
|
18
|
+
} | null;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { getConfig, saveConfig, getIcoaDir } from './config.js';
|
|
3
|
+
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
// SHA-256 hashes of 100 pre-generated access tokens (ICOA-XXXX-XXXX-XXXX)
|
|
6
|
+
const TOKEN_HASHES = new Set([
|
|
7
|
+
"93490bf664679467b4ab5af12e240175364b7af988b72cb7d000e309b7c68c15",
|
|
8
|
+
"cba7a6f075a4bd8c2fc973336cc0b1966669fc481381a065ce670730bbe0b7d3",
|
|
9
|
+
"f6773f614341acb8d2c35e8f466f2876095a2c8026a207e77de621050fa64559",
|
|
10
|
+
"d060ab36056fdac449a561fd3340c4b79ad4cde10c9a21c3397aa74574583a2b",
|
|
11
|
+
"523c11774e103298c15eb9d138872295194078f2d29486d0a6bd210436a592c3",
|
|
12
|
+
"1e16864067470f0a97088f067d8df4b655753ff498a91353b5f9b75989b6be39",
|
|
13
|
+
"f1031aac52879c776d4db5ba43eb25bb40f1a841b91304f8adf65a838fb8c666",
|
|
14
|
+
"bcd9e25014633ec6556345ab43d0cd213c454bb10d4ea0bd5be5ba02e1a6622c",
|
|
15
|
+
"9239ba9c3db74eadbb6dd05949c2a3cbe1b22e14e375ab3ca93d050e2e2ce38b",
|
|
16
|
+
"80fc0a899ed4d37138ae3172dafd466de2d3b1ce9630d67e6b00bec9315964d7",
|
|
17
|
+
"33ed35ce028824519d3a013ca326d6a58b1ca3723ec182527a7367e1ffc2712b",
|
|
18
|
+
"7e90933e760ce5e8df48bdb6bab0e95962bdc1098fceedecf617c0dac37b2a11",
|
|
19
|
+
"33fa6e3639a691b217ed0f927a8e2e2aef5eaa15eab7bd90c57874c2df2a28ac",
|
|
20
|
+
"67013663fb4536a47f6ea46b434fb7a00574bd4fc4f579e9439ba07f748db31b",
|
|
21
|
+
"10c83ad26fc965fcb1d228018f6b54d331e4a2e6087603920b75b500e191c5e4",
|
|
22
|
+
"3079268d847dc7eb196ce8cdc0af8a8506b9636539a1b7cce53dc5cb3f4cbe15",
|
|
23
|
+
"985eb5309c546d2f9d5018976973d6da06a374b897c73c830fc9d1305e78efa0",
|
|
24
|
+
"3a7326d4a7f40cc356b17769f4c5fc0d832d926aff9f067efd60f34c053bf9ef",
|
|
25
|
+
"129f408a98ad6c1b13924cf2c831c752b0c726bf1975d6b46f212a178ced52e1",
|
|
26
|
+
"ec44bc0b4eb6a929b969caaa561492527c369cc07efa50e40c2c6c16162f0c2c",
|
|
27
|
+
"07610942d9da62dd5b91c0ba48602bc44973a9d374ed10ab6c708151677ec626",
|
|
28
|
+
"8ce3165bb8e23f3b67ac53b39191aca3e2f0a620b7f932fe6a43e2c42315efcc",
|
|
29
|
+
"69938b605e652afc1cf8bc7c0dcf6a99820878367dfb3b2ec954ba90b36014f5",
|
|
30
|
+
"660377ce87d378285ab7263fdad86208c8563367d2cc36b4c1402520e45dd5f0",
|
|
31
|
+
"bef8a3727dffc94861a1dfa7d6a336ceac30aab9f0679dcab662cb9f682079dd",
|
|
32
|
+
"02318a3597b64a2c7de56b01fc25f33cc4142c76315136186df680f490681294",
|
|
33
|
+
"b7179114b42b90e9557399ae505050111b6afc55f9295d3ee84d5db6873470c5",
|
|
34
|
+
"e227ec0a6b22773500b35bee46f21bc6bc6a00fb001d54e71479f15b8fdfd72b",
|
|
35
|
+
"1c9802ae1e932d55e73994ce173a996d170e4d274aeccb1b784c9d9b69913f3d",
|
|
36
|
+
"5ed51694773bd1b46a085d10e9a369786a997c50b72ec2bdb6e68921ffca5c51",
|
|
37
|
+
"478e457279a41e9b76a3dc5894418e38246d2af31e0f4776dda8aa89c74c88c8",
|
|
38
|
+
"a7ec7e047c0e62862e9be64b0a65d3d7894d41efe6eb16ca6cfca45d8007f16f",
|
|
39
|
+
"88cf2b75604486ac5f1a0db9afbab28e8e866f9ea00f3e4b4353a7ecc81f6b55",
|
|
40
|
+
"dc4daa9ef4149f122979ffd5a8b23bca3cbb83b24a87ba99a1d00dc09e39936e",
|
|
41
|
+
"cffd9a3637b5ae30c27cae4ae42dc21374caa27dd818b5d361f112406bf3c0e7",
|
|
42
|
+
"33b3e85a964f606e857280ba379de46508f9343cbe1d2722abebbed21b82a106",
|
|
43
|
+
"a3754074df08f9c9407c73e680eac1868175cee3beac3f0e7d2bd211bdf51f58",
|
|
44
|
+
"f9eb5fd82f3360e22ad45d4797f3a95b907a766ef5a61d856bf959b60a4930dc",
|
|
45
|
+
"1c53d3145cb38501a2422bf70f55a0c206b68c14e46da31e0e3dc63c7bee78d6",
|
|
46
|
+
"20bc5f2dedbf6423a543ba25183fab3c7268cdd2762e080f6d0b3387c4b3cad8",
|
|
47
|
+
"654faf29e2a177dba2409db4942007630674b86d3ff87d2b11d428e8b4d0db7f",
|
|
48
|
+
"c27979d019266887a1b86225d3c85b2d4553e85814ab2d791547ffc3f9dc664a",
|
|
49
|
+
"74f982351453468e39aff8373cb2649d57344b8002b85fc9c0df118d051817fc",
|
|
50
|
+
"891e59d7896f29a9af2bd1a9aec98ae2ebcc57d9685027e3f07067734e603923",
|
|
51
|
+
"4a8a234bbad11dc98aa27871ed69d351385f9476af0c15b8b426cfa5630cc3db",
|
|
52
|
+
"e42c967f02a434a061152d86d68fa0a3da70235f6f8262a792abeeace14fc419",
|
|
53
|
+
"c1560c78197abd62252f8932abf73d5f7506fcd157e98390230aec81fdf5e5ac",
|
|
54
|
+
"afa2cc8fe8b9b8785921f2e649d90d239f729223b2ce7460c0f22d79427c7b5e",
|
|
55
|
+
"d574f129616c989797b3a4a7988e9d4ca3afb59ed1de67136d459307b9a1807a",
|
|
56
|
+
"94b1c1e2fab8c51bb76d9d7bc07d23f8b509919009feb141f12562cab696d49a",
|
|
57
|
+
"79bf74936c7274d1ee529fb888f30fbf2a33a15308bedb8c77c1c50793790b42",
|
|
58
|
+
"2ee8962250f82c51fdbbf170c6c7fa9b69dca336434714ba607c6f816034cb08",
|
|
59
|
+
"6e8e665df73c6934d5b432f5aa898ba33cc9a40d630b049404a23fa5d2776298",
|
|
60
|
+
"3561d1bd9ae8c916eace5bdd1eb788cd743ad461cff58acf58ed5b2064b30b7b",
|
|
61
|
+
"46a48aa8aef9f09dabb9cf340dacc1889cf3eeebc49c6d2f26ff017cd087857c",
|
|
62
|
+
"561e0536b3cf214a4bf41b2bfb2209c8d659990a871e44bef9bc0136613a4ab9",
|
|
63
|
+
"7c1f3e331c8f59cc6dab7e310d9ad151628f72e5ca3db5a6ff592c8c976c1974",
|
|
64
|
+
"fb9d9d484edfa2c0a531a3e08074f52b6988f1082606f1eecaf0c58f117dbf3d",
|
|
65
|
+
"2810e928a4ce6e2ae016751a7e8b73496de47fe1926abe6ff486bc0eed23e3d7",
|
|
66
|
+
"723c3ede92f08e9ac12b7dc0b9e6a857ab0426d3418524f4eb50ad69f0228ccd",
|
|
67
|
+
"66a07cdf0c9d56558ad4f853203071b56adfa990a4fb065b49c10108f4996fe7",
|
|
68
|
+
"dc826253950962dbc2e5699dc5c138490c4233a23b39a5cf471a931cbbc689b8",
|
|
69
|
+
"ec29a6c60c70a25a5a238ba1d03407dadafee78c01bc9f921549da428c4fbc03",
|
|
70
|
+
"0564c17382a344aa22b5452989d2950f514bbd366e7b8289a458a4604c987641",
|
|
71
|
+
"157ca17a0c1fc9faed3c2557cdf83323210a40020e72497e8e48e1d0055e54c7",
|
|
72
|
+
"e643beaf8a92e574da88b9efa424852d34261be06496ca78d08acd84c38861b7",
|
|
73
|
+
"df982789a1598f53bbcf44f1b2991c86bfd7713b36b04a06cf52640cfc9c579d",
|
|
74
|
+
"e135e4dd1aebe24484909d56fb47619eb975f9790ef4541fdc87f63a3478ecf4",
|
|
75
|
+
"823235bafe4f9e523cc4ffeb16789a2e2d6b246ab5c248335eba364d54474241",
|
|
76
|
+
"a314bda9257484bbbabc1159b957d3aae1de0bca777bcd1462fdc52abf5a62fe",
|
|
77
|
+
"65f6699712af969820645bbe271f66fe92f5da41fd834e329dadeae7801bc112",
|
|
78
|
+
"17e812f25ef6d73de97b3464ca3fd2109433d9b5bc567e4e74feda35bf660492",
|
|
79
|
+
"37ff30e4c4384b810bd86e4472e9ada4b447cd6057daf8df3f923fac3b8b3d77",
|
|
80
|
+
"c55f13e79ce2bb698606f787551591e050e33e576cb845a2f40b03edac56f819",
|
|
81
|
+
"2124d3353d3f105c3780c937f1eaf377f756e59c6ab6a97ef780a718f3a47c7b",
|
|
82
|
+
"e581535019c839a7c383d0eca3de97a6a5701025d4a24ea56f01932f910cb99c",
|
|
83
|
+
"eb089f8a27da4b94b79e5d125ed6205849b1909d0a6c1d0f51c351be2d5b8466",
|
|
84
|
+
"36ee22c148c0282e112950f8e9939fed4b95d2f8a52c9cd2fd3744070df54333",
|
|
85
|
+
"c81b6509d23e889c5b5ec79fdfaf64fed1bf00ced8f4b224aa37b5a37821ccfe",
|
|
86
|
+
"b529c4de4a2fff86266d9b0318f15246ed30f4641b4156778ff9f3fca3099e32",
|
|
87
|
+
"647c2aa42b094baf52537a8f7ffb015b7aa212c67ca779853d263d420760d8b6",
|
|
88
|
+
"8043ea259b8bbb2bf488643376c2a2b9a3864e5617ea9ac5dc72966e289c07c2",
|
|
89
|
+
"bba0397ebaf1a6093a8adb117e84198bb5d2dcf9e8e7792221af9215cda8c10b",
|
|
90
|
+
"978ec66df04567ef8e4f02aa67b903535859f7bf24a5b63298cae1c938a72ab7",
|
|
91
|
+
"124befb26711b599a311b0b0d7352b6e980a594cd56c8447e32c1aba26818fd1",
|
|
92
|
+
"990ddff1fd05882796d2081ebc0d966a39c04af7120a9440562d71db2f48d65d",
|
|
93
|
+
"f396be48a42c9cca6b6fec0a8ee7f5d15f93a57fba3baf04ab5a7d86af68bea5",
|
|
94
|
+
"4f2487f0af254ea067e853bb6874d80d05c46f6322ca6e3a8815b37931533aea",
|
|
95
|
+
"dbacdcc317d0cc436dd93c8be4d1471c712bcc567bda8e9a47dc0c1543d33e19",
|
|
96
|
+
"6658cb8824afdf853e9cf3e667ac9d6bf566d54cc0f3b81ad62f7d7eeadff530",
|
|
97
|
+
"b2c4ea7f091911a1c3733e42a52c75d3ee1666ceb62faa4e8ddb15c8a4b12b14",
|
|
98
|
+
"0f29f04bebfe26d9b0ef7b802e3b2c725e85cdc03407915a3d5753436977f24e",
|
|
99
|
+
"7eadf05c33f72431c7414e60e4068051143154d647452e3888de4c61316950ea",
|
|
100
|
+
"45237d7cbb04c768d25f2dd13708f448f3c50af5262015d468ccbdb51fe0c090",
|
|
101
|
+
"21f14816d480bc5c2e5f0a3387a0897d928e926a4341df3d32d65416b6e387f1",
|
|
102
|
+
"a39bd710b9489e8bef21362c8df87ed76008939e89dbf8e6708a2c574f0727e0",
|
|
103
|
+
"cf365a79503e1457c23cd1ea8c9fa8c398d168288f90b5c1920af798508c2b56",
|
|
104
|
+
"d123f90b3a932edf596fd561e74fc8cc0adc481c3be118a7fa6a5834cd7b787c",
|
|
105
|
+
"cde02309ad295648d86fa4acefdf7224809abb2ab9b025f14ae41e021512c5ed",
|
|
106
|
+
"39835a1337c2afd9a9690cb9946752899a110df312d9d3aa68e10f85fad49dea",
|
|
107
|
+
]);
|
|
108
|
+
// Free commands that don't require token
|
|
109
|
+
const FREE_COMMANDS = new Set(['ref', 'help', 'exit', 'quit', 'q', 'clear', 'cls', 'activate']);
|
|
110
|
+
function hashToken(token) {
|
|
111
|
+
return createHash('sha256').update(token.trim().toUpperCase()).digest('hex');
|
|
112
|
+
}
|
|
113
|
+
export function validateToken(token) {
|
|
114
|
+
return TOKEN_HASHES.has(hashToken(token));
|
|
115
|
+
}
|
|
116
|
+
export function isActivated() {
|
|
117
|
+
const config = getConfig();
|
|
118
|
+
return !!(config.accessToken && TOKEN_HASHES.has(hashToken(config.accessToken)));
|
|
119
|
+
}
|
|
120
|
+
export function activateToken(token) {
|
|
121
|
+
if (validateToken(token)) {
|
|
122
|
+
saveConfig({ accessToken: token.trim().toUpperCase() });
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
export function isFreeCommand(cmd) {
|
|
128
|
+
return FREE_COMMANDS.has(cmd.toLowerCase());
|
|
129
|
+
}
|
|
130
|
+
// Session tracking for anti-cheat
|
|
131
|
+
const SESSION_FILE = () => join(getIcoaDir(), 'session-state.json');
|
|
132
|
+
function getDefaultSession() {
|
|
133
|
+
return {
|
|
134
|
+
startedAt: new Date().toISOString(),
|
|
135
|
+
lastExitAt: null,
|
|
136
|
+
lastResumeAt: null,
|
|
137
|
+
exitCount: 0,
|
|
138
|
+
totalAwaySeconds: 0,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export function getSession() {
|
|
142
|
+
const file = SESSION_FILE();
|
|
143
|
+
if (!existsSync(file))
|
|
144
|
+
return getDefaultSession();
|
|
145
|
+
try {
|
|
146
|
+
return { ...getDefaultSession(), ...JSON.parse(readFileSync(file, 'utf-8')) };
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return getDefaultSession();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
export function saveSession(session) {
|
|
153
|
+
const current = getSession();
|
|
154
|
+
const merged = { ...current, ...session };
|
|
155
|
+
writeFileSync(SESSION_FILE(), JSON.stringify(merged, null, 2));
|
|
156
|
+
}
|
|
157
|
+
export function recordExit() {
|
|
158
|
+
const session = getSession();
|
|
159
|
+
session.lastExitAt = new Date().toISOString();
|
|
160
|
+
session.exitCount++;
|
|
161
|
+
saveSession(session);
|
|
162
|
+
}
|
|
163
|
+
export function recordResume() {
|
|
164
|
+
const session = getSession();
|
|
165
|
+
if (!session.lastExitAt)
|
|
166
|
+
return null;
|
|
167
|
+
const exitTime = new Date(session.lastExitAt).getTime();
|
|
168
|
+
const now = Date.now();
|
|
169
|
+
const awaySeconds = Math.round((now - exitTime) / 1000);
|
|
170
|
+
session.lastResumeAt = new Date().toISOString();
|
|
171
|
+
session.totalAwaySeconds += awaySeconds;
|
|
172
|
+
saveSession(session);
|
|
173
|
+
return { awaySeconds, exitCount: session.exitCount };
|
|
174
|
+
}
|
package/dist/repl.d.ts
ADDED
package/dist/repl.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { isConnected, getConfig } from './lib/config.js';
|
|
4
|
+
import { isActivated, activateToken, isFreeCommand, recordExit, recordResume } from './lib/access.js';
|
|
5
|
+
const INTERCEPT = '__REPL_NO_EXIT__';
|
|
6
|
+
export function startRepl(program, resumeMode) {
|
|
7
|
+
const config = getConfig();
|
|
8
|
+
const connected = isConnected();
|
|
9
|
+
const realExit = process.exit.bind(process);
|
|
10
|
+
const activated = isActivated();
|
|
11
|
+
// Handle resume
|
|
12
|
+
if (resumeMode) {
|
|
13
|
+
const info = recordResume();
|
|
14
|
+
if (info) {
|
|
15
|
+
const mins = Math.floor(info.awaySeconds / 60);
|
|
16
|
+
const secs = info.awaySeconds % 60;
|
|
17
|
+
console.log(chalk.yellow(` Session resumed. Away: ${mins}m ${secs}s | Total exits: ${info.exitCount}`));
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
console.log(chalk.gray(' No previous session to resume.'));
|
|
21
|
+
}
|
|
22
|
+
console.log();
|
|
23
|
+
}
|
|
24
|
+
if (connected) {
|
|
25
|
+
console.log(chalk.gray(' Connected to: ') + chalk.white(config.ctfdUrl));
|
|
26
|
+
console.log(chalk.gray(' User: ') + chalk.white(config.userName));
|
|
27
|
+
}
|
|
28
|
+
else if (activated) {
|
|
29
|
+
console.log(chalk.green(' Access granted. ') + chalk.gray('Type: ') + chalk.white('join <url>') + chalk.gray(' to connect.'));
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
console.log(chalk.yellow(' Restricted mode. ') + chalk.gray('Type: ') + chalk.white('activate <token>') + chalk.gray(' to unlock.'));
|
|
33
|
+
console.log(chalk.gray(' Available: ') + chalk.white('ref [topic]') + chalk.gray(', ') + chalk.white('help') + chalk.gray(', ') + chalk.white('exit'));
|
|
34
|
+
}
|
|
35
|
+
console.log();
|
|
36
|
+
console.log(chalk.gray(' Type ') + chalk.white('help') + chalk.gray(' for commands, ') + chalk.white('exit') + chalk.gray(' to quit.'));
|
|
37
|
+
console.log();
|
|
38
|
+
program.exitOverride();
|
|
39
|
+
program.configureOutput({
|
|
40
|
+
writeErr: () => { },
|
|
41
|
+
writeOut: (str) => { console.log(str); },
|
|
42
|
+
});
|
|
43
|
+
const rl = createInterface({
|
|
44
|
+
input: process.stdin,
|
|
45
|
+
output: process.stdout,
|
|
46
|
+
prompt: chalk.cyan('icoa> '),
|
|
47
|
+
terminal: true,
|
|
48
|
+
});
|
|
49
|
+
let processing = false;
|
|
50
|
+
rl.prompt();
|
|
51
|
+
rl.on('line', async (line) => {
|
|
52
|
+
if (processing)
|
|
53
|
+
return;
|
|
54
|
+
const input = line.trim();
|
|
55
|
+
if (!input) {
|
|
56
|
+
rl.prompt();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// Exit — record and quit
|
|
60
|
+
if (input === 'exit' || input === 'quit' || input === 'q') {
|
|
61
|
+
recordExit();
|
|
62
|
+
console.log(chalk.gray(' Session saved. Use ') + chalk.white('icoa --resume') + chalk.gray(' to continue.'));
|
|
63
|
+
realExit(0);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// Help
|
|
67
|
+
if (input === 'help' || input === '?') {
|
|
68
|
+
printReplHelp(isActivated());
|
|
69
|
+
rl.prompt();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Clear
|
|
73
|
+
if (input === 'clear' || input === 'cls') {
|
|
74
|
+
console.clear();
|
|
75
|
+
rl.prompt();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Activate token
|
|
79
|
+
if (input.startsWith('activate ')) {
|
|
80
|
+
const token = input.slice(9).trim();
|
|
81
|
+
if (activateToken(token)) {
|
|
82
|
+
console.log(chalk.green(' Access granted! All commands unlocked.'));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
console.log(chalk.red(' Invalid token.'));
|
|
86
|
+
}
|
|
87
|
+
console.log();
|
|
88
|
+
rl.prompt();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (input === 'activate') {
|
|
92
|
+
console.log(chalk.gray(' Usage: ') + chalk.white('activate <token>'));
|
|
93
|
+
console.log();
|
|
94
|
+
rl.prompt();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Token check — only ref allowed without activation
|
|
98
|
+
const cmd = input.split(/\s+/)[0].toLowerCase();
|
|
99
|
+
if (!isActivated() && !isFreeCommand(cmd)) {
|
|
100
|
+
console.log(chalk.yellow(' Restricted mode. ') + chalk.gray('Enter your access token:'));
|
|
101
|
+
console.log(chalk.white(' activate <token>'));
|
|
102
|
+
console.log();
|
|
103
|
+
console.log(chalk.gray(' Free commands: ') + chalk.white('ref [topic]') + chalk.gray(', ') + chalk.white('help') + chalk.gray(', ') + chalk.white('exit'));
|
|
104
|
+
console.log();
|
|
105
|
+
rl.prompt();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
processing = true;
|
|
109
|
+
const args = mapCommand(input);
|
|
110
|
+
process.exit = (() => {
|
|
111
|
+
throw new Error(INTERCEPT);
|
|
112
|
+
});
|
|
113
|
+
try {
|
|
114
|
+
await program.parseAsync(['node', 'icoa', ...args]);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
118
|
+
if (msg === INTERCEPT) {
|
|
119
|
+
// Command tried to exit — continue REPL
|
|
120
|
+
}
|
|
121
|
+
else if (msg.includes('commander.unknownCommand')) {
|
|
122
|
+
console.log(chalk.yellow(` Unknown command: ${cmd}. Type 'help' for commands.`));
|
|
123
|
+
}
|
|
124
|
+
else if (msg.includes('commander.')) {
|
|
125
|
+
// Internal Commander errors — ignore
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
process.exit = realExit;
|
|
130
|
+
processing = false;
|
|
131
|
+
}
|
|
132
|
+
console.log();
|
|
133
|
+
rl.prompt();
|
|
134
|
+
});
|
|
135
|
+
rl.on('close', () => {
|
|
136
|
+
recordExit();
|
|
137
|
+
realExit(0);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
function mapCommand(input) {
|
|
141
|
+
const parts = input.split(/\s+/);
|
|
142
|
+
const cmd = parts[0].toLowerCase();
|
|
143
|
+
const rest = parts.slice(1);
|
|
144
|
+
const ctfShortcuts = {
|
|
145
|
+
'join': ['ctf', 'join', ...rest],
|
|
146
|
+
'activate': ['ctf', 'activate', ...rest],
|
|
147
|
+
'challenges': ['ctf', 'challenges'],
|
|
148
|
+
'ch': ['ctf', 'challenges'],
|
|
149
|
+
'open': ['ctf', 'open', ...rest],
|
|
150
|
+
'submit': ['ctf', 'submit', ...rest],
|
|
151
|
+
'flag': ['ctf', 'submit', ...rest],
|
|
152
|
+
'scoreboard': ['ctf', 'scoreboard', ...rest],
|
|
153
|
+
'sb': ['ctf', 'scoreboard', ...rest],
|
|
154
|
+
'status': ['ctf', 'status'],
|
|
155
|
+
'time': ['ctf', 'time'],
|
|
156
|
+
};
|
|
157
|
+
if (ctfShortcuts[cmd]) {
|
|
158
|
+
return ctfShortcuts[cmd];
|
|
159
|
+
}
|
|
160
|
+
const directCommands = [
|
|
161
|
+
'hint', 'hint-b', 'hint-c', 'hint-budget',
|
|
162
|
+
'ref', 'shell', 'files', 'connect', 'note',
|
|
163
|
+
'log', 'lang', 'setup', 'model',
|
|
164
|
+
'ctf',
|
|
165
|
+
];
|
|
166
|
+
if (directCommands.includes(cmd)) {
|
|
167
|
+
return [cmd, ...rest];
|
|
168
|
+
}
|
|
169
|
+
return parts;
|
|
170
|
+
}
|
|
171
|
+
function printReplHelp(activated) {
|
|
172
|
+
console.log();
|
|
173
|
+
if (!activated) {
|
|
174
|
+
console.log(chalk.bold.yellow(' Restricted Mode — activate with a token to unlock all commands'));
|
|
175
|
+
console.log();
|
|
176
|
+
console.log(chalk.white(' activate <token> ') + chalk.gray('Unlock full access'));
|
|
177
|
+
console.log(chalk.white(' ref [topic] ') + chalk.gray('Quick reference'));
|
|
178
|
+
console.log(chalk.white(' exit ') + chalk.gray('Quit'));
|
|
179
|
+
console.log();
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
console.log(chalk.bold.white(' Competition'));
|
|
183
|
+
console.log(chalk.white(' join <url> ') + chalk.gray('Connect to CTFd'));
|
|
184
|
+
console.log(chalk.white(' challenges (ch) ') + chalk.gray('List challenges'));
|
|
185
|
+
console.log(chalk.white(' open <id> ') + chalk.gray('View challenge details'));
|
|
186
|
+
console.log(chalk.white(' submit <id> <flag> ') + chalk.gray('Submit a flag'));
|
|
187
|
+
console.log(chalk.white(' scoreboard (sb) ') + chalk.gray('View scoreboard'));
|
|
188
|
+
console.log(chalk.white(' status ') + chalk.gray('Competition status'));
|
|
189
|
+
console.log(chalk.white(' time ') + chalk.gray('Countdown timer'));
|
|
190
|
+
console.log();
|
|
191
|
+
console.log(chalk.bold.white(' AI Hints'));
|
|
192
|
+
console.log(chalk.white(' hint <question> ') + chalk.gray('Level A — General guidance'));
|
|
193
|
+
console.log(chalk.white(' hint-b <question> ') + chalk.gray('Level B — Deep analysis'));
|
|
194
|
+
console.log(chalk.white(' hint-c <question> ') + chalk.gray('Level C — Critical assist'));
|
|
195
|
+
console.log(chalk.white(' hint budget ') + chalk.gray('Check remaining budget'));
|
|
196
|
+
console.log();
|
|
197
|
+
console.log(chalk.bold.white(' Tools'));
|
|
198
|
+
console.log(chalk.white(' ref [topic] ') + chalk.gray('Quick reference'));
|
|
199
|
+
console.log(chalk.white(' shell ') + chalk.gray('Docker sandbox'));
|
|
200
|
+
console.log(chalk.white(' files <id> ') + chalk.gray('Download challenge files'));
|
|
201
|
+
console.log(chalk.white(' connect <id> ') + chalk.gray('Connect to remote target'));
|
|
202
|
+
console.log(chalk.white(' note [text] ') + chalk.gray('Personal notepad'));
|
|
203
|
+
console.log(chalk.white(' log ') + chalk.gray('Session history'));
|
|
204
|
+
console.log();
|
|
205
|
+
console.log(chalk.bold.white(' System'));
|
|
206
|
+
console.log(chalk.white(' setup ') + chalk.gray('Configure settings'));
|
|
207
|
+
console.log(chalk.white(' lang [code] ') + chalk.gray('Switch language'));
|
|
208
|
+
console.log(chalk.white(' clear ') + chalk.gray('Clear screen'));
|
|
209
|
+
console.log(chalk.white(' exit ') + chalk.gray('Quit (session saved)'));
|
|
210
|
+
console.log();
|
|
211
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -98,6 +98,7 @@ export interface IcoaConfig {
|
|
|
98
98
|
competitionEndsAt: string;
|
|
99
99
|
geminiApiKey: string;
|
|
100
100
|
geminiModel: string;
|
|
101
|
+
accessToken: string;
|
|
101
102
|
}
|
|
102
103
|
export type CompetitionState = 'pre_competition' | 'demo' | 'live' | 'finished' | 'unknown';
|
|
103
104
|
export type HintLevel = 'A' | 'B' | 'C';
|
package/dist/types/index.js
CHANGED