icoa-cli 2.19.349 → 2.19.351

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.
Files changed (47) hide show
  1. package/dist/commands/ai4ctf.js +1 -1
  2. package/dist/commands/connect.js +1 -1
  3. package/dist/commands/ctf.js +1 -1
  4. package/dist/commands/ctf4ai-demo.js +1 -1
  5. package/dist/commands/ctf4vla.js +1 -1
  6. package/dist/commands/exam.js +1 -1
  7. package/dist/index.js +1 -355
  8. package/dist/lib/access.js +1 -184
  9. package/dist/lib/arena-submit.js +1 -21
  10. package/dist/lib/budget.js +1 -6
  11. package/dist/lib/challenge-dir.js +1 -16
  12. package/dist/lib/comms.js +1 -212
  13. package/dist/lib/config.js +1 -93
  14. package/dist/lib/country-lang.js +1 -39
  15. package/dist/lib/demo-exam.js +1 -478
  16. package/dist/lib/demo-flags.js +1 -27
  17. package/dist/lib/demo-stats.js +1 -62
  18. package/dist/lib/demo2-progress.js +1 -102
  19. package/dist/lib/exam-client.js +1 -54
  20. package/dist/lib/exam-state.js +1 -273
  21. package/dist/lib/gemini.js +1 -247
  22. package/dist/lib/integrity-snapshot.js +1 -88
  23. package/dist/lib/interactive-spawn.js +1 -55
  24. package/dist/lib/ipynb-input.js +1 -65
  25. package/dist/lib/kernel-protocol.js +1 -88
  26. package/dist/lib/kernel.js +2 -146
  27. package/dist/lib/learn-curricula.js +1 -309
  28. package/dist/lib/learn-i18n.js +1 -184
  29. package/dist/lib/log-sync.js +1 -155
  30. package/dist/lib/logger.js +1 -49
  31. package/dist/lib/open-file.js +1 -55
  32. package/dist/lib/paper-upgrade.js +1 -119
  33. package/dist/lib/render-card.js +1 -112
  34. package/dist/lib/repl-asker.js +1 -67
  35. package/dist/lib/sample-runner.js +1 -227
  36. package/dist/lib/shell-split.js +1 -69
  37. package/dist/lib/sim-cooldown.js +1 -75
  38. package/dist/lib/theme.js +1 -119
  39. package/dist/lib/token-format.js +1 -74
  40. package/dist/lib/toolset-hash.js +1 -48
  41. package/dist/lib/translations-fetcher.js +1 -95
  42. package/dist/lib/ui.js +1 -99
  43. package/dist/lib/version.js +1 -24
  44. package/dist/postinstall.js +1 -48
  45. package/dist/repl.js +1 -2251
  46. package/dist/types/index.js +1 -63
  47. package/package.json +1 -1
@@ -1,95 +1 @@
1
- /**
2
- * Lazy translation-pack fetcher.
3
- *
4
- * v2.19.197 moved per-language translation files (translations/<lang>/*.json)
5
- * out of the npm package, saving ~280 KB. They live on icoa2026.au and get
6
- * fetched + extracted under ~/.icoa/translations/<lang>/ on first lang switch.
7
- *
8
- * - English is bundled in source (no fetch needed)
9
- * - All other supported langs ship as <lang>.tar.gz on icoa2026.au
10
- * - Per-lang tarballs are 4–17 KB compressed; single HTTP round-trip
11
- * - A `.cached` sentinel file marks completion (idempotent re-runs)
12
- * - Extraction shells out to `tar -xzf` — present on macOS, Linux, WSL2
13
- */
14
- import chalk from 'chalk';
15
- import { spawn } from 'node:child_process';
16
- import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
17
- import { join } from 'node:path';
18
- import { getIcoaDir } from './config.js';
19
- // Multi-source fallback: try each mirror in order before giving up to the
20
- // on-the-fly translator. Two boxes on different IPs = real redundancy, so a
21
- // single host being down (or its asset path not yet provisioned) no longer
22
- // drops non-EN users to live Gemini. Same single git source builds both packs
23
- // (scripts/build-translation-packs.sh --push sha256-gates them identical), so
24
- // whichever mirror answers first serves byte-identical content.
25
- //
26
- // Hosts are the two boxes we hold SSH creds for → fully automatable publish
27
- // (the retired icoa2026.au website had no SSH access, only a deploy webhook):
28
- // primary = practice (public, always-on, NOT frozen, where learn already points)
29
- // backup = mel/au (competition box). Safe as a backup because (1) the client
30
- // only reaches it if practice is down, and (2) packs cache once (.cached) at
31
- // language-switch time — BEFORE the exam — so exam-time fetch traffic ≈ 0,
32
- // neutralising the single-venue-IP fail2ban risk.
33
- const TRANSLATIONS_BASE_URLS = [
34
- 'https://practice.icoa2026.au/assets/translations',
35
- 'https://au.icoa2026.au/assets/translations',
36
- ];
37
- export function getLangCacheDir(lang) {
38
- return join(getIcoaDir(), 'translations', lang);
39
- }
40
- export function isLangCached(lang) {
41
- if (lang === 'en')
42
- return true;
43
- const dir = getLangCacheDir(lang);
44
- // RULE: the menu / UI chrome stays English-only — non-EN `ui.json` packs are
45
- // intentionally NOT served from the asset host, so t() falls back to EN per key
46
- // for chrome. Lazy-load (the `lang` command) only translates question + demo
47
- // content. So the cache is valid on the `.cached` sentinel alone; do NOT also
48
- // require ui.json here — it will never be present, which would force a full
49
- // pack re-download on every boot for non-EN users.
50
- return existsSync(join(dir, '.cached'));
51
- }
52
- export async function ensureLangCache(lang, opts = {}) {
53
- if (lang === 'en')
54
- return true;
55
- if (isLangCached(lang))
56
- return true;
57
- const cacheDir = getLangCacheDir(lang);
58
- mkdirSync(cacheDir, { recursive: true });
59
- if (!opts.silent) {
60
- console.log(chalk.gray(` Fetching ${lang} translation pack ...`));
61
- }
62
- // Try each mirror in order; the first that returns the tarball wins.
63
- let lastErr = null;
64
- for (const base of TRANSLATIONS_BASE_URLS) {
65
- try {
66
- const url = `${base}/${lang}.tar.gz`;
67
- const res = await fetch(url);
68
- if (!res.ok) {
69
- lastErr = `HTTP ${res.status}`;
70
- continue; // try next mirror
71
- }
72
- const tgzPath = join(cacheDir, `${lang}.tar.gz`);
73
- writeFileSync(tgzPath, Buffer.from(await res.arrayBuffer()));
74
- await new Promise((resolve, reject) => {
75
- const proc = spawn('tar', ['-xzf', tgzPath, '-C', cacheDir], { stdio: 'ignore' });
76
- proc.on('close', (code) => (code === 0 ? resolve() : reject(new Error(`tar exit ${code}`))));
77
- proc.on('error', reject);
78
- });
79
- writeFileSync(join(cacheDir, '.cached'), new Date().toISOString());
80
- if (!opts.silent) {
81
- console.log(chalk.green(` ✓ ${lang} translations cached at ~/.icoa/translations/${lang}/`));
82
- }
83
- return true;
84
- }
85
- catch (e) {
86
- lastErr = e instanceof Error ? e.message : String(e);
87
- // try next mirror
88
- }
89
- }
90
- // All mirrors exhausted → fall back to the on-the-fly translator.
91
- if (!opts.silent) {
92
- console.log(chalk.yellow(` Translation pack not available (${lastErr ?? 'all mirrors unreachable'}). The interface stays in English; per-question translations fall back to the on-the-fly translator.`));
93
- }
94
- return false;
95
- }
1
+ import chalk from"chalk";import{spawn as t}from"node:child_process";import{existsSync as n,mkdirSync as r,writeFileSync as a}from"node:fs";import{join as e}from"node:path";import{getIcoaDir as o}from"./config.js";const s=["https://practice.icoa2026.au/assets/translations","https://au.icoa2026.au/assets/translations"];export function getLangCacheDir(t){return e(o(),"translations",t)}export function isLangCached(t){if("en"===t)return!0;const r=getLangCacheDir(t);return n(e(r,".cached"))}export async function ensureLangCache(n,o={}){if("en"===n)return!0;if(isLangCached(n))return!0;const i=getLangCacheDir(n);r(i,{recursive:!0}),o.silent||console.log(chalk.gray(` Fetching ${n} translation pack ...`));let c=null;for(const r of s)try{const s=`${r}/${n}.tar.gz`,l=await fetch(s);if(!l.ok){c=`HTTP ${l.status}`;continue}const f=e(i,`${n}.tar.gz`);return a(f,Buffer.from(await l.arrayBuffer())),await new Promise((n,r)=>{const a=t("tar",["-xzf",f,"-C",i],{stdio:"ignore"});a.on("close",t=>0===t?n():r(new Error(`tar exit ${t}`))),a.on("error",r)}),a(e(i,".cached"),(new Date).toISOString()),o.silent||console.log(chalk.green(` ✓ ${n} translations cached at ~/.icoa/translations/${n}/`)),!0}catch(t){c=t instanceof Error?t.message:String(t)}return o.silent||console.log(chalk.yellow(` Translation pack not available (${c??"all mirrors unreachable"}). The interface stays in English; per-question translations fall back to the on-the-fly translator.`)),!1}
package/dist/lib/ui.js CHANGED
@@ -1,99 +1 @@
1
- import chalk from 'chalk';
2
- import Table from 'cli-table3';
3
- import ora from 'ora';
4
- import { Marked } from 'marked';
5
- import { markedTerminal } from 'marked-terminal';
6
- import { c } from './colors.js';
7
- const marked = new Marked(markedTerminal());
8
- // Wrap the message body in explicit Darcula fg (#A9B7C6). Without this,
9
- // chalk.green('✓ ') emits \x1b[39m at the end and the unstyled msg falls
10
- // back to the terminal's profile fg — which is black on macOS Terminal.app
11
- // default, invisible on our forced #2B2B2B background.
12
- export function printSuccess(msg) {
13
- console.log(chalk.green('✓ ') + c.fg(msg));
14
- }
15
- export function printError(msg) {
16
- console.log(chalk.red('✗ ') + c.fg(msg));
17
- }
18
- export function printWarning(msg) {
19
- console.log(chalk.yellow('⚠ ') + c.fg(msg));
20
- }
21
- export function printInfo(msg) {
22
- console.log(chalk.blue('ℹ ') + c.fg(msg));
23
- }
24
- export function printTable(headers, rows) {
25
- const table = new Table({
26
- head: headers.map((h) => chalk.cyan.bold(h)),
27
- style: { head: [], border: [] },
28
- });
29
- for (const row of rows) {
30
- table.push(row);
31
- }
32
- console.log(table.toString());
33
- }
34
- export function printMarkdown(text) {
35
- const rendered = marked.parse(text);
36
- if (typeof rendered === 'string') {
37
- console.log(rendered);
38
- }
39
- }
40
- // In REPL mode, spinners conflict with readline — use simple log instead
41
- let replMode = false;
42
- export function setReplMode(enabled) {
43
- replMode = enabled;
44
- }
45
- export function createSpinner(text) {
46
- if (replMode) {
47
- // Fake spinner that just logs — no terminal cursor manipulation
48
- const fake = {
49
- text,
50
- start() {
51
- console.log(chalk.cyan(` ${text}`));
52
- return fake;
53
- },
54
- stop() {
55
- return fake;
56
- },
57
- succeed(msg) {
58
- console.log(chalk.green(` ✓ ${msg}`));
59
- return fake;
60
- },
61
- fail(msg) {
62
- console.log(chalk.red(` ✗ ${msg}`));
63
- return fake;
64
- },
65
- info(msg) {
66
- console.log(chalk.blue(` ℹ ${msg}`));
67
- return fake;
68
- },
69
- warn(msg) {
70
- console.log(chalk.yellow(` ⚠ ${msg}`));
71
- return fake;
72
- },
73
- };
74
- return fake;
75
- }
76
- return ora({ text, color: 'cyan' });
77
- }
78
- export function formatCountdown(targetDate) {
79
- const now = new Date();
80
- const diff = targetDate.getTime() - now.getTime();
81
- if (diff <= 0)
82
- return '00:00:00';
83
- const hours = Math.floor(diff / (1000 * 60 * 60));
84
- const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
85
- const seconds = Math.floor((diff % (1000 * 60)) / 1000);
86
- return [
87
- hours.toString().padStart(2, '0'),
88
- minutes.toString().padStart(2, '0'),
89
- seconds.toString().padStart(2, '0'),
90
- ].join(':');
91
- }
92
- export function printHeader(title) {
93
- console.log();
94
- console.log(chalk.cyan.bold(` ${title}`));
95
- console.log(chalk.cyan(` ${'─'.repeat(title.length + 4)}`));
96
- }
97
- export function printKeyValue(key, value) {
98
- console.log(` ${chalk.gray(`${key}:`)} ${c.fg(value)}`);
99
- }
1
+ import chalk from"chalk";import o from"cli-table3";import ora from"ora";import{Marked as t}from"marked";import{markedTerminal as n}from"marked-terminal";import{c as e}from"./colors.js";const r=new t(n());export function printSuccess(o){console.log(chalk.green("✓ ")+e.fg(o))}export function printError(o){console.log(chalk.red("✗ ")+e.fg(o))}export function printWarning(o){console.log(chalk.yellow("⚠ ")+e.fg(o))}export function printInfo(o){console.log(chalk.blue("ℹ ")+e.fg(o))}export function printTable(t,n){const e=new o({head:t.map(o=>chalk.cyan.bold(o)),style:{head:[],border:[]}});for(const o of n)e.push(o);console.log(e.toString())}export function printMarkdown(o){const t=r.parse(o);"string"==typeof t&&console.log(t)}let l=!1;export function setReplMode(o){l=o}export function createSpinner(o){if(l){const t={text:o,start:()=>(console.log(chalk.cyan(` ${o}`)),t),stop:()=>t,succeed:o=>(console.log(chalk.green(` ✓ ${o}`)),t),fail:o=>(console.log(chalk.red(` ✗ ${o}`)),t),info:o=>(console.log(chalk.blue(` ℹ ${o}`)),t),warn:o=>(console.log(chalk.yellow(` ⚠ ${o}`)),t)};return t}return ora({text:o,color:"cyan"})}export function formatCountdown(o){const t=new Date,n=o.getTime()-t.getTime();if(n<=0)return"00:00:00";const e=Math.floor(n/36e5),r=Math.floor(n%36e5/6e4),l=Math.floor(n%6e4/1e3);return[e.toString().padStart(2,"0"),r.toString().padStart(2,"0"),l.toString().padStart(2,"0")].join(":")}export function printHeader(o){console.log(),console.log(chalk.cyan.bold(` ${o}`)),console.log(chalk.cyan(` ${"─".repeat(o.length+4)}`))}export function printKeyValue(o,t){console.log(` ${chalk.gray(`${o}:`)} ${e.fg(t)}`)}
@@ -1,24 +1 @@
1
- import { readFileSync } from 'node:fs';
2
- import { dirname, join } from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
- const __dirname_version = dirname(fileURLToPath(import.meta.url));
5
- let _cached = null;
6
- /**
7
- * Returns the running CLI's package.json version, cached after first call.
8
- * Used by interaction events + log-sync payload so future forensic audits
9
- * can pinpoint which CLI version produced any given event. Added in
10
- * v2.19.185 in response to the Paper E dispatcher bug, where we couldn't
11
- * tell from the data which contestants ran pre-fix vs post-fix code.
12
- */
13
- export function getCliVersion() {
14
- if (_cached)
15
- return _cached;
16
- try {
17
- const pkg = JSON.parse(readFileSync(join(__dirname_version, '..', '..', 'package.json'), 'utf-8'));
18
- _cached = pkg.version || 'unknown';
19
- }
20
- catch {
21
- _cached = 'unknown';
22
- }
23
- return _cached;
24
- }
1
+ import{readFileSync as n}from"node:fs";import{dirname as o,join as r}from"node:path";import{fileURLToPath as t}from"node:url";const e=o(t(import.meta.url));let i=null;export function getCliVersion(){if(i)return i;try{const o=JSON.parse(n(r(e,"..","..","package.json"),"utf-8"));i=o.version||"unknown"}catch{i="unknown"}return i}
@@ -1,49 +1,2 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * Post-install script: shows a progress bar during ICOA CLI setup.
4
- * Runs automatically after `npm install -g icoa-cli`.
5
- */
6
- const steps = [
7
- 'Initializing ICOA CLI...',
8
- 'Loading competition modules...',
9
- 'Configuring exam system...',
10
- 'Setting up references...',
11
- 'Finalizing installation...',
12
- ];
13
- const BAR_WIDTH = 30;
14
- const TOTAL = 100;
15
- function drawBar(percent, label) {
16
- const filled = Math.round((percent / TOTAL) * BAR_WIDTH);
17
- const empty = BAR_WIDTH - filled;
18
- const bar = `\x1b[32m${'█'.repeat(filled)}\x1b[90m${'░'.repeat(empty)}\x1b[0m`;
19
- const pct = `${String(percent).padStart(3)}%`;
20
- process.stdout.write(`\r ${bar} ${pct} \x1b[90m${label}\x1b[0m`);
21
- }
22
- async function run() {
23
- console.log();
24
- console.log(' \x1b[1m\x1b[37m██╗ ██████╗ ██████╗ █████╗\x1b[0m');
25
- console.log(' \x1b[1m\x1b[37m██║██╔════╝██╔═══██╗██╔══██╗\x1b[0m');
26
- console.log(' \x1b[1m\x1b[37m██║██║ ██║ ██║███████║\x1b[0m');
27
- console.log(' \x1b[1m\x1b[37m██║██║ ██║ ██║██╔══██║\x1b[0m');
28
- console.log(' \x1b[1m\x1b[37m██║╚██████╗╚██████╔╝██║ ██║\x1b[0m');
29
- console.log(' \x1b[1m\x1b[37m╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝\x1b[0m');
30
- console.log();
31
- for (let i = 0; i < steps.length; i++) {
32
- const startPct = Math.round((i / steps.length) * TOTAL);
33
- const endPct = Math.round(((i + 1) / steps.length) * TOTAL);
34
- for (let p = startPct; p <= endPct; p++) {
35
- drawBar(p, steps[i]);
36
- await new Promise((r) => setTimeout(r, 15));
37
- }
38
- }
39
- console.log();
40
- console.log();
41
- console.log(' \x1b[32m✓\x1b[0m ICOA CLI installed successfully!');
42
- console.log();
43
- console.log(' \x1b[90mGet started:\x1b[0m');
44
- console.log(' \x1b[1m\x1b[37micoa\x1b[0m \x1b[90mLaunch and select your mode\x1b[0m');
45
- console.log(' \x1b[1m\x1b[37micoa --help\x1b[0m \x1b[90mShow all commands\x1b[0m');
46
- console.log();
47
- }
48
- run().catch(() => { });
49
- export {};
2
+ const o=["Initializing ICOA CLI...","Loading competition modules...","Configuring exam system...","Setting up references...","Finalizing installation..."];function l(o,l){const e=Math.round(o/100*30),n=30-e,m=`${"█".repeat(e)}${"░".repeat(n)}`,t=`${String(o).padStart(3)}%`;process.stdout.write(`\r ${m} ${t} ${l}`)}(async function(){console.log(),console.log(" ██╗ ██████╗ ██████╗ █████╗"),console.log(" ██║██╔════╝██╔═══██╗██╔══██╗"),console.log(" ██║██║ ██║ ██║███████║"),console.log(" ██║██║ ██║ ██║██╔══██║"),console.log(" ██║╚██████╗╚██████╔╝██║ ██║"),console.log(" ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝"),console.log();for(let e=0;e<o.length;e++){const n=Math.round(e/o.length*100),m=Math.round((e+1)/o.length*100);for(let t=n;t<=m;t++)l(t,o[e]),await new Promise(o=>setTimeout(o,15))}console.log(),console.log(),console.log(" ✓ ICOA CLI installed successfully!"),console.log(),console.log(" Get started:"),console.log(" icoa Launch and select your mode"),console.log(" icoa --help Show all commands"),console.log()})().catch(()=>{});export{};