icoa-cli 1.6.0 → 1.6.1

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 CHANGED
@@ -36,7 +36,7 @@ ${LINE}
36
36
  ${chalk.white('Sydney, Australia')} ${chalk.gray('Jun 27 - Jul 2, 2026')}
37
37
  ${chalk.cyan.underline('https://icoa2026.au')}
38
38
 
39
- ${chalk.gray('CLI-Native Competition Terminal v1.6.0')}
39
+ ${chalk.gray('CLI-Native Competition Terminal v1.6.1')}
40
40
 
41
41
  ${LINE}
42
42
  `;
@@ -1,3 +1,5 @@
1
+ export declare function isFirstRunOrUpgrade(currentVersion: string): boolean;
2
+ export declare function markVersionSeen(version: string): void;
1
3
  export declare function validateToken(token: string): boolean;
2
4
  export declare function isActivated(): boolean;
3
5
  export type ActivateResult = 'ok' | 'invalid' | 'already_bound';
@@ -107,6 +107,14 @@ const TOKEN_HASHES = new Set([
107
107
  "cde02309ad295648d86fa4acefdf7224809abb2ab9b025f14ae41e021512c5ed",
108
108
  "39835a1337c2afd9a9690cb9946752899a110df312d9d3aa68e10f85fad49dea",
109
109
  ]);
110
+ // Check if first run or version upgrade
111
+ export function isFirstRunOrUpgrade(currentVersion) {
112
+ const config = getConfig();
113
+ return config.lastVersion !== currentVersion;
114
+ }
115
+ export function markVersionSeen(version) {
116
+ saveConfig({ lastVersion: version });
117
+ }
110
118
  // Free commands that don't require token
111
119
  const FREE_COMMANDS = new Set(['ref', 'help', 'exit', 'quit', 'q', 'clear', 'cls', 'activate']);
112
120
  function hashToken(token) {
package/dist/repl.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  import { Command } from 'commander';
2
- export declare function startRepl(program: Command, resumeMode: boolean): void;
2
+ export declare function startRepl(program: Command, resumeMode: boolean): Promise<void>;
package/dist/repl.js CHANGED
@@ -2,15 +2,59 @@ import { createInterface } from 'node:readline';
2
2
  import { spawn } from 'node:child_process';
3
3
  import chalk from 'chalk';
4
4
  import { isConnected, getConfig } from './lib/config.js';
5
- import { isActivated, activateToken, isFreeCommand, isDeviceMatch, recordExit, recordResume } from './lib/access.js';
5
+ import { isActivated, activateToken, isFreeCommand, isDeviceMatch, recordExit, recordResume, isFirstRunOrUpgrade, markVersionSeen } from './lib/access.js';
6
6
  import { resetTerminalTheme } from './lib/theme.js';
7
7
  import { ensureSandbox, runInSandbox, isDockerAvailable } from './lib/sandbox.js';
8
8
  const INTERCEPT = '__REPL_NO_EXIT__';
9
- export function startRepl(program, resumeMode) {
9
+ const VERSION = '1.6.1';
10
+ export async function startRepl(program, resumeMode) {
10
11
  const config = getConfig();
11
12
  const connected = isConnected();
12
13
  const realExit = process.exit.bind(process);
13
14
  const activated = isActivated();
15
+ // First run or upgrade: prompt to install env
16
+ if (isFirstRunOrUpgrade(VERSION)) {
17
+ markVersionSeen(VERSION);
18
+ console.log(chalk.gray(' Checking competition environment...'));
19
+ // Quick check: count missing Python libs
20
+ const { execSync } = await import('node:child_process');
21
+ const checks = [
22
+ { name: 'pwntools', cmd: 'python3 -c "import pwn"' },
23
+ { name: 'z3-solver', cmd: 'python3 -c "import z3"' },
24
+ { name: 'numpy', cmd: 'python3 -c "import numpy"' },
25
+ { name: 'requests', cmd: 'python3 -c "import requests"' },
26
+ ];
27
+ let missing = 0;
28
+ for (const c of checks) {
29
+ try {
30
+ execSync(c.cmd, { stdio: 'ignore' });
31
+ }
32
+ catch {
33
+ missing++;
34
+ }
35
+ }
36
+ if (missing > 0) {
37
+ console.log(chalk.yellow(` ${missing} core libraries missing.`));
38
+ try {
39
+ const { confirm } = await import('@inquirer/prompts');
40
+ const yes = await confirm({ message: ' Install competition Python libraries now?', default: true });
41
+ if (yes) {
42
+ console.log();
43
+ // Trigger env setup
44
+ const { execSync: ex } = await import('node:child_process');
45
+ ex('node ' + new URL('../index.js', import.meta.url).pathname + ' env setup', { stdio: 'inherit' });
46
+ }
47
+ }
48
+ catch {
49
+ console.log(chalk.gray(' Run ') + chalk.white('env setup') + chalk.gray(' later to install.'));
50
+ }
51
+ console.log();
52
+ }
53
+ else {
54
+ console.log(chalk.green(' All core libraries ready.'));
55
+ console.log();
56
+ }
57
+ }
14
58
  // Handle resume
15
59
  if (resumeMode) {
16
60
  const info = recordResume();
@@ -100,6 +100,7 @@ export interface IcoaConfig {
100
100
  geminiModel: string;
101
101
  accessToken: string;
102
102
  deviceFingerprint: string;
103
+ lastVersion: string;
103
104
  }
104
105
  export type CompetitionState = 'pre_competition' | 'demo' | 'live' | 'finished' | 'unknown';
105
106
  export type HintLevel = 'A' | 'B' | 'C';
@@ -28,5 +28,6 @@ export const DEFAULT_CONFIG = {
28
28
  geminiModel: 'gemini-2.5-flash',
29
29
  accessToken: '',
30
30
  deviceFingerprint: '',
31
+ lastVersion: '',
31
32
  };
32
33
  export const SUPPORTED_LANGUAGES = ['en', 'zh', 'ja', 'ko', 'es'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "ICOA CLI — The world's first CLI-native CTF competition terminal",
5
5
  "type": "module",
6
6
  "bin": {