icoa-cli 2.19.54 → 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.
@@ -1453,12 +1453,30 @@ 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
+ 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
+ }
1459
1478
  const answered = Object.keys(existing.answers).length;
1460
1479
  const total = existing.session.questionCount;
1461
- const existingToken = existing.session.token;
1462
1480
  const deadline = getExamDeadline();
1463
1481
  const remainingMin = deadline ? Math.max(0, Math.round((deadline.getTime() - Date.now()) / 60000)) : null;
1464
1482
  console.log();
@@ -1571,6 +1589,11 @@ export function registerExamCommand(program) {
1571
1589
  // ── Timer starts NOW ──
1572
1590
  const confirmedAt = new Date().toISOString();
1573
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();
1574
1597
  saveExamState({ session, questions, answers: {}, interactions: [], aiUsage: { ai4ctf: 0, ctf4ai: 0 } });
1575
1598
  console.log();
1576
1599
  printSuccess('Exam started! Timer is running.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "2.19.54",
3
+ "version": "2.19.55",
4
4
  "description": "ICOA CLI — The world's first CLI-native CTF competition terminal",
5
5
  "type": "module",
6
6
  "bin": {