icoa-cli 1.2.0 → 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 +7 -6
- package/dist/lib/access.d.ts +19 -0
- package/dist/lib/access.js +174 -0
- package/dist/repl.d.ts +1 -1
- package/dist/repl.js +71 -13
- 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
|
@@ -29,7 +29,7 @@ ${chalk.cyan('║')} ${
|
|
|
29
29
|
${chalk.cyan('║')} ${chalk.green.bold('AI4CTF')} ${chalk.gray('Use AI to solve challenges')} ${chalk.cyan('║')}
|
|
30
30
|
${chalk.cyan('║')} ${chalk.red.bold('CTF4AI')} ${chalk.gray('Hack, attack & evaluate AI systems')} ${chalk.cyan('║')}
|
|
31
31
|
${chalk.cyan('║')} ${chalk.cyan('║')}
|
|
32
|
-
${chalk.cyan('║')} ${chalk.gray('CLI-Native Competition Terminal v1.
|
|
32
|
+
${chalk.cyan('║')} ${chalk.gray('CLI-Native Competition Terminal v1.3.0')} ${chalk.cyan('║')}
|
|
33
33
|
${chalk.cyan('║')} ${chalk.cyan('║')}
|
|
34
34
|
${chalk.cyan('╚══════════════════════════════════════════════════════════╝')}
|
|
35
35
|
`;
|
|
@@ -50,13 +50,14 @@ process.on('unhandledRejection', (reason) => {
|
|
|
50
50
|
const program = new Command();
|
|
51
51
|
program
|
|
52
52
|
.name('icoa')
|
|
53
|
-
.version('1.
|
|
53
|
+
.version('1.2.0')
|
|
54
54
|
.description('ICOA CLI — CLI-Native CTF Competition Terminal')
|
|
55
|
-
.
|
|
55
|
+
.option('--resume', 'Resume previous session')
|
|
56
|
+
.action((opts) => {
|
|
56
57
|
console.log(BANNER);
|
|
57
|
-
// If running interactively (no extra args), start REPL
|
|
58
|
-
if (process.argv.length <= 2) {
|
|
59
|
-
startRepl(program);
|
|
58
|
+
// If running interactively (no extra args or --resume), start REPL
|
|
59
|
+
if (process.argv.length <= 2 || opts.resume) {
|
|
60
|
+
startRepl(program, !!opts.resume);
|
|
60
61
|
return;
|
|
61
62
|
}
|
|
62
63
|
});
|
|
@@ -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
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
export declare function startRepl(program: Command): void;
|
|
2
|
+
export declare function startRepl(program: Command, resumeMode: boolean): void;
|
package/dist/repl.js
CHANGED
|
@@ -1,23 +1,40 @@
|
|
|
1
1
|
import { createInterface } from 'node:readline';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { isConnected, getConfig } from './lib/config.js';
|
|
4
|
-
|
|
4
|
+
import { isActivated, activateToken, isFreeCommand, recordExit, recordResume } from './lib/access.js';
|
|
5
5
|
const INTERCEPT = '__REPL_NO_EXIT__';
|
|
6
|
-
export function startRepl(program) {
|
|
6
|
+
export function startRepl(program, resumeMode) {
|
|
7
7
|
const config = getConfig();
|
|
8
8
|
const connected = isConnected();
|
|
9
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
|
+
}
|
|
10
24
|
if (connected) {
|
|
11
25
|
console.log(chalk.gray(' Connected to: ') + chalk.white(config.ctfdUrl));
|
|
12
26
|
console.log(chalk.gray(' User: ') + chalk.white(config.userName));
|
|
13
27
|
}
|
|
28
|
+
else if (activated) {
|
|
29
|
+
console.log(chalk.green(' Access granted. ') + chalk.gray('Type: ') + chalk.white('join <url>') + chalk.gray(' to connect.'));
|
|
30
|
+
}
|
|
14
31
|
else {
|
|
15
|
-
console.log(chalk.
|
|
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'));
|
|
16
34
|
}
|
|
17
35
|
console.log();
|
|
18
36
|
console.log(chalk.gray(' Type ') + chalk.white('help') + chalk.gray(' for commands, ') + chalk.white('exit') + chalk.gray(' to quit.'));
|
|
19
37
|
console.log();
|
|
20
|
-
// Configure Commander to throw instead of calling process.exit
|
|
21
38
|
program.exitOverride();
|
|
22
39
|
program.configureOutput({
|
|
23
40
|
writeErr: () => { },
|
|
@@ -39,25 +56,57 @@ export function startRepl(program) {
|
|
|
39
56
|
rl.prompt();
|
|
40
57
|
return;
|
|
41
58
|
}
|
|
42
|
-
//
|
|
59
|
+
// Exit — record and quit
|
|
43
60
|
if (input === 'exit' || input === 'quit' || input === 'q') {
|
|
44
|
-
|
|
61
|
+
recordExit();
|
|
62
|
+
console.log(chalk.gray(' Session saved. Use ') + chalk.white('icoa --resume') + chalk.gray(' to continue.'));
|
|
45
63
|
realExit(0);
|
|
46
64
|
return;
|
|
47
65
|
}
|
|
66
|
+
// Help
|
|
48
67
|
if (input === 'help' || input === '?') {
|
|
49
|
-
printReplHelp();
|
|
68
|
+
printReplHelp(isActivated());
|
|
50
69
|
rl.prompt();
|
|
51
70
|
return;
|
|
52
71
|
}
|
|
72
|
+
// Clear
|
|
53
73
|
if (input === 'clear' || input === 'cls') {
|
|
54
74
|
console.clear();
|
|
55
75
|
rl.prompt();
|
|
56
76
|
return;
|
|
57
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
|
+
}
|
|
58
108
|
processing = true;
|
|
59
109
|
const args = mapCommand(input);
|
|
60
|
-
// Override process.exit so commands don't kill the REPL
|
|
61
110
|
process.exit = (() => {
|
|
62
111
|
throw new Error(INTERCEPT);
|
|
63
112
|
});
|
|
@@ -67,17 +116,16 @@ export function startRepl(program) {
|
|
|
67
116
|
catch (err) {
|
|
68
117
|
const msg = err instanceof Error ? err.message : String(err);
|
|
69
118
|
if (msg === INTERCEPT) {
|
|
70
|
-
// Command tried to exit —
|
|
119
|
+
// Command tried to exit — continue REPL
|
|
71
120
|
}
|
|
72
121
|
else if (msg.includes('commander.unknownCommand')) {
|
|
73
|
-
console.log(chalk.yellow(` Unknown command: ${
|
|
122
|
+
console.log(chalk.yellow(` Unknown command: ${cmd}. Type 'help' for commands.`));
|
|
74
123
|
}
|
|
75
124
|
else if (msg.includes('commander.')) {
|
|
76
125
|
// Internal Commander errors — ignore
|
|
77
126
|
}
|
|
78
127
|
}
|
|
79
128
|
finally {
|
|
80
|
-
// Always restore real process.exit
|
|
81
129
|
process.exit = realExit;
|
|
82
130
|
processing = false;
|
|
83
131
|
}
|
|
@@ -85,6 +133,7 @@ export function startRepl(program) {
|
|
|
85
133
|
rl.prompt();
|
|
86
134
|
});
|
|
87
135
|
rl.on('close', () => {
|
|
136
|
+
recordExit();
|
|
88
137
|
realExit(0);
|
|
89
138
|
});
|
|
90
139
|
}
|
|
@@ -119,8 +168,17 @@ function mapCommand(input) {
|
|
|
119
168
|
}
|
|
120
169
|
return parts;
|
|
121
170
|
}
|
|
122
|
-
function printReplHelp() {
|
|
171
|
+
function printReplHelp(activated) {
|
|
123
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
|
+
}
|
|
124
182
|
console.log(chalk.bold.white(' Competition'));
|
|
125
183
|
console.log(chalk.white(' join <url> ') + chalk.gray('Connect to CTFd'));
|
|
126
184
|
console.log(chalk.white(' challenges (ch) ') + chalk.gray('List challenges'));
|
|
@@ -148,6 +206,6 @@ function printReplHelp() {
|
|
|
148
206
|
console.log(chalk.white(' setup ') + chalk.gray('Configure settings'));
|
|
149
207
|
console.log(chalk.white(' lang [code] ') + chalk.gray('Switch language'));
|
|
150
208
|
console.log(chalk.white(' clear ') + chalk.gray('Clear screen'));
|
|
151
|
-
console.log(chalk.white(' exit ') + chalk.gray('Quit
|
|
209
|
+
console.log(chalk.white(' exit ') + chalk.gray('Quit (session saved)'));
|
|
152
210
|
console.log();
|
|
153
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