icoa-cli 2.19.53 → 2.19.55
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 +54 -3
- package/package.json +1 -1
package/dist/commands/exam.js
CHANGED
|
@@ -1453,11 +1453,54 @@ export function registerExamCommand(program) {
|
|
|
1453
1453
|
.description('Enter exam with access token (no login needed)')
|
|
1454
1454
|
.action(async (code) => {
|
|
1455
1455
|
logCommand(`exam token ${code}`);
|
|
1456
|
-
const { getRealExamState } = await import('../lib/exam-state.js');
|
|
1456
|
+
const { getRealExamState, saveExamState: saveExamStateFn } = await import('../lib/exam-state.js');
|
|
1457
1457
|
const existing = getRealExamState();
|
|
1458
1458
|
if (existing) {
|
|
1459
|
-
|
|
1460
|
-
|
|
1459
|
+
const existingToken = existing.session.token;
|
|
1460
|
+
// Migration path: pre-v2.19.55 sessions didn't persist the token on
|
|
1461
|
+
// state. Those sessions can't use `lang` or submit via the new token
|
|
1462
|
+
// path. If the user re-types the matching token, attach it so the
|
|
1463
|
+
// session can reach the server again. Device binding on the server
|
|
1464
|
+
// means only the original token holder can do this.
|
|
1465
|
+
if (!existingToken && code) {
|
|
1466
|
+
existing.session.token = code.trim();
|
|
1467
|
+
saveExamStateFn(existing);
|
|
1468
|
+
console.log();
|
|
1469
|
+
console.log(chalk.green(' ✓ Token re-attached to your in-progress exam.'));
|
|
1470
|
+
console.log(chalk.gray(' You can now use: ') + chalk.white('lang <code>') + chalk.gray(' · ') + chalk.white('exam submit'));
|
|
1471
|
+
console.log();
|
|
1472
|
+
console.log(chalk.bold.white(' Continue this exam:'));
|
|
1473
|
+
console.log(chalk.gray(' → ') + chalk.bold.cyan('exam q 1') + chalk.gray(' resume at any question'));
|
|
1474
|
+
console.log(chalk.gray(' → ') + chalk.bold.cyan('exam review') + chalk.gray(' see progress + flagged items'));
|
|
1475
|
+
console.log();
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
const answered = Object.keys(existing.answers).length;
|
|
1479
|
+
const total = existing.session.questionCount;
|
|
1480
|
+
const deadline = getExamDeadline();
|
|
1481
|
+
const remainingMin = deadline ? Math.max(0, Math.round((deadline.getTime() - Date.now()) / 60000)) : null;
|
|
1482
|
+
console.log();
|
|
1483
|
+
console.log(chalk.yellow(` ⚠ An exam is already in progress on this device.`));
|
|
1484
|
+
console.log();
|
|
1485
|
+
console.log(chalk.gray(' Exam: ') + chalk.white(existing.session.examName));
|
|
1486
|
+
if (existingToken) {
|
|
1487
|
+
console.log(chalk.gray(' Token: ') + chalk.white(existingToken) + (existingToken === code ? chalk.green(' (same as you just typed)') : chalk.yellow(' (different from the one you typed)')));
|
|
1488
|
+
}
|
|
1489
|
+
console.log(chalk.gray(' Answers: ') + chalk.white(`${answered}/${total}`));
|
|
1490
|
+
if (remainingMin !== null) {
|
|
1491
|
+
console.log(chalk.gray(' Time: ') + chalk.white(`${remainingMin} min remaining`));
|
|
1492
|
+
}
|
|
1493
|
+
console.log();
|
|
1494
|
+
console.log(chalk.bold.white(' To continue this exam:'));
|
|
1495
|
+
console.log(chalk.gray(' → ') + chalk.bold.cyan('exam q 1') + chalk.gray(' resume at any question'));
|
|
1496
|
+
console.log(chalk.gray(' → ') + chalk.bold.cyan('exam review') + chalk.gray(' see progress + flagged items'));
|
|
1497
|
+
console.log(chalk.gray(' → ') + chalk.bold.cyan('exam submit') + chalk.gray(' finish and submit'));
|
|
1498
|
+
if (existingToken && existingToken !== code) {
|
|
1499
|
+
console.log();
|
|
1500
|
+
console.log(chalk.yellow(' Note: you typed a different token. Each exam token is bound to'));
|
|
1501
|
+
console.log(chalk.yellow(' one device + one session. You cannot switch tokens mid-exam.'));
|
|
1502
|
+
}
|
|
1503
|
+
console.log();
|
|
1461
1504
|
return;
|
|
1462
1505
|
}
|
|
1463
1506
|
// Gate: require exam setup
|
|
@@ -1465,6 +1508,7 @@ export function registerExamCommand(program) {
|
|
|
1465
1508
|
console.log();
|
|
1466
1509
|
printWarning('Pre-exam setup required before entering a token.');
|
|
1467
1510
|
console.log(chalk.gray(' → ') + chalk.bold.cyan('exam setup'));
|
|
1511
|
+
console.log(chalk.gray(' Or type ') + chalk.bold.cyan('back') + chalk.gray(' to return to the main menu.'));
|
|
1468
1512
|
console.log();
|
|
1469
1513
|
return;
|
|
1470
1514
|
}
|
|
@@ -1545,6 +1589,11 @@ export function registerExamCommand(program) {
|
|
|
1545
1589
|
// ── Timer starts NOW ──
|
|
1546
1590
|
const confirmedAt = new Date().toISOString();
|
|
1547
1591
|
session.confirmedAt = confirmedAt;
|
|
1592
|
+
// Store the token on session so lang switching, exam submit, and
|
|
1593
|
+
// resume can reach the server without re-prompting. Device binding
|
|
1594
|
+
// means the token alone is useless on another machine, so keeping
|
|
1595
|
+
// it in exam-state.json is safe.
|
|
1596
|
+
session.token = code.trim();
|
|
1548
1597
|
saveExamState({ session, questions, answers: {}, interactions: [], aiUsage: { ai4ctf: 0, ctf4ai: 0 } });
|
|
1549
1598
|
console.log();
|
|
1550
1599
|
printSuccess('Exam started! Timer is running.');
|
|
@@ -1721,6 +1770,7 @@ export function registerExamCommand(program) {
|
|
|
1721
1770
|
}
|
|
1722
1771
|
console.log();
|
|
1723
1772
|
console.log(chalk.white(' Next step: ') + chalk.bold.cyan('exam <token>'));
|
|
1773
|
+
console.log(chalk.gray(' Or type ') + chalk.bold.cyan('back') + chalk.gray(' to return to the main menu.'));
|
|
1724
1774
|
console.log();
|
|
1725
1775
|
return;
|
|
1726
1776
|
}
|
|
@@ -1857,6 +1907,7 @@ export function registerExamCommand(program) {
|
|
|
1857
1907
|
console.log(chalk.cyan(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
1858
1908
|
console.log();
|
|
1859
1909
|
console.log(chalk.white(' Next step: ') + chalk.bold.cyan('exam <token>'));
|
|
1910
|
+
console.log(chalk.gray(' Or type ') + chalk.bold.cyan('back') + chalk.gray(' to return to the main menu.'));
|
|
1860
1911
|
console.log();
|
|
1861
1912
|
});
|
|
1862
1913
|
// Default action: progressive onboarding dashboard
|