icoa-cli 2.19.89 → 2.19.91
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/commands/theme.d.ts +13 -0
- package/dist/commands/theme.js +50 -0
- package/dist/commands/tutorial.js +3 -0
- package/dist/index.js +6 -2
- package/dist/lib/theme.d.ts +2 -1
- package/dist/lib/theme.js +24 -6
- package/dist/repl.js +10 -3
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `theme` command — toggle terminal color scheme.
|
|
3
|
+
*
|
|
4
|
+
* Two variants:
|
|
5
|
+
* - `dark` (default): Darcula — #2B2B2B bg + #A9B7C6 fg, easy on the eyes.
|
|
6
|
+
* - `high-contrast`: pure black bg + pure white fg for low-vision users,
|
|
7
|
+
* projectors, and cheap LCDs where Darcula's grays disappear.
|
|
8
|
+
*
|
|
9
|
+
* Change applies on next `icoa` launch (the current session keeps its paint
|
|
10
|
+
* to avoid flicker while students are mid-question).
|
|
11
|
+
*/
|
|
12
|
+
import { Command } from 'commander';
|
|
13
|
+
export declare function registerThemeCommand(program: Command): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `theme` command — toggle terminal color scheme.
|
|
3
|
+
*
|
|
4
|
+
* Two variants:
|
|
5
|
+
* - `dark` (default): Darcula — #2B2B2B bg + #A9B7C6 fg, easy on the eyes.
|
|
6
|
+
* - `high-contrast`: pure black bg + pure white fg for low-vision users,
|
|
7
|
+
* projectors, and cheap LCDs where Darcula's grays disappear.
|
|
8
|
+
*
|
|
9
|
+
* Change applies on next `icoa` launch (the current session keeps its paint
|
|
10
|
+
* to avoid flicker while students are mid-question).
|
|
11
|
+
*/
|
|
12
|
+
import chalk from 'chalk';
|
|
13
|
+
import { getConfig, saveConfig } from '../lib/config.js';
|
|
14
|
+
import { logCommand } from '../lib/logger.js';
|
|
15
|
+
const VALID = ['dark', 'high-contrast'];
|
|
16
|
+
export function registerThemeCommand(program) {
|
|
17
|
+
program
|
|
18
|
+
.command('theme [variant]')
|
|
19
|
+
.description('Switch color scheme (dark | high-contrast)')
|
|
20
|
+
.action((variant) => {
|
|
21
|
+
logCommand(`theme ${variant || ''}`);
|
|
22
|
+
const config = getConfig();
|
|
23
|
+
const current = config.themeVariant === 'high-contrast' ? 'high-contrast' : 'dark';
|
|
24
|
+
if (!variant) {
|
|
25
|
+
console.log();
|
|
26
|
+
console.log(chalk.gray(' Current theme: ') + chalk.white(current));
|
|
27
|
+
console.log();
|
|
28
|
+
console.log(chalk.gray(' Available themes:'));
|
|
29
|
+
console.log(' ' + chalk.white('dark ') + chalk.gray('Darcula — gray on dark gray (default)'));
|
|
30
|
+
console.log(' ' + chalk.white('high-contrast ') + chalk.gray('Pure white on pure black — low vision / projectors'));
|
|
31
|
+
console.log();
|
|
32
|
+
console.log(chalk.gray(' Usage: ') + chalk.cyan('theme <name>'));
|
|
33
|
+
console.log(chalk.gray(' Applies on next ') + chalk.cyan('icoa') + chalk.gray(' launch.'));
|
|
34
|
+
console.log();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (!VALID.includes(variant)) {
|
|
38
|
+
console.log();
|
|
39
|
+
console.log(chalk.red(` Unknown theme: ${variant}`));
|
|
40
|
+
console.log(chalk.gray(' Valid: ') + chalk.white(VALID.join(', ')));
|
|
41
|
+
console.log();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
saveConfig({ themeVariant: variant });
|
|
45
|
+
console.log();
|
|
46
|
+
console.log(chalk.green(` ✓ Theme set to: ${variant}`));
|
|
47
|
+
console.log(chalk.gray(' Restart ') + chalk.cyan('icoa') + chalk.gray(' to see the new colors.'));
|
|
48
|
+
console.log();
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -112,6 +112,9 @@ export async function runTutorial() {
|
|
|
112
112
|
console.log(' ' + chalk.cyan('demo') + chalk.gray(' — free practice (10 questions, no timer)'));
|
|
113
113
|
console.log(' ' + chalk.cyan('exam <token>') + chalk.gray(' — real exam (when you have an organizer-issued token)'));
|
|
114
114
|
console.log(' ' + chalk.cyan('lang es') + chalk.gray(' — switch UI language (17 supported)'));
|
|
115
|
+
console.log(' ' + chalk.cyan('theme high-contrast') + chalk.gray(' — low-vision-friendly colors (restart after)'));
|
|
116
|
+
console.log();
|
|
117
|
+
console.log(chalk.gray(' Full beginner guide: ') + chalk.cyan.underline('https://github.com/newaipanda/ICOA_CLI/blob/main/docs/getting-started-k12.md'));
|
|
115
118
|
console.log();
|
|
116
119
|
}
|
|
117
120
|
export function registerTutorialCommand(program) {
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { registerAi4ctfCommand } from './commands/ai4ctf.js';
|
|
|
16
16
|
import { registerExamCommand } from './commands/exam.js';
|
|
17
17
|
import { registerCtf4aiDemoCommand } from './commands/ctf4ai-demo.js';
|
|
18
18
|
import { registerTutorialCommand } from './commands/tutorial.js';
|
|
19
|
+
import { registerThemeCommand } from './commands/theme.js';
|
|
19
20
|
import { getConfig, saveConfig } from './lib/config.js';
|
|
20
21
|
import { startRepl } from './repl.js';
|
|
21
22
|
import { setTerminalTheme } from './lib/theme.js';
|
|
@@ -105,8 +106,10 @@ program
|
|
|
105
106
|
.description('ICOA CLI — CLI-Native CTF Competition Terminal')
|
|
106
107
|
.option('--resume', 'Resume previous session')
|
|
107
108
|
.action(async (opts) => {
|
|
108
|
-
//
|
|
109
|
-
|
|
109
|
+
// Terminal theme — Darcula by default, 'high-contrast' for low-vision users.
|
|
110
|
+
// Variant stored in config.themeVariant; toggled via `theme` REPL command.
|
|
111
|
+
const cfg = getConfig();
|
|
112
|
+
setTerminalTheme(cfg.themeVariant === 'high-contrast' ? 'high-contrast' : 'dark');
|
|
110
113
|
checkForUpdates();
|
|
111
114
|
// T2-7: UTF-8 locale sanity check. Non-UTF-8 terminals mangle the box-
|
|
112
115
|
// drawing banner, the ✓/✗/⚠ glyphs used throughout the CLI, and any
|
|
@@ -147,6 +150,7 @@ registerAi4ctfCommand(program);
|
|
|
147
150
|
registerExamCommand(program);
|
|
148
151
|
registerCtf4aiDemoCommand(program);
|
|
149
152
|
registerTutorialCommand(program);
|
|
153
|
+
registerThemeCommand(program);
|
|
150
154
|
// Hidden command: switch AI model
|
|
151
155
|
program
|
|
152
156
|
.command('model', { hidden: true })
|
package/dist/lib/theme.d.ts
CHANGED
package/dist/lib/theme.js
CHANGED
|
@@ -19,20 +19,35 @@
|
|
|
19
19
|
//
|
|
20
20
|
// Legacy cmd.exe (pre-Win10 1809) can't run Node 22 anyway, so no separate
|
|
21
21
|
// fallback path is needed.
|
|
22
|
-
const
|
|
22
|
+
const OSC_INIT_DARK = '\x1b]10;#A9B7C6\x07' + // default fg
|
|
23
23
|
'\x1b]11;#2B2B2B\x07' + // default bg
|
|
24
24
|
'\x1b]12;#A9B7C6\x07'; // cursor color
|
|
25
|
+
// High-contrast: pure black bg + pure white fg. For students with low vision
|
|
26
|
+
// or screens where Darcula's subtle grays wash out (e.g., projectors, cheap
|
|
27
|
+
// LCDs under fluorescent light). Still works with existing chalk colors —
|
|
28
|
+
// cyan/green/yellow/red all show up clearly against pure black.
|
|
29
|
+
const OSC_INIT_HC = '\x1b]10;#FFFFFF\x07' +
|
|
30
|
+
'\x1b]11;#000000\x07' +
|
|
31
|
+
'\x1b]12;#FFFFFF\x07';
|
|
25
32
|
const OSC_RESET = '\x1b]110\x07' + // reset default fg
|
|
26
33
|
'\x1b]111\x07' + // reset default bg
|
|
27
34
|
'\x1b]112\x07'; // reset cursor color
|
|
28
|
-
const
|
|
35
|
+
const SGR_INIT_TRUECOLOR_DARK = '\x1b[38;2;169;183;198m' + // fg #A9B7C6
|
|
29
36
|
'\x1b[48;2;43;43;43m' + // bg #2B2B2B
|
|
30
37
|
'\x1b[2J' +
|
|
31
38
|
'\x1b[H';
|
|
32
|
-
const
|
|
39
|
+
const SGR_INIT_256_DARK = '\x1b[38;5;250m' + // fg ≈ #BCBCBC
|
|
33
40
|
'\x1b[48;5;235m' + // bg ≈ #262626
|
|
34
41
|
'\x1b[2J' +
|
|
35
42
|
'\x1b[H';
|
|
43
|
+
const SGR_INIT_TRUECOLOR_HC = '\x1b[38;2;255;255;255m' + // fg pure white
|
|
44
|
+
'\x1b[48;2;0;0;0m' + // bg pure black
|
|
45
|
+
'\x1b[2J' +
|
|
46
|
+
'\x1b[H';
|
|
47
|
+
const SGR_INIT_256_HC = '\x1b[38;5;231m' + // fg white (231 = pure white in 256)
|
|
48
|
+
'\x1b[48;5;16m' + // bg black (16 = pure black in 256)
|
|
49
|
+
'\x1b[2J' +
|
|
50
|
+
'\x1b[H';
|
|
36
51
|
const SGR_RESET = '\x1b[0m\x1b[2J\x1b[H';
|
|
37
52
|
function supportsAnsi() {
|
|
38
53
|
if (!process.stdout.isTTY)
|
|
@@ -58,13 +73,16 @@ function isAppleTerminal() {
|
|
|
58
73
|
return process.env.TERM_PROGRAM === 'Apple_Terminal';
|
|
59
74
|
}
|
|
60
75
|
let armed = false;
|
|
61
|
-
export function setTerminalTheme() {
|
|
76
|
+
export function setTerminalTheme(variant = 'dark') {
|
|
62
77
|
if (!supportsAnsi())
|
|
63
78
|
return;
|
|
64
79
|
if (isIcoaTerminal())
|
|
65
80
|
return; // host is already Darcula; nothing to do
|
|
66
|
-
const
|
|
67
|
-
|
|
81
|
+
const osc = variant === 'high-contrast' ? OSC_INIT_HC : OSC_INIT_DARK;
|
|
82
|
+
const sgr = isAppleTerminal()
|
|
83
|
+
? (variant === 'high-contrast' ? SGR_INIT_256_HC : SGR_INIT_256_DARK)
|
|
84
|
+
: (variant === 'high-contrast' ? SGR_INIT_TRUECOLOR_HC : SGR_INIT_TRUECOLOR_DARK);
|
|
85
|
+
process.stdout.write(osc + sgr);
|
|
68
86
|
if (!armed) {
|
|
69
87
|
armed = true;
|
|
70
88
|
// Belt-and-braces cleanup on every exit path. Without these, Ctrl+C leaves
|
package/dist/repl.js
CHANGED
|
@@ -179,16 +179,23 @@ function printSelectionMenu() {
|
|
|
179
179
|
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
180
180
|
}
|
|
181
181
|
else {
|
|
182
|
-
// State 2: fully prepared.
|
|
182
|
+
// State 2: fully prepared. Exam entry is the primary CTA — list it FIRST
|
|
183
|
+
// and make the token format + origin obvious. Students landing here have
|
|
184
|
+
// already done demo; without a concrete example they stare at "<token>"
|
|
185
|
+
// and don't know what to type or where the token comes from.
|
|
183
186
|
const plural = stats.attempts === 1 ? 'attempt' : 'attempts';
|
|
184
187
|
console.log(chalk.green(' ✓ Demo completed ') + chalk.gray(`(${stats.attempts} ${plural})`));
|
|
185
188
|
console.log(chalk.green(' ✓ Environment ready'));
|
|
186
189
|
console.log(chalk.yellow(' → Enter your exam token to begin.'));
|
|
190
|
+
console.log(chalk.gray(' (10-char code from your organizer, starts with your country code like ') + chalk.cyan('UA') + chalk.gray(' — case-insensitive)'));
|
|
187
191
|
console.log();
|
|
188
192
|
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
193
|
+
console.log(chalk.bold.yellow(' exam <token>') + chalk.gray(' Enter exam (primary action — use your organizer-issued token)'));
|
|
194
|
+
console.log(chalk.gray(' format: ') + chalk.white('exam UAxxxxxxxx') + chalk.gray(' (2-letter country prefix + 8 chars)'));
|
|
195
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
196
|
+
console.log(chalk.gray(' Other commands:'));
|
|
189
197
|
console.log(chalk.white(' demo') + chalk.gray(` ${demoLine}`));
|
|
190
198
|
console.log(chalk.white(' exam setup') + chalk.gray(' Re-verify tool environment'));
|
|
191
|
-
console.log(chalk.bold.yellow(' exam <token>') + chalk.gray(' Enter exam with access token'));
|
|
192
199
|
console.log(chalk.white(' lang es') + chalk.gray(' Switch language (17 supported)'));
|
|
193
200
|
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
194
201
|
}
|
|
@@ -984,7 +991,7 @@ export async function startRepl(program, resumeMode) {
|
|
|
984
991
|
const KNOWN_CMDS = [
|
|
985
992
|
'ctf', 'hint', 'hint-b', 'hint-c', 'hint-budget', 'ref', 'shell',
|
|
986
993
|
'files', 'connect', 'note', 'log', 'lang', 'setup', 'env',
|
|
987
|
-
'ai4ctf', 'exam', 'ctf4ai', 'tutorial',
|
|
994
|
+
'ai4ctf', 'exam', 'ctf4ai', 'tutorial', 'theme',
|
|
988
995
|
'clear', 'cls', 'quit', 'exit', 'back', 'menu', 'help',
|
|
989
996
|
'continue', 'activate', 'demo', 'challenges', 'status', 'scoreboard',
|
|
990
997
|
'join', 'logout',
|
package/dist/types/index.d.ts
CHANGED
|
@@ -106,6 +106,7 @@ export interface IcoaConfig {
|
|
|
106
106
|
country: string;
|
|
107
107
|
mode: IcoaMode | '';
|
|
108
108
|
demoIntroSeen: boolean;
|
|
109
|
+
themeVariant?: 'dark' | 'high-contrast';
|
|
109
110
|
}
|
|
110
111
|
export type CompetitionState = 'pre_competition' | 'demo' | 'live' | 'finished' | 'unknown';
|
|
111
112
|
export type IcoaMode = 'selection' | 'olympiad' | 'organizer';
|
package/dist/types/index.js
CHANGED