icoa-cli 2.19.92 → 2.19.94
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/exam.js +77 -1
- package/dist/commands/lang.js +6 -1
- package/dist/index.js +0 -2
- package/dist/repl.js +7 -5
- package/package.json +1 -1
- package/dist/commands/tutorial.d.ts +0 -12
- package/dist/commands/tutorial.js +0 -127
package/dist/commands/exam.js
CHANGED
|
@@ -1578,10 +1578,36 @@ export function registerExamCommand(program) {
|
|
|
1578
1578
|
.description('Enter exam with access token (no login needed)')
|
|
1579
1579
|
.action(async (code) => {
|
|
1580
1580
|
logCommand(`exam token ${code}`);
|
|
1581
|
-
const { getRealExamState, saveExamState: saveExamStateFn } = await import('../lib/exam-state.js');
|
|
1581
|
+
const { getRealExamState, saveExamState: saveExamStateFn, clearExamState: clearState } = await import('../lib/exam-state.js');
|
|
1582
1582
|
const existing = getRealExamState();
|
|
1583
1583
|
if (existing) {
|
|
1584
1584
|
const existingToken = existing.session.token;
|
|
1585
|
+
// Auto-recover from abandoned/expired sessions. If the stored state's
|
|
1586
|
+
// deadline is >30 min in the past, the session is unusable (past the
|
|
1587
|
+
// server's submit grace window). If user types a DIFFERENT token in
|
|
1588
|
+
// this state, they clearly mean "start fresh". Clear silently rather
|
|
1589
|
+
// than locking them out forever. Prevents the stale-A-paper-content +
|
|
1590
|
+
// "Could not reload questions in new language" lockout.
|
|
1591
|
+
const deadlineNow = getExamDeadline();
|
|
1592
|
+
const typedDiffers = !!existingToken && existingToken.trim().toUpperCase() !== code.trim().toUpperCase();
|
|
1593
|
+
const expiredPast30min = deadlineNow ? (deadlineNow.getTime() + 30 * 60 * 1000) < Date.now() : false;
|
|
1594
|
+
if (typedDiffers && expiredPast30min) {
|
|
1595
|
+
clearState();
|
|
1596
|
+
console.log();
|
|
1597
|
+
console.log(chalk.gray(' (previous exam session expired — cleared state, starting fresh with your new token)'));
|
|
1598
|
+
console.log();
|
|
1599
|
+
// Fall through to the fresh-start flow below (skip the "already in progress" guard)
|
|
1600
|
+
// Re-run this action: easiest to just continue out of the `if (existing)` block
|
|
1601
|
+
// by setting a flag. We do that by falling through — but since we're inside
|
|
1602
|
+
// `if (existing)`, we need to proceed. Easiest: re-invoke the inner start flow.
|
|
1603
|
+
// Actually: since we cleared state, the subsequent code paths will treat it
|
|
1604
|
+
// as a fresh session. But we're inside `if (existing)`. Jump to the fresh path
|
|
1605
|
+
// by executing a goto-equivalent via function call. Simplest: return here
|
|
1606
|
+
// after instructing the user to retype the command.
|
|
1607
|
+
console.log(chalk.white(' Please re-type: ') + chalk.bold.cyan(`exam ${code}`));
|
|
1608
|
+
console.log();
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1585
1611
|
// Migration path: pre-v2.19.55 sessions didn't persist the token on
|
|
1586
1612
|
// state. Those sessions can't use `lang` or submit via the new token
|
|
1587
1613
|
// path. If the user re-types the matching token, attach it so the
|
|
@@ -1624,6 +1650,9 @@ export function registerExamCommand(program) {
|
|
|
1624
1650
|
console.log();
|
|
1625
1651
|
console.log(chalk.yellow(' Note: you typed a different token. Each exam token is bound to'));
|
|
1626
1652
|
console.log(chalk.yellow(' one device + one session. You cannot switch tokens mid-exam.'));
|
|
1653
|
+
console.log();
|
|
1654
|
+
console.log(chalk.gray(' If you need to abandon this session (e.g. expired / wrong paper),'));
|
|
1655
|
+
console.log(chalk.gray(' run ') + chalk.bold.cyan('exam reset') + chalk.gray(' to wipe local state, then re-enter the new token.'));
|
|
1627
1656
|
}
|
|
1628
1657
|
console.log();
|
|
1629
1658
|
return;
|
|
@@ -1782,6 +1811,53 @@ export function registerExamCommand(program) {
|
|
|
1782
1811
|
}
|
|
1783
1812
|
});
|
|
1784
1813
|
// ─── exam demo ───
|
|
1814
|
+
exam
|
|
1815
|
+
.command('reset', { hidden: true })
|
|
1816
|
+
.description('Abandon current exam session and wipe local state (recovery only)')
|
|
1817
|
+
.action(async () => {
|
|
1818
|
+
logCommand('exam reset');
|
|
1819
|
+
const { getRealExamState, clearExamState: clearState } = await import('../lib/exam-state.js');
|
|
1820
|
+
const existing = getRealExamState();
|
|
1821
|
+
if (!existing) {
|
|
1822
|
+
console.log();
|
|
1823
|
+
console.log(chalk.gray(' No active exam session to reset.'));
|
|
1824
|
+
console.log();
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
const tok = existing.session.token;
|
|
1828
|
+
console.log();
|
|
1829
|
+
console.log(chalk.yellow(' ⚠ Reset will abandon your current exam session on this device.'));
|
|
1830
|
+
console.log(chalk.gray(' Exam: ') + chalk.white(existing.session.examName));
|
|
1831
|
+
if (tok)
|
|
1832
|
+
console.log(chalk.gray(' Token: ') + chalk.white(tok));
|
|
1833
|
+
console.log(chalk.gray(' The token itself is NOT revoked server-side. If your session is'));
|
|
1834
|
+
console.log(chalk.gray(' still active and not submitted, re-entering the same token resumes it.'));
|
|
1835
|
+
console.log();
|
|
1836
|
+
console.log(chalk.gray(' Type ') + chalk.bold.cyan('reset') + chalk.gray(' to confirm, or anything else to cancel:'));
|
|
1837
|
+
// Typed-word confirmation — matches existing exam submit confirm pattern
|
|
1838
|
+
const confirmWord = await new Promise((resolve) => {
|
|
1839
|
+
process.stdout.write(' > ');
|
|
1840
|
+
const onData = (chunk) => {
|
|
1841
|
+
const s = chunk.toString().trim();
|
|
1842
|
+
if (s.includes('\n') || s.includes('\r') || s.length > 0) {
|
|
1843
|
+
process.stdin.removeListener('data', onData);
|
|
1844
|
+
resolve(s.replace(/[\r\n]/g, ''));
|
|
1845
|
+
}
|
|
1846
|
+
};
|
|
1847
|
+
process.stdin.on('data', onData);
|
|
1848
|
+
process.stdin.resume();
|
|
1849
|
+
});
|
|
1850
|
+
if (confirmWord.toLowerCase() !== 'reset') {
|
|
1851
|
+
console.log(chalk.gray(' Cancelled.'));
|
|
1852
|
+
console.log();
|
|
1853
|
+
return;
|
|
1854
|
+
}
|
|
1855
|
+
clearState();
|
|
1856
|
+
console.log();
|
|
1857
|
+
console.log(chalk.green(' ✓ Exam state cleared.'));
|
|
1858
|
+
console.log(chalk.gray(' You can now enter a new token with ') + chalk.bold.cyan('exam <token>'));
|
|
1859
|
+
console.log();
|
|
1860
|
+
});
|
|
1785
1861
|
exam
|
|
1786
1862
|
.command('demo')
|
|
1787
1863
|
.description('Try a free practice exam (no account needed)')
|
package/dist/commands/lang.js
CHANGED
|
@@ -133,7 +133,12 @@ export function registerLangCommand(program) {
|
|
|
133
133
|
console.log(chalk.white(` Resume: exam q ${currentQ}`));
|
|
134
134
|
}
|
|
135
135
|
else {
|
|
136
|
-
|
|
136
|
+
// Most common cause here: the stored session's token was already
|
|
137
|
+
// submitted on the server (409) or the exam window has closed.
|
|
138
|
+
// Surface the recovery path so the user isn't locked out.
|
|
139
|
+
console.log(chalk.yellow(' Could not reload questions in new language.'));
|
|
140
|
+
console.log(chalk.gray(' The saved session may be expired or already submitted server-side.'));
|
|
141
|
+
console.log(chalk.gray(' To abandon it and start fresh: ') + chalk.bold.cyan('back') + chalk.gray(' → ') + chalk.bold.cyan('exam reset') + chalk.gray(' → ') + chalk.bold.cyan('exam <new-token>'));
|
|
137
142
|
}
|
|
138
143
|
}
|
|
139
144
|
catch {
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,6 @@ import { registerEnvCommand } from './commands/env.js';
|
|
|
15
15
|
import { registerAi4ctfCommand } from './commands/ai4ctf.js';
|
|
16
16
|
import { registerExamCommand } from './commands/exam.js';
|
|
17
17
|
import { registerCtf4aiDemoCommand } from './commands/ctf4ai-demo.js';
|
|
18
|
-
import { registerTutorialCommand } from './commands/tutorial.js';
|
|
19
18
|
import { registerThemeCommand } from './commands/theme.js';
|
|
20
19
|
import { getConfig, saveConfig } from './lib/config.js';
|
|
21
20
|
import { startRepl } from './repl.js';
|
|
@@ -149,7 +148,6 @@ registerEnvCommand(program);
|
|
|
149
148
|
registerAi4ctfCommand(program);
|
|
150
149
|
registerExamCommand(program);
|
|
151
150
|
registerCtf4aiDemoCommand(program);
|
|
152
|
-
registerTutorialCommand(program);
|
|
153
151
|
registerThemeCommand(program);
|
|
154
152
|
// Hidden command: switch AI model
|
|
155
153
|
program
|
package/dist/repl.js
CHANGED
|
@@ -162,8 +162,8 @@ function printSelectionMenu() {
|
|
|
162
162
|
console.log();
|
|
163
163
|
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
164
164
|
console.log(chalk.bold.cyan(' demo') + chalk.gray(` ${demoLine}`));
|
|
165
|
-
console.log(chalk.white(' lang
|
|
166
|
-
console.log(chalk.gray('
|
|
165
|
+
console.log(chalk.white(' lang') + chalk.gray(' List all supported languages'));
|
|
166
|
+
console.log(chalk.white(' lang es') + chalk.gray(' Switch language (e.g. lang es, lang zh, lang fr)'));
|
|
167
167
|
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
168
168
|
}
|
|
169
169
|
else if (!setupDone) {
|
|
@@ -175,7 +175,8 @@ function printSelectionMenu() {
|
|
|
175
175
|
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
176
176
|
console.log(chalk.white(' demo') + chalk.gray(` ${demoLine}`));
|
|
177
177
|
console.log(chalk.bold.yellow(' exam setup') + chalk.gray(' Install tools for national selection (~150MB)'));
|
|
178
|
-
console.log(chalk.white(' lang
|
|
178
|
+
console.log(chalk.white(' lang') + chalk.gray(' List all supported languages'));
|
|
179
|
+
console.log(chalk.white(' lang es') + chalk.gray(' Switch language (e.g. lang es, lang zh, lang fr)'));
|
|
179
180
|
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
180
181
|
}
|
|
181
182
|
else {
|
|
@@ -196,7 +197,8 @@ function printSelectionMenu() {
|
|
|
196
197
|
console.log(chalk.gray(' Other commands:'));
|
|
197
198
|
console.log(chalk.white(' demo') + chalk.gray(` ${demoLine}`));
|
|
198
199
|
console.log(chalk.white(' exam setup') + chalk.gray(' Re-verify tool environment'));
|
|
199
|
-
console.log(chalk.white(' lang
|
|
200
|
+
console.log(chalk.white(' lang') + chalk.gray(' List all supported languages'));
|
|
201
|
+
console.log(chalk.white(' lang es') + chalk.gray(' Switch language (e.g. lang es, lang zh, lang fr)'));
|
|
200
202
|
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
201
203
|
}
|
|
202
204
|
console.log(chalk.gray(' ') +
|
|
@@ -982,7 +984,7 @@ export async function startRepl(program, resumeMode) {
|
|
|
982
984
|
const KNOWN_CMDS = [
|
|
983
985
|
'ctf', 'hint', 'hint-b', 'hint-c', 'hint-budget', 'ref', 'shell',
|
|
984
986
|
'files', 'connect', 'note', 'log', 'lang', 'setup', 'env',
|
|
985
|
-
'ai4ctf', 'exam', 'ctf4ai', '
|
|
987
|
+
'ai4ctf', 'exam', 'ctf4ai', 'theme',
|
|
986
988
|
'clear', 'cls', 'quit', 'exit', 'back', 'menu', 'help',
|
|
987
989
|
'continue', 'activate', 'demo', 'challenges', 'status', 'scoreboard',
|
|
988
990
|
'join', 'logout',
|
package/package.json
CHANGED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `tutorial` command — a 30-second interactive walk-through for K-12 beginners
|
|
3
|
-
* who have never used a command-line tool before. Explains the 4 core
|
|
4
|
-
* mechanics of ICOA CLI: typing commands, answering questions, getting help,
|
|
5
|
-
* and exiting safely. No real exam state touched — pure narration + press-Enter.
|
|
6
|
-
*
|
|
7
|
-
* Triggered by: typing `tutorial` at the REPL prompt.
|
|
8
|
-
* Advertised in: printSelectionMenu tip footer and help listings.
|
|
9
|
-
*/
|
|
10
|
-
import { Command } from 'commander';
|
|
11
|
-
export declare function runTutorial(): Promise<void>;
|
|
12
|
-
export declare function registerTutorialCommand(program: Command): void;
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `tutorial` command — a 30-second interactive walk-through for K-12 beginners
|
|
3
|
-
* who have never used a command-line tool before. Explains the 4 core
|
|
4
|
-
* mechanics of ICOA CLI: typing commands, answering questions, getting help,
|
|
5
|
-
* and exiting safely. No real exam state touched — pure narration + press-Enter.
|
|
6
|
-
*
|
|
7
|
-
* Triggered by: typing `tutorial` at the REPL prompt.
|
|
8
|
-
* Advertised in: printSelectionMenu tip footer and help listings.
|
|
9
|
-
*/
|
|
10
|
-
import chalk from 'chalk';
|
|
11
|
-
function waitForEnter(promptText = ' Press Enter to continue... ') {
|
|
12
|
-
// Raw-stdin read (matches pattern used by exam start + demo confirm).
|
|
13
|
-
// A second readline on process.stdin fights the parent REPL, so we read
|
|
14
|
-
// raw bytes and resolve on \n or \r.
|
|
15
|
-
return new Promise((resolve) => {
|
|
16
|
-
process.stdout.write(chalk.bold.yellow(promptText));
|
|
17
|
-
const wasRaw = process.stdin.isTTY ? process.stdin.isRaw : false;
|
|
18
|
-
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
19
|
-
process.stdin.setRawMode(false);
|
|
20
|
-
}
|
|
21
|
-
const onData = (chunk) => {
|
|
22
|
-
const s = chunk.toString();
|
|
23
|
-
if (s.includes('\n') || s.includes('\r')) {
|
|
24
|
-
process.stdin.removeListener('data', onData);
|
|
25
|
-
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
26
|
-
process.stdin.setRawMode(wasRaw);
|
|
27
|
-
}
|
|
28
|
-
process.stdout.write('\n');
|
|
29
|
-
resolve();
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
process.stdin.on('data', onData);
|
|
33
|
-
process.stdin.resume();
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
function divider() {
|
|
37
|
-
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
38
|
-
}
|
|
39
|
-
function screenHeader(n, total, title) {
|
|
40
|
-
console.log();
|
|
41
|
-
console.log(chalk.cyan(` ─── Step ${n} / ${total} · ${title} ───`));
|
|
42
|
-
console.log();
|
|
43
|
-
}
|
|
44
|
-
export async function runTutorial() {
|
|
45
|
-
console.log();
|
|
46
|
-
console.log(chalk.bold.green(' 📚 ICOA CLI — 30-Second Tutorial'));
|
|
47
|
-
console.log(chalk.gray(' Never used a command-line before? You\'ll be fine. 4 steps total.'));
|
|
48
|
-
console.log();
|
|
49
|
-
await waitForEnter();
|
|
50
|
-
// ─── Step 1 ───────────────────────────────────────────────────────
|
|
51
|
-
screenHeader(1, 4, 'Typing commands');
|
|
52
|
-
console.log(chalk.white(' The CLI waits for you to ') + chalk.bold('type a command and press Enter') + chalk.white('.'));
|
|
53
|
-
console.log();
|
|
54
|
-
console.log(chalk.gray(' For example, when you see the prompt:'));
|
|
55
|
-
console.log();
|
|
56
|
-
console.log(' ' + chalk.cyan('icoa> '));
|
|
57
|
-
console.log();
|
|
58
|
-
console.log(chalk.gray(' You type a word like ') + chalk.cyan('help') + chalk.gray(' or ') + chalk.cyan('demo') + chalk.gray(' and press Enter.'));
|
|
59
|
-
console.log(chalk.gray(' If you type something wrong, nothing bad happens — try again.'));
|
|
60
|
-
console.log();
|
|
61
|
-
await waitForEnter();
|
|
62
|
-
// ─── Step 2 ───────────────────────────────────────────────────────
|
|
63
|
-
screenHeader(2, 4, 'Answering questions');
|
|
64
|
-
console.log(chalk.white(' Multiple-choice questions show 4 options (A / B / C / D).'));
|
|
65
|
-
console.log(chalk.gray(' Example:'));
|
|
66
|
-
console.log();
|
|
67
|
-
divider();
|
|
68
|
-
console.log(chalk.white(' Q1. Which command shows the current directory?'));
|
|
69
|
-
console.log(chalk.gray(' A) cd B) pwd C) ls D) dir'));
|
|
70
|
-
divider();
|
|
71
|
-
console.log();
|
|
72
|
-
console.log(chalk.gray(' To answer, type: ') + chalk.cyan('exam answer 1 B') + chalk.gray(' (question number + letter)'));
|
|
73
|
-
console.log(chalk.gray(' Or the shortcut: ') + chalk.cyan('B') + chalk.gray(' (letter alone on the current question)'));
|
|
74
|
-
console.log();
|
|
75
|
-
console.log(chalk.gray(' After you answer, the CLI auto-saves — you won\'t lose work if you exit.'));
|
|
76
|
-
console.log();
|
|
77
|
-
await waitForEnter();
|
|
78
|
-
// ─── Step 3 ───────────────────────────────────────────────────────
|
|
79
|
-
screenHeader(3, 4, 'Getting help when stuck');
|
|
80
|
-
console.log(chalk.white(' Stuck? Several ways to get help, from gentlest to most revealing:'));
|
|
81
|
-
console.log();
|
|
82
|
-
console.log(' ' + chalk.cyan('help') + chalk.gray(' — list all available commands'));
|
|
83
|
-
console.log(' ' + chalk.cyan('help ') + chalk.gray('(or ') + chalk.cyan('?') + chalk.gray(') — same, shorter to type'));
|
|
84
|
-
console.log(' ' + chalk.cyan('ref grep') + chalk.gray(' — quick reference for a specific tool'));
|
|
85
|
-
console.log();
|
|
86
|
-
console.log(chalk.white(' Inside a question:'));
|
|
87
|
-
console.log(' ' + chalk.cyan('help') + chalk.gray(' — eliminate one wrong multiple-choice option'));
|
|
88
|
-
console.log(' ' + chalk.cyan('hint a') + chalk.gray(' — general direction (practical questions only)'));
|
|
89
|
-
console.log(' ' + chalk.cyan('hint b') + chalk.gray(' — specific technique'));
|
|
90
|
-
console.log(' ' + chalk.cyan('hint c') + chalk.gray(' — near-solution (masked, 50% revealed)'));
|
|
91
|
-
console.log();
|
|
92
|
-
console.log(chalk.gray(' The exam has a budget for each — using one doesn\'t end the question.'));
|
|
93
|
-
console.log();
|
|
94
|
-
await waitForEnter();
|
|
95
|
-
// ─── Step 4 ───────────────────────────────────────────────────────
|
|
96
|
-
screenHeader(4, 4, 'Exiting & pausing safely');
|
|
97
|
-
console.log(chalk.white(' Three escape hatches, each with a different meaning:'));
|
|
98
|
-
console.log();
|
|
99
|
-
console.log(' ' + chalk.cyan('back') + chalk.gray(' or ') + chalk.cyan('menu') + chalk.gray(' — return to the main menu; exam stays saved, timer keeps running'));
|
|
100
|
-
console.log(' ' + chalk.cyan('exit') + chalk.gray(' — same as back (from any prompt)'));
|
|
101
|
-
console.log(' ' + chalk.cyan('quit') + chalk.gray(' — close the CLI entirely; next time use ') + chalk.cyan('icoa --resume'));
|
|
102
|
-
console.log(' ' + chalk.cyan('Ctrl+C') + chalk.gray(' — pause + show where you are (no data loss)'));
|
|
103
|
-
console.log();
|
|
104
|
-
console.log(chalk.gray(' Closing your terminal window is also safe — your answers are on disk.'));
|
|
105
|
-
console.log();
|
|
106
|
-
await waitForEnter();
|
|
107
|
-
// ─── Final ────────────────────────────────────────────────────────
|
|
108
|
-
console.log();
|
|
109
|
-
console.log(chalk.bold.green(' ✨ That\'s it. You\'re ready.'));
|
|
110
|
-
console.log();
|
|
111
|
-
console.log(chalk.white(' Try next:'));
|
|
112
|
-
console.log(' ' + chalk.cyan('demo') + chalk.gray(' — free practice (10 questions, no timer)'));
|
|
113
|
-
console.log(' ' + chalk.cyan('exam <token>') + chalk.gray(' — real exam (when you have an organizer-issued token)'));
|
|
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'));
|
|
118
|
-
console.log();
|
|
119
|
-
}
|
|
120
|
-
export function registerTutorialCommand(program) {
|
|
121
|
-
program
|
|
122
|
-
.command('tutorial')
|
|
123
|
-
.description('30-second walk-through for first-time CLI users')
|
|
124
|
-
.action(async () => {
|
|
125
|
-
await runTutorial();
|
|
126
|
-
});
|
|
127
|
-
}
|