icoa-cli 2.19.262 → 2.19.264
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/ai4ctf.js +1 -1
- package/dist/commands/aienv.d.ts +21 -0
- package/dist/commands/aienv.js +1 -0
- package/dist/commands/ctf4ai-demo.js +1 -1
- package/dist/commands/ctf4vla.js +1 -1
- package/dist/commands/demo2.js +1 -1
- package/dist/commands/exam.js +1 -1
- package/dist/commands/ipynb.d.ts +11 -0
- package/dist/commands/ipynb.js +1 -0
- package/dist/index.js +1 -1
- package/dist/lib/aienv.d.ts +93 -0
- package/dist/lib/aienv.js +1 -0
- package/dist/lib/hint-client.js +1 -1
- package/dist/lib/ipynb-input.d.ts +25 -0
- package/dist/lib/ipynb-input.js +1 -0
- package/dist/lib/kernel-protocol.d.ts +83 -0
- package/dist/lib/kernel-protocol.js +1 -0
- package/dist/lib/kernel.d.ts +55 -0
- package/dist/lib/kernel.js +81 -0
- package/dist/repl.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `icoa ipynb` — an in-terminal Python notebook (Phase 1 UI of the CLI notebook
|
|
3
|
+
* arena, see `project_cli_notebook_arena_plan`). The free, ungraded engine —
|
|
4
|
+
* "a free Colab in your terminal" — and the cell surface the scored `arena`
|
|
5
|
+
* will later wrap.
|
|
6
|
+
*
|
|
7
|
+
* Runs against the aienv venv kernel via NotebookKernel (Node ↔ Python bridge).
|
|
8
|
+
* Lives in the main REPL using the BUG-008 listener-swap (no second readline).
|
|
9
|
+
*/
|
|
10
|
+
import type { Command } from 'commander';
|
|
11
|
+
export declare function registerIpynbCommand(program: Command): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{existsSync as e,mkdirSync as o,writeFileSync as n}from"node:fs";import{homedir as t}from"node:os";import{createInterface as r}from"node:readline";import{join as l}from"node:path";import chalk from"chalk";import{aienvPaths as a}from"../lib/aienv.js";import{shouldExecuteCell as s}from"../lib/ipynb-input.js";import{NotebookKernel as i}from"../lib/kernel.js";import{getMainRl as c}from"../lib/main-rl.js";const g=new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`,"g");export function registerIpynbCommand(o){o.command("ipynb").description("Open an in-terminal Python notebook (AI/ML — needs `aienv setup`)").action(async()=>{await async function(){const o=a(t()),n=l(t(),".icoa","ipynb-out");if(y(),!e(o.python))return console.log(chalk.yellow(" The AI/ML environment is not set up yet.")),console.log(chalk.gray(" Run ")+chalk.bold.cyan("aienv setup")+chalk.gray(" first (one-time, ~300 MB).")),void console.log();const p=new i({venvPython:o.python,venvRoot:o.root});process.stdout.write(chalk.gray(" Starting Python kernel…"));try{await p.start()}catch{return console.log(chalk.red(" failed.")),console.log(chalk.gray(" Check the environment: ")+chalk.cyan("aienv status")),void console.log()}console.log(chalk.green(" ready.")),console.log(chalk.gray(" Type Python and press Enter. Blocks (def/for/…) continue until a blank line.")),console.log(chalk.gray(" ")+chalk.cyan("help")+chalk.gray(" · ")+chalk.cyan("clear")+chalk.gray(" · ")+chalk.cyan("restart")+chalk.gray(" (fresh kernel) · ")+chalk.cyan("quit")),console.log();const f=c(),u=null!==f,m=u?f.listeners("line").slice():[];u&&f.removeAllListeners("line");const h=u?f:r({input:process.stdin,output:process.stdout,terminal:!0}),w=[];let b=!1;const v=()=>{h.setPrompt(chalk.bold.cyan(w.length?"icoa ipynb ...> ":"icoa ipynb> ")),h.prompt()};let k=()=>{};const x=new Promise(e=>{k=e}),P=async e=>{if(b)return;const o=e.trim();if(0===w.length){const e=o.toLowerCase();if("quit"===e||"exit"===e||"back"===e||"q"===e)return void await(async()=>{if(h.removeAllListeners("line"),console.log(),console.log(chalk.gray(" Notebook closed. Kernel stopped.")),console.log(),await p.shutdown().catch(()=>{}),u){h.setPrompt(chalk.bold.cyan("icoa> "));for(const e of m)h.on("line",e);h.prompt(),k()}else k(),h.close()})();if("clear"===e||"cls"===e)return console.clear(),y(),void v();if("restart"===e)return await(async()=>{process.stdout.write(chalk.gray(" Restarting kernel…")),await p.shutdown().catch(()=>{});try{await p.start(),console.log(chalk.green(" fresh kernel ready.")+chalk.gray(" (all variables cleared)"))}catch{console.log(chalk.red(" restart failed.")+chalk.gray(" Leave and re-enter the notebook."))}})(),void v();if("help"===e||"?"===e)return console.log(),console.log(chalk.bold.white(" Notebook commands")),console.log(chalk.cyan(" help")+chalk.gray(" show this")),console.log(chalk.cyan(" clear")+chalk.gray(" clear the screen")),console.log(chalk.cyan(" restart")+chalk.gray(" restart the kernel (wipes all variables)")),console.log(chalk.cyan(" quit")+chalk.gray(" leave the notebook")),console.log(chalk.gray(" Everything else runs as Python in the persistent kernel.")),console.log(),void v();if(""===o)return void v()}if(w.push(e),!s(w))return void v();const t=w.join("\n").replace(/\s+$/,"");if(w.length=0,""!==t.trim()){b=!0;try{!function(e,o){for(const n of e.outputs)if("stream"===n.kind){const e=n.text.replace(/\n$/,"");if(0===e.length)continue;console.log("stderr"===n.name?chalk.yellow(e):chalk.white(e))}else if("result"===n.kind)console.log(chalk.cyan(` Out[${e.execCount??"*"}]: `)+chalk.white(n.text));else if("error"===n.kind){console.log(chalk.bold.red(` ${n.ename}: ${n.evalue}`));for(const e of n.traceback)console.log(chalk.gray(` ${e.replace(g,"")}`))}else"display"===n.kind&&d(n.data,o)}(await p.execute(t),n)}catch(e){console.log(chalk.red(" Kernel error: ")+chalk.gray(e instanceof Error?e.message:String(e)))}b=!1,v()}else v()};h.on("line",e=>{P(e)}),u||h.on("close",async()=>{await p.shutdown().catch(()=>{}),process.exit(0)}),v(),await x}()})}function y(){console.log(),console.log(chalk.bold.white(" ICOA Notebook ")+chalk.gray("· in-terminal Python · AI/ML notebook arena")),console.log(chalk.gray(" ─────────────────────────────────────────────"))}let p=0;function d(e,t){if(e["image/png"])try{o(t,{recursive:!0}),p+=1;const r=l(t,`figure-${p}.png`);return n(r,Buffer.from(e["image/png"],"base64")),console.log(chalk.magenta(" 🖼 figure saved → ")+chalk.cyan(r)),void console.log(chalk.gray(" open it with your file browser (inline images need a graphical viewer)."))}catch{}e["text/plain"]&&console.log(chalk.white(` ${e["text/plain"]}`))}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as o}from"commander";import chalk from"chalk";import{registerCtfCommands as e}from"./commands/ctf.js";import{registerRefCommand as n}from"./commands/ref.js";import{registerShellCommand as s}from"./commands/shell.js";import{registerFilesCommand as r}from"./commands/files.js";import{registerConnectCommand as t}from"./commands/connect.js";import{registerNoteCommand as i}from"./commands/note.js";import{registerLogCommand as
|
|
2
|
+
import{Command as o}from"commander";import chalk from"chalk";import{registerCtfCommands as e}from"./commands/ctf.js";import{registerRefCommand as n}from"./commands/ref.js";import{registerShellCommand as s}from"./commands/shell.js";import{registerFilesCommand as r}from"./commands/files.js";import{registerConnectCommand as t}from"./commands/connect.js";import{registerNoteCommand as i}from"./commands/note.js";import{registerLogCommand as a}from"./commands/log.js";import{registerLangCommand as l}from"./commands/lang.js";import{registerSetupCommand as m}from"./commands/setup.js";import{registerEnvCommand as c}from"./commands/env.js";import{registerAienvCommand as g}from"./commands/aienv.js";import{registerIpynbCommand as d}from"./commands/ipynb.js";import{registerAi4ctfCommand as p}from"./commands/ai4ctf.js";import{registerExamCommand as y}from"./commands/exam.js";import{registerCtf4aiDemoCommand as h}from"./commands/ctf4ai-demo.js";import{registerThemeCommand as f}from"./commands/theme.js";import{registerLearnCommand as u}from"./commands/learn.js";import{registerCtf4VlaCommand as w}from"./commands/ctf4vla.js";import{registerDemo2Command as T}from"./commands/demo2.js";import{registerSimCommand as b}from"./commands/sim.js";import{registerArenaCommand as v}from"./commands/arena.js";import{getConfig as I,saveConfig as $}from"./lib/config.js";import{startRepl as A}from"./repl.js";import{RELAUNCH_CODE as j}from"./lib/menu-nav.js";import{setTerminalTheme as C}from"./lib/theme.js";import{checkForUpdates as E}from"./lib/update-check.js";import{detectIcoaInstalls as _}from"./lib/platform.js";import{ICOA_BIG as S}from"./lib/banner.js";import{readFileSync as x}from"node:fs";import{fileURLToPath as L}from"node:url";import{dirname as N,join as O}from"node:path";const k=N(L(import.meta.url)),F=JSON.parse(x(O(k,"..","package.json"),"utf-8")).version,R=chalk.cyan(" ─────────────────────────────────────────────────────"),U=`\n${R}\n\n${S.map(o=>` ${chalk.bold.white(o)}`).join("\n")}\n\n ${chalk.yellow("International Cyber Olympiad in AI 2026")}\n ${chalk.bold.magenta("The World's First AI-Native CLI Operating System")}\n ${chalk.bold.magenta("for Cybersecurity & AI Security Competition")}\n ${chalk.bold.magenta("and Olympiad for K-12")}\n\n ${chalk.green.bold("AI4CTF")} ${chalk.gray("[Day 1]")} ${chalk.white("AI as your teammate")}\n ${chalk.red.bold("CTF4AI")} ${chalk.gray("[Day 2]")} ${chalk.white("Challenge & evaluate AI systems")}\n ${chalk.cyan.bold("CTF4EAI")} ${chalk.gray("[Pioneer]")} ${chalk.white("Embodied AI security")}\n ${chalk.bold.yellow("AI is your teammate. AI is your target. AI is embodied.")}\n\n ${chalk.white("Sydney, Australia")} ${chalk.gray("Jun 27 - Jul 2, 2026")}\n ${chalk.cyan.underline("https://icoa2026.au")}\n\n ${chalk.gray(`CLI-Native Competition Terminal v${F}`)}\n\n${R}\n`;process.on("uncaughtException",o=>{"__REPL_NO_EXIT__"!==o.message&&(console.error(chalk.red("Error:"),o.message),process.exit(1))}),process.on("unhandledRejection",o=>{const e=o instanceof Error?o.message:String(o);"__REPL_NO_EXIT__"!==e&&(console.error(chalk.red("Error:"),e),process.exit(1))});{const o=process.argv.slice(2).some(o=>["-V","--version","-h","--help"].includes(o));if(!0===process.stdin.isTTY&&"1"!==process.env.ICOA_SUPERVISED&&!o){const{spawnSync:o}=await import("node:child_process");process.on("SIGINT",()=>{}),process.on("SIGTERM",()=>{});let e=0,n=!1;do{const s=o(process.execPath,process.argv.slice(1),{stdio:"inherit",env:{...process.env,ICOA_SUPERVISED:"1",...n?{ICOA_RELAUNCH:"1"}:{}}});if(s.signal){e=0;break}e=s.status,n=!0}while(e===j);process.exit(e??0)}}const P=new o;if(P.name("icoa").version(F).description("ICOA CLI — CLI-Native CTF Competition Terminal").option("--resume","Resume previous session").action(async o=>{const e=I();C("high-contrast"===e.themeVariant?"high-contrast":"dark"),E(),function(){const o=_();if(o.length<=1)return;const e=o[0];if([...o.map(o=>o.version||"0.0.0")].sort((o,e)=>function(o,e){const n=o.split(".").map(o=>parseInt(o,10)||0),s=e.split(".").map(o=>parseInt(o,10)||0);for(let o=0;o<3;o++)if((n[o]||0)!==(s[o]||0))return(n[o]||0)-(s[o]||0);return 0}(e,o))[0]!==e.version){console.log(),console.log(chalk.yellow.bold(" ⚠ Multiple icoa installations detected on PATH:"));for(let e=0;e<o.length;e++){const n=o[e],s=n.version?`v${n.version}`:"(version unreadable)",r=0===e?chalk.yellow("→"):" ",t=0===e?chalk.gray(" ← currently running (older — shadowing newer install)"):chalk.gray(" ← shadowed");console.log(` ${r} ${chalk.cyan(n.path.padEnd(28))} ${chalk.white(s)}${t}`)}console.log(),console.log(chalk.yellow(" The first install on PATH wins. To use the newer one, remove the older:")),console.log(),e.pkgDir?console.log(` ${chalk.bold.cyan(`sudo rm -rf ${e.pkgDir} ${e.path}`)}`):console.log(` ${chalk.bold.cyan(`sudo rm -rf ${e.path} # also delete its node_modules dir`)}`),console.log(),console.log(chalk.gray(" Then re-run icoa to confirm version banner shows the newer release.")),console.log()}}();const n=process.env.LANG||process.env.LC_ALL||process.env.LC_CTYPE||"";if(!/UTF-?8/i.test(n))if("win32"===process.platform){let o="";try{const{execFileSync:e}=await import("node:child_process");o=e("chcp.com",[],{encoding:"utf-8",timeout:1500,stdio:["ignore","pipe","ignore"]}).trim()}catch{}o.includes("65001")||(console.log(chalk.yellow(`⚠ Windows terminal is not using UTF-8 (current: ${o||"unknown"}).`)),console.log(chalk.gray(' Non-English text (Ukrainian, Chinese, Japanese, etc.) may show as "?" or garbled glyphs.')),console.log(chalk.gray(" Fix (run before ")+chalk.cyan("icoa")+chalk.gray("): ")+chalk.cyan("chcp 65001")),console.log(chalk.gray(" Or stay in English inside the CLI: ")+chalk.cyan("lang en")),console.log())}else console.log(chalk.yellow(`⚠ Your terminal locale is not UTF-8 (LANG=${n||"(unset)"}).`)),console.log(chalk.gray(' Non-English text and box characters may display as "?" or garbled glyphs.')),console.log(chalk.gray(" Fix: ")+chalk.cyan("export LANG=en_US.UTF-8")+chalk.gray(" (or your locale, e.g. ")+chalk.cyan("zh_CN.UTF-8")+chalk.gray(", ")+chalk.cyan("uk_UA.UTF-8")+chalk.gray(")")),console.log();if(console.log(U),process.argv.length<=2||o.resume)return o.resume||"1"===process.env.ICOA_RELAUNCH||await async function(){const o=process.stdin;if(o.isTTY&&"function"==typeof o.setRawMode)return new Promise(e=>{let n=!1;const s=()=>{if(!n){n=!0,o.removeListener("data",r);try{o.setRawMode(!1)}catch{}o.pause(),e()}},r=()=>s();o.setRawMode(!0),o.resume(),o.once("data",r),console.log(chalk.gray(" (press any key to continue...)")),setTimeout(s,3e3)});await new Promise(o=>setTimeout(o,3e3))}(),void A(P,!!o.resume)}),e(P),n(P),s(P),r(P),t(P),i(P),a(P),l(P),m(P),c(P),g(P),d(P),p(P),y(P),h(P),f(P),u(P),w(P),T(P),b(P),v(P),P.command("model",{hidden:!0}).argument("[name]","model name to switch to").action(o=>{const e=I().geminiModel||"gemini-2.5-flash-lite";o?($({geminiModel:o}),console.log(),console.log(chalk.green(" Model switched: ")+chalk.gray(e)+chalk.white(" -> ")+chalk.bold.white(o)),console.log()):(console.log(),console.log(chalk.gray(" Current model: ")+chalk.white(e)),console.log(),console.log(chalk.gray(" Available models:")),console.log(chalk.bold.white(" Gemini 3.x (Latest)")),console.log(chalk.white(" model gemini-3.5-flash ")+chalk.gray("Newest stable, paid Tier 1+")),console.log(chalk.white(" model gemini-3-flash-preview ")+chalk.gray("Preview")),console.log(chalk.bold.white(" Gemini 2.5 (Stable)")),console.log(chalk.white(" model gemini-2.5-flash-lite ")+chalk.gray("Fastest, default, free tier")),console.log(chalk.white(" model gemini-2.5-flash ")+chalk.gray("Heavier, thinking enabled")),console.log(chalk.white(" model gemini-2.5-pro ")+chalk.gray("Strong reasoning, paid")),console.log(chalk.bold.white(" Open Source")),console.log(chalk.white(" model gemma-4-31b-it ")+chalk.gray("Free, open-source")),console.log(chalk.white(" model <any-model-id> ")+chalk.gray("Custom model")),console.log(),console.log(chalk.gray(" Translation uses gemini-3.5-flash for best quality.")),console.log())}),"1"===process.env.ICOA_RESET_STATE)try{const{clearExamState:o}=await import("./lib/exam-state.js");o(),console.log(chalk.yellow("⚠ ICOA_RESET_STATE=1 — local exam state wiped.")),console.log(chalk.gray(" (Token NOT revoked server-side. Re-enter a fresh token with `exam <token>`.)")),console.log()}catch(o){console.log(chalk.red("⚠ ICOA_RESET_STATE: could not clear state — ")+chalk.gray(String(o)))}P.parse();
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* aienv — the AI/ML notebook-arena environment (Phase 0 of the CLI notebook
|
|
3
|
+
* arena, see `project_cli_notebook_arena_plan`).
|
|
4
|
+
*
|
|
5
|
+
* This is the PURE, testable core. The orchestration (venv creation, pip
|
|
6
|
+
* installs, status rendering) lives in `src/commands/aienv.ts`.
|
|
7
|
+
*
|
|
8
|
+
* Hard rule (BUG3 / Team Indonesia): aienv provisions its OWN venv under
|
|
9
|
+
* ~/.icoa/aienv and NEVER repoints the system python3. It also stays fully
|
|
10
|
+
* independent of `env.ts` so the heavy ML stack (jupyter/numpy/torch...) can
|
|
11
|
+
* never drag down the lightweight exam-only `env setup` path, and so a broken
|
|
12
|
+
* ML install is fault-isolated from the competition toolkit.
|
|
13
|
+
*/
|
|
14
|
+
export type AiGroup = 'core' | 'data' | 'deep';
|
|
15
|
+
export interface AiPkg {
|
|
16
|
+
/** Display name + the name `pip show` uses. */
|
|
17
|
+
name: string;
|
|
18
|
+
/** Module name for the `import x` readiness probe. */
|
|
19
|
+
import: string;
|
|
20
|
+
/** pip install spec (floor-pinned — see note below). */
|
|
21
|
+
spec: string;
|
|
22
|
+
group: AiGroup;
|
|
23
|
+
note?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface PyVersion {
|
|
26
|
+
major: number;
|
|
27
|
+
minor: number;
|
|
28
|
+
}
|
|
29
|
+
export interface AienvPaths {
|
|
30
|
+
/** The venv root: <home>/.icoa/aienv */
|
|
31
|
+
root: string;
|
|
32
|
+
/** Directory holding the venv executables (bin on posix, Scripts on win). */
|
|
33
|
+
binDir: string;
|
|
34
|
+
/** Absolute path to the venv's python interpreter. */
|
|
35
|
+
python: string;
|
|
36
|
+
/** Absolute path to the venv's pip. */
|
|
37
|
+
pip: string;
|
|
38
|
+
/** Manifest written after a successful setup — records python version + groups. */
|
|
39
|
+
marker: string;
|
|
40
|
+
}
|
|
41
|
+
export declare const MIN_HOST_PY: PyVersion;
|
|
42
|
+
export declare const PREFERRED_PY: PyVersion;
|
|
43
|
+
export declare const AIENV_MARKER_VERSION = 1;
|
|
44
|
+
/**
|
|
45
|
+
* ML stack for the notebook arena.
|
|
46
|
+
*
|
|
47
|
+
* Versions are FLOOR-pinned (`>=`), not exact-pinned like the CTF toolkit in
|
|
48
|
+
* env.ts. The scientific/ML stack ships platform- and arch-specific wheels that
|
|
49
|
+
* churn fast; exact pins across macOS/Linux/WSL × x64/arm64 routinely fail wheel
|
|
50
|
+
* resolution. Phase 0 only provisions — fairness-critical determinism lives
|
|
51
|
+
* server-side on the (fixed-model) GPU box, not in the student's local venv.
|
|
52
|
+
*
|
|
53
|
+
* - core: the Jupyter kernel-protocol foundation Phase 1's cell loop talks to.
|
|
54
|
+
* - data: the everyday data-science stack students compute with on CPU.
|
|
55
|
+
* - deep: multi-GB DL stack — OPT-IN via `aienv setup --deep` only. Under the
|
|
56
|
+
* fixed-model GPU design, most students call the server's inference API and
|
|
57
|
+
* never need torch locally, so it must not be in the default footprint.
|
|
58
|
+
*/
|
|
59
|
+
export declare const AIENV_PACKAGES: AiPkg[];
|
|
60
|
+
export declare function packagesForGroups(groups: AiGroup[]): AiPkg[];
|
|
61
|
+
/** Compute the venv paths for a given home directory + platform. */
|
|
62
|
+
export declare function aienvPaths(home: string, plat?: NodeJS.Platform): AienvPaths;
|
|
63
|
+
/**
|
|
64
|
+
* Ordered host-python candidates to build the venv from. We probe a versioned
|
|
65
|
+
* 3.12 BEFORE the bare `python3` so we don't accidentally seed the venv with a
|
|
66
|
+
* distro 3.10 when a 3.12 is present. We never touch the system python3 beyond
|
|
67
|
+
* reading its --version.
|
|
68
|
+
*/
|
|
69
|
+
export declare function hostPythonCandidates(plat?: NodeJS.Platform): string[];
|
|
70
|
+
/** Parse `Python 3.12.13` → {major:3, minor:12}. Tolerant of trailing newline. */
|
|
71
|
+
export declare function parsePyVersion(out: string | null | undefined): PyVersion | null;
|
|
72
|
+
/** True if `v` is at least `min` (major-then-minor comparison). */
|
|
73
|
+
export declare function meetsMinPy(v: PyVersion, min: PyVersion): boolean;
|
|
74
|
+
export interface HostPython {
|
|
75
|
+
cmd: string;
|
|
76
|
+
version: PyVersion;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the best host python to create the venv from.
|
|
80
|
+
*
|
|
81
|
+
* `probe(cmd)` runs `cmd --version` and returns its stdout (or null if the
|
|
82
|
+
* command is absent / errored) — injected so this stays pure + testable.
|
|
83
|
+
*
|
|
84
|
+
* Returns the first 3.12 interpreter found; otherwise the first python3 that
|
|
85
|
+
* meets MIN_HOST_PY; otherwise null.
|
|
86
|
+
*/
|
|
87
|
+
export declare function resolveHostPython(probe: (cmd: string) => string | null, plat?: NodeJS.Platform): HostPython | null;
|
|
88
|
+
/**
|
|
89
|
+
* Build the readiness probe for one package, run via the VENV python (absolute
|
|
90
|
+
* path) — never the system python3. Mirrors env.ts's `python3 -c "import x"`
|
|
91
|
+
* convention but always targets the venv interpreter.
|
|
92
|
+
*/
|
|
93
|
+
export declare function importCheckCmd(venvPython: string, pkg: AiPkg): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{posix as o,win32 as r}from"node:path";export const MIN_HOST_PY={major:3,minor:10};export const PREFERRED_PY={major:3,minor:12};export const AIENV_MARKER_VERSION=1;export const AIENV_PACKAGES=[{name:"jupyter_client",import:"jupyter_client",spec:"jupyter_client>=8.6",group:"core"},{name:"ipykernel",import:"ipykernel",spec:"ipykernel>=6.29",group:"core"},{name:"numpy",import:"numpy",spec:"numpy>=1.26",group:"data"},{name:"pandas",import:"pandas",spec:"pandas>=2.2",group:"data"},{name:"scikit-learn",import:"sklearn",spec:"scikit-learn>=1.4",group:"data"},{name:"matplotlib",import:"matplotlib",spec:"matplotlib>=3.8",group:"data"},{name:"torch",import:"torch",spec:"torch>=2.2",group:"deep",note:"large download (~2 GB)"},{name:"transformers",import:"transformers",spec:"transformers>=4.40",group:"deep"},{name:"datasets",import:"datasets",spec:"datasets>=2.19",group:"deep"}];export function packagesForGroups(o){const r=new Set(o);return AIENV_PACKAGES.filter(o=>r.has(o.group))}export function aienvPaths(n,t=process.platform){const e="win32"===t?r:o,p=e.join(n,".icoa","aienv");if("win32"===t){const o=e.join(p,"Scripts");return{root:p,binDir:o,python:e.join(o,"python.exe"),pip:e.join(o,"pip.exe"),marker:e.join(p,"icoa-aienv.json")}}const i=e.join(p,"bin");return{root:p,binDir:i,python:e.join(i,"python"),pip:e.join(i,"pip"),marker:e.join(p,"icoa-aienv.json")}}export function hostPythonCandidates(o=process.platform){return"win32"===o?["py -3.12","python","py -3","py"]:"darwin"===o?["python3.12","/opt/homebrew/opt/python@3.12/bin/python3.12","/usr/local/opt/python@3.12/bin/python3.12","python3"]:["python3.12","python3"]}export function parsePyVersion(o){if(!o)return null;const r=o.match(/(\d+)\.(\d+)(?:\.\d+)?/);return r?{major:Number(r[1]),minor:Number(r[2])}:null}export function meetsMinPy(o,r){return o.major!==r.major?o.major>r.major:o.minor>=r.minor}export function resolveHostPython(o,r=process.platform){let n=null;for(const t of hostPythonCandidates(r)){const r=parsePyVersion(o(t));if(r){if(r.major===PREFERRED_PY.major&&r.minor===PREFERRED_PY.minor)return{cmd:t,version:r};!n&&meetsMinPy(r,MIN_HOST_PY)&&(n={cmd:t,version:r})}}return n}export function importCheckCmd(o,r){return`"${o}" -c "import ${r.import}"`}
|
package/dist/lib/hint-client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(a,b){const v=a0b,c=a();while(!![]){try{const d
|
|
1
|
+
function a0a(){const x=['BMv0D29YAYbLCNjVCG','mti1mde1oujtuuDitq','mta2mJC4ne9Qt1HmEa','y3rMzfvYBa','ntq2nZi4rgDuB0Li','ue9tva','C3rHDhvZ','yxbWBgLJyxrPB24VANnVBG','zxHHBuLK','y2f0y2G','Bgv2zwW','DgLTzw91De1Z','mZmWota2rMjKugjg','Dg9Rzw4','ntbRwK1My2C','nJmWmde4oxvivwfwwa','mtmZmtG0mhbXzMPowG','AwnVys1JBgK','zgf0yq','BgfUzW','oJKWotaVyxbPl2LJB2eVzxHHBxmV','otbczfnoBeO','Ahr0Chm6lY9WCMfJDgLJzs5Py29HmJaYnI5HDq','BwvZC2fNzq','CxvLC3rPB24','l2fWAs9Py29Hl2v4yw1ZlW','m0L4C2vSAG','C3vJy2vZCW','mtG5nJeWofDYugTeDa'];a0a=function(){return x;};return a0a();}(function(a,b){const v=a0b,c=a();while(!![]){try{const d=parseInt(v(0x180))/(-0x25e7+-0x41e+-0x21*-0x146)+-parseInt(v(0x181))/(-0xcb9*0x3+-0x11ba*0x1+0x37e7)+-parseInt(v(0x17c))/(-0x2*0xb3e+0x6c5+0xfba)*(-parseInt(v(0x17e))/(-0x145a+-0xd07+-0x67*-0x53))+-parseInt(v(0x18d))/(0xb2f*-0x2+-0x109e+-0x7cd*-0x5)*(parseInt(v(0x18b))/(0x9*-0x3ef+-0xfc4*-0x1+0x13a9))+parseInt(v(0x18e))/(0x4*0x23b+-0x1*-0x21a3+-0x2a88)+-parseInt(v(0x183))/(0xc5a+-0x1*-0x8a1+-0x14f3)*(parseInt(v(0x194))/(-0x95b+-0x13c*-0x18+-0x1*0x143c))+-parseInt(v(0x18f))/(-0x15d3+-0x3c9+-0x19a6*-0x1);if(d===b)break;else c['push'](c['shift']());}catch(e){c['push'](c['shift']());}}}(a0a,-0xa027e+0x15ba68+-0xa8fd));import{getConfig as a0c}from'./config.js';function a0b(a,b){a=a-(-0x86e+-0x723+0x31*0x59);const c=a0a();let d=c[a];if(a0b['iAglZo']===undefined){var e=function(i){const j='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let l='',m='';for(let n=0x4*-0x873+-0x357+0x2523,o,p,q=-0x9be+-0x16f9+0x20b7;p=i['charAt'](q++);~p&&(o=n%(-0x243d+0x1c67*0x1+0xf*0x86)?o*(-0x1b20+-0xdd5+0x2935)+p:p,n++%(0x1101*0x2+0x284+-0x2*0x1241))?l+=String['fromCharCode'](0x1842+-0x126b+-0x5*0xf8&o>>(-(0x11d8+-0x484*0x2+-0x8ce)*n&0x2*0xdd+0x1*0x1ba7+-0x1f5*0xf)):-0x1*-0x1515+-0x2*-0x1190+-0x3835){p=j['indexOf'](p);}for(let r=-0x1ffd+-0x1f*0xb7+-0xef*-0x3a,s=l['length'];r<s;r++){m+='%'+('00'+l['charCodeAt'](r)['toString'](0x5f7+0x8*-0x133+0x3b1))['slice'](-(-0x23b5*0x1+-0x1d92+-0x4149*-0x1));}return decodeURIComponent(m);};a0b['SCQkvT']=e,a0b['LfjQXO']={},a0b['iAglZo']=!![];}const f=c[0x86d*0x1+-0x7c4+0xa9*-0x1],g=a+f,h=a0b['LfjQXO'][g];return!h?(d=a0b['SCQkvT'](d),a0b['LfjQXO'][g]=d):d=h,d;}export async function requestHint(d){const w=a0b,f=a0c(),g=f[w(0x182)]||w(0x178),h=d[w(0x192)]||f['language']||'en',j=d[w(0x18a)]??0x3b44+0x930+-0x2534,k=[g+w(0x17b)+d[w(0x187)]+'/hint',g+w(0x193)+d[w(0x187)]+'/hint'];let l=null;for(const p of k)try{const q=await fetch(p,{'method':w(0x184),'headers':{'Content-Type':w(0x186),'User-Agent':w(0x190)},'body':JSON['stringify']({'token':d[w(0x18c)],'question':d[w(0x17a)],'level':d[w(0x189)],'lang':h}),'signal':AbortSignal['timeout'](j)}),r=await q['json']()[w(0x188)](()=>({}));if(!q['ok']||!(-0x16f9+-0xc97+0x2391)===r[w(0x17d)]){if(l={'status':q[w(0x185)],'message':r?.['message']||'hint\x20request\x20failed\x20('+q['status']+')'},q[w(0x185)]>=0x1c67*0x1+0x1c*-0x42+-0x139f&&q[w(0x185)]<-0xdd5+-0x144c+0x1*0x2415)throw l;continue;}return r[w(0x191)];}catch(u){if(u&&'object'==typeof u&&'status'in u)throw u;l={'status':0x0,'message':u?.['message']||w(0x17f)};}const m={};m[w(0x185)]=0x0,m[w(0x179)]='hint\x20API\x20unreachable';throw l||m;}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ipynb-input — pure cell-input decision logic for the `icoa ipynb` REPL
|
|
3
|
+
* (Phase 1 UI of the CLI notebook arena).
|
|
4
|
+
*
|
|
5
|
+
* IPython-style model: a one-liner runs the moment you press Enter; a block
|
|
6
|
+
* (def/for/if/…, an open bracket, or a trailing backslash) keeps accepting
|
|
7
|
+
* lines until you end it with a blank line. Kept pure so the rule is unit-
|
|
8
|
+
* tested without a kernel or readline.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Does this line open a multi-line block? True when its CODE portion (trailing
|
|
12
|
+
* `#` comment stripped) ends with a colon. Naive comment-strip — good enough for
|
|
13
|
+
* opener detection (a real block opener never hides its colon in a comment).
|
|
14
|
+
*/
|
|
15
|
+
export declare function isBlockOpener(line: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Given the lines accumulated SO FAR (including the just-entered line), decide
|
|
18
|
+
* whether to execute the cell now or keep taking input.
|
|
19
|
+
*
|
|
20
|
+
* - empty buffer → nothing to run (false),
|
|
21
|
+
* - multi-line cell → execute as soon as the last line is blank,
|
|
22
|
+
* - single line → execute unless it opens a block / ends with `\` / has an
|
|
23
|
+
* unbalanced bracket.
|
|
24
|
+
*/
|
|
25
|
+
export declare function shouldExecuteCell(lines: string[]): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function isBlockOpener(t){const e=t.split("#")[0];return/:\s*$/.test(e)}export function shouldExecuteCell(t){if(0===t.length)return!1;const e=t[t.length-1];return t.length>1?""===e.trim():!(""===e.trim()||isBlockOpener(e)||/\\\s*$/.test(e)||function(t){let e=0;for(const n of t)"("===n||"["===n||"{"===n?e++:")"!==n&&"]"!==n&&"}"!==n||e--;return e}(e)>0)}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kernel protocol — the pure Node side of the Node ↔ Python-bridge boundary
|
|
3
|
+
* (Phase 1 of the CLI notebook arena, see `project_cli_notebook_arena_plan`).
|
|
4
|
+
*
|
|
5
|
+
* icoa is TypeScript/Node, but Jupyter kernels are driven by `jupyter_client`
|
|
6
|
+
* (Python). So a thin Python bridge (shipped alongside, run from the aienv
|
|
7
|
+
* venv) owns the kernel and speaks a line-delimited JSON protocol to Node: one
|
|
8
|
+
* event object per stdout line. This module parses those lines and folds them
|
|
9
|
+
* into renderable cell outputs.
|
|
10
|
+
*
|
|
11
|
+
* Keeping this layer pure (no I/O) means the wire contract is unit-tested
|
|
12
|
+
* without spawning Python — and it stays identical whether the bridge drives a
|
|
13
|
+
* LOCAL kernel (Phase 1) or a REMOTE one over a Kernel Gateway (Phase 2/4). The
|
|
14
|
+
* Node side never changes; only the bridge's connection does.
|
|
15
|
+
*/
|
|
16
|
+
/** Events the Python bridge emits, one JSON object per stdout line. */
|
|
17
|
+
export type KernelEvent = {
|
|
18
|
+
type: 'ready';
|
|
19
|
+
} | {
|
|
20
|
+
type: 'stream';
|
|
21
|
+
name: 'stdout' | 'stderr';
|
|
22
|
+
text: string;
|
|
23
|
+
} | {
|
|
24
|
+
type: 'result';
|
|
25
|
+
text: string;
|
|
26
|
+
data?: Record<string, string>;
|
|
27
|
+
} | {
|
|
28
|
+
type: 'display';
|
|
29
|
+
data: Record<string, string>;
|
|
30
|
+
} | {
|
|
31
|
+
type: 'error';
|
|
32
|
+
ename: string;
|
|
33
|
+
evalue: string;
|
|
34
|
+
traceback: string[];
|
|
35
|
+
} | {
|
|
36
|
+
type: 'done';
|
|
37
|
+
id: number;
|
|
38
|
+
count: number | null;
|
|
39
|
+
} | {
|
|
40
|
+
type: 'fatal';
|
|
41
|
+
message: string;
|
|
42
|
+
};
|
|
43
|
+
/** Normalized, render-ready outputs for one executed cell. */
|
|
44
|
+
export type CellOutput = {
|
|
45
|
+
kind: 'stream';
|
|
46
|
+
name: 'stdout' | 'stderr';
|
|
47
|
+
text: string;
|
|
48
|
+
} | {
|
|
49
|
+
kind: 'result';
|
|
50
|
+
text: string;
|
|
51
|
+
data?: Record<string, string>;
|
|
52
|
+
} | {
|
|
53
|
+
kind: 'display';
|
|
54
|
+
data: Record<string, string>;
|
|
55
|
+
} | {
|
|
56
|
+
kind: 'error';
|
|
57
|
+
ename: string;
|
|
58
|
+
evalue: string;
|
|
59
|
+
traceback: string[];
|
|
60
|
+
};
|
|
61
|
+
export interface FoldedCell {
|
|
62
|
+
outputs: CellOutput[];
|
|
63
|
+
/** false if the cell raised. */
|
|
64
|
+
ok: boolean;
|
|
65
|
+
/** the kernel's execution_count, or null if no `done` event was seen. */
|
|
66
|
+
execCount: number | null;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Parse one stdout line from the bridge into a KernelEvent. Returns null for
|
|
70
|
+
* blank lines, non-JSON, or JSON without a recognized `type` (the bridge may
|
|
71
|
+
* interleave incidental output; we ignore anything not in the protocol).
|
|
72
|
+
*/
|
|
73
|
+
export declare function parseKernelEvent(line: string): KernelEvent | null;
|
|
74
|
+
/**
|
|
75
|
+
* Fold a cell's event stream into renderable outputs.
|
|
76
|
+
*
|
|
77
|
+
* - consecutive `stream` events of the SAME name coalesce into one text block
|
|
78
|
+
* (matches Jupyter's stdout/stderr batching → clean rendering),
|
|
79
|
+
* - `result` / `display` / `error` pass through as discrete blocks,
|
|
80
|
+
* - any `error` sets ok=false,
|
|
81
|
+
* - `done.count` becomes execCount (null if the cell never finished).
|
|
82
|
+
*/
|
|
83
|
+
export declare function foldCellOutputs(events: KernelEvent[]): FoldedCell;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=new Set(["ready","stream","result","display","error","done","fatal"]);export function parseKernelEvent(t){const a=t.trim();if(!a)return null;let r;try{r=JSON.parse(a)}catch{return null}if(!r||"object"!=typeof r)return null;const n=r.type;return"string"==typeof n&&e.has(n)?r:null}export function foldCellOutputs(e){const t=[];let a=!0,r=null;for(const n of e)switch(n.type){case"stream":{const e=t[t.length-1];e&&"stream"===e.kind&&e.name===n.name?e.text+=n.text:t.push({kind:"stream",name:n.name,text:n.text});break}case"result":t.push({kind:"result",text:n.text,...n.data?{data:n.data}:{}});break;case"display":t.push({kind:"display",data:n.data});break;case"error":a=!1,t.push({kind:"error",ename:n.ename,evalue:n.evalue,traceback:n.traceback});break;case"done":r=n.count;break;case"fatal":a=!1,t.push({kind:"error",ename:"KernelError",evalue:n.message,traceback:[n.message]})}return{outputs:t,ok:a,execCount:r}}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NotebookKernel — the Node driver for a Jupyter kernel, Phase 1 of the CLI
|
|
3
|
+
* notebook arena (see `project_cli_notebook_arena_plan`).
|
|
4
|
+
*
|
|
5
|
+
* icoa (Node) can't talk to a Jupyter kernel directly, so we spawn a small
|
|
6
|
+
* Python BRIDGE under the aienv venv. The bridge owns the kernel via
|
|
7
|
+
* `jupyter_client` and exchanges line-delimited JSON with us (parsed by
|
|
8
|
+
* `kernel-protocol.ts`). This keeps the kernel a PLUGGABLE backend: Phase 1
|
|
9
|
+
* runs it locally; Phase 2/4 point the same bridge at a remote Kernel Gateway
|
|
10
|
+
* with zero change to the Node side.
|
|
11
|
+
*
|
|
12
|
+
* Isolation: the bridge runs as `<venv>/bin/python`, and it registers its
|
|
13
|
+
* kernelspec INSIDE the venv's own `share/jupyter` tree — so the kernel is the
|
|
14
|
+
* venv interpreter, never the system python3 (BUG3 invariant), and nothing is
|
|
15
|
+
* written outside the venv. On posix the kernel uses IPC transport (unix
|
|
16
|
+
* sockets) so it opens no TCP ports.
|
|
17
|
+
*/
|
|
18
|
+
import { type FoldedCell } from './kernel-protocol.js';
|
|
19
|
+
export declare const KERNEL_BRIDGE_PY: string;
|
|
20
|
+
export interface KernelStartOptions {
|
|
21
|
+
/** Absolute path to the venv python (from aienvPaths().python). */
|
|
22
|
+
venvPython: string;
|
|
23
|
+
/** Directory to drop the bridge script in (the venv root). */
|
|
24
|
+
venvRoot: string;
|
|
25
|
+
/** Optional: max ms to wait for the kernel `ready` event. */
|
|
26
|
+
readyTimeoutMs?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A live notebook kernel. Executes are serialized (one cell at a time, as a
|
|
30
|
+
* notebook does) so the line-buffered output demux stays unambiguous.
|
|
31
|
+
*/
|
|
32
|
+
export declare class NotebookKernel {
|
|
33
|
+
private proc;
|
|
34
|
+
private buf;
|
|
35
|
+
private nextId;
|
|
36
|
+
private ready;
|
|
37
|
+
private pending;
|
|
38
|
+
private onReady;
|
|
39
|
+
private onExit;
|
|
40
|
+
private readonly opts;
|
|
41
|
+
private readonly bridgePath;
|
|
42
|
+
constructor(opts: KernelStartOptions);
|
|
43
|
+
/**
|
|
44
|
+
* Write the bridge + spawn it; resolves once the kernel reports `ready`.
|
|
45
|
+
* Safe to call again after shutdown() to get a fresh kernel (restart) — all
|
|
46
|
+
* per-run state is reset here.
|
|
47
|
+
*/
|
|
48
|
+
start(): Promise<void>;
|
|
49
|
+
/** Run one cell; resolves with its folded outputs after the `done` event. */
|
|
50
|
+
execute(code: string): Promise<FoldedCell>;
|
|
51
|
+
/** Ask the bridge to shut the kernel down; resolves when the process exits. */
|
|
52
|
+
shutdown(): Promise<void>;
|
|
53
|
+
private onStdout;
|
|
54
|
+
private handleEvent;
|
|
55
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import{spawn as e}from"node:child_process";import{writeFileSync as t}from"node:fs";import{join as i}from"node:path";import{foldCellOutputs as n,parseKernelEvent as r}from"./kernel-protocol.js";export const KERNEL_BRIDGE_PY=String.raw`import sys, os, json, queue
|
|
2
|
+
|
|
3
|
+
def emit(o):
|
|
4
|
+
sys.stdout.write(json.dumps(o) + "\n")
|
|
5
|
+
sys.stdout.flush()
|
|
6
|
+
|
|
7
|
+
def main():
|
|
8
|
+
# Self-contained kernelspec INSIDE the venv (found via sys.prefix; isolated,
|
|
9
|
+
# wiped with the venv). Points the kernel at this very interpreter.
|
|
10
|
+
specdir = os.path.join(sys.prefix, "share", "jupyter", "kernels", "icoa-aienv")
|
|
11
|
+
try:
|
|
12
|
+
os.makedirs(specdir, exist_ok=True)
|
|
13
|
+
with open(os.path.join(specdir, "kernel.json"), "w") as f:
|
|
14
|
+
json.dump({"argv": [sys.executable, "-m", "ipykernel_launcher", "-f", "{connection_file}"],
|
|
15
|
+
"display_name": "ICOA aienv", "language": "python"}, f)
|
|
16
|
+
except Exception as e:
|
|
17
|
+
emit({"type": "fatal", "message": "could not write kernelspec: %s" % e}); os._exit(1)
|
|
18
|
+
|
|
19
|
+
from jupyter_client.manager import KernelManager
|
|
20
|
+
transport = "ipc" if os.name == "posix" else "tcp"
|
|
21
|
+
km = KernelManager(kernel_name="icoa-aienv", transport=transport)
|
|
22
|
+
try:
|
|
23
|
+
km.start_kernel()
|
|
24
|
+
kc = km.client(); kc.start_channels(); kc.wait_for_ready(timeout=60)
|
|
25
|
+
except Exception as e:
|
|
26
|
+
emit({"type": "fatal", "message": "kernel did not start: %s" % e}); os._exit(1)
|
|
27
|
+
emit({"type": "ready"})
|
|
28
|
+
|
|
29
|
+
for line in sys.stdin:
|
|
30
|
+
line = line.strip()
|
|
31
|
+
if not line:
|
|
32
|
+
continue
|
|
33
|
+
try:
|
|
34
|
+
req = json.loads(line)
|
|
35
|
+
except Exception:
|
|
36
|
+
continue
|
|
37
|
+
if req.get("cmd") == "shutdown":
|
|
38
|
+
break
|
|
39
|
+
cid = req.get("id"); code = req.get("code", "")
|
|
40
|
+
mid = kc.execute(code)
|
|
41
|
+
count = None
|
|
42
|
+
# Drain iopub for THIS request until the kernel goes idle.
|
|
43
|
+
while True:
|
|
44
|
+
try:
|
|
45
|
+
m = kc.get_iopub_msg(timeout=1)
|
|
46
|
+
except queue.Empty:
|
|
47
|
+
if not km.is_alive():
|
|
48
|
+
emit({"type": "fatal", "message": "kernel died during execution"}); break
|
|
49
|
+
continue
|
|
50
|
+
if m.get("parent_header", {}).get("msg_id") != mid:
|
|
51
|
+
continue
|
|
52
|
+
t = m["msg_type"]; c = m["content"]
|
|
53
|
+
if t == "stream":
|
|
54
|
+
emit({"type": "stream", "name": c.get("name", "stdout"), "text": c.get("text", "")})
|
|
55
|
+
elif t == "execute_result":
|
|
56
|
+
count = c.get("execution_count", count)
|
|
57
|
+
emit({"type": "result", "text": c["data"].get("text/plain", ""), "data": c["data"]})
|
|
58
|
+
elif t == "display_data":
|
|
59
|
+
emit({"type": "display", "data": c["data"]})
|
|
60
|
+
elif t == "error":
|
|
61
|
+
emit({"type": "error", "ename": c.get("ename", ""), "evalue": c.get("evalue", ""),
|
|
62
|
+
"traceback": c.get("traceback", [])})
|
|
63
|
+
elif t == "status" and c.get("execution_state") == "idle":
|
|
64
|
+
break
|
|
65
|
+
# Shell reply carries the authoritative execution_count for In[n] numbering.
|
|
66
|
+
try:
|
|
67
|
+
reply = kc.get_shell_msg(timeout=5)
|
|
68
|
+
if reply.get("parent_header", {}).get("msg_id") == mid:
|
|
69
|
+
count = reply["content"].get("execution_count", count)
|
|
70
|
+
except Exception:
|
|
71
|
+
pass
|
|
72
|
+
emit({"type": "done", "id": cid, "count": count})
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
km.shutdown_kernel(now=True)
|
|
76
|
+
except Exception:
|
|
77
|
+
pass
|
|
78
|
+
os._exit(0)
|
|
79
|
+
|
|
80
|
+
main()
|
|
81
|
+
`;export class NotebookKernel{proc=null;buf="";nextId=1;ready=!1;pending=null;onReady=null;onExit=null;opts;bridgePath;constructor(e){this.opts=e,this.bridgePath=i(e.venvRoot,"icoa-kernel-bridge.py")}start(){this.buf="",this.ready=!1,this.pending=null,this.nextId=1,t(this.bridgePath,KERNEL_BRIDGE_PY);const i=e(this.opts.venvPython,[this.bridgePath],{stdio:["pipe","pipe","pipe"]});this.proc=i,i.stdout.setEncoding("utf-8"),i.stdout.on("data",e=>this.onStdout(e)),i.on("exit",()=>{this.ready=!1,this.onExit?.()});const n=this.opts.readyTimeoutMs??6e4;return new Promise((e,t)=>{const i=setTimeout(()=>t(new Error("kernel did not become ready in time")),n);this.onReady=()=>{clearTimeout(i),e()},this.onExit=()=>{clearTimeout(i),this.ready||t(new Error("kernel process exited before ready"))}})}execute(e){if(!this.proc||!this.ready)return Promise.reject(new Error("kernel not ready"));if(this.pending)return Promise.reject(new Error("a cell is already executing"));const t=this.nextId++;return new Promise(i=>{this.pending={id:t,events:[],resolve:i},this.proc?.stdin.write(`${JSON.stringify({id:t,code:e})}\n`)})}shutdown(){const e=this.proc;return e?new Promise(t=>{this.onExit=()=>t();try{e.stdin.write(`${JSON.stringify({cmd:"shutdown"})}\n`)}catch{e.kill()}setTimeout(()=>{this.proc&&(this.proc.kill(),t())},5e3)}):Promise.resolve()}onStdout(e){this.buf+=e;let t=this.buf.indexOf("\n");for(;-1!==t;){const e=this.buf.slice(0,t);this.buf=this.buf.slice(t+1),this.handleEvent(r(e)),t=this.buf.indexOf("\n")}}handleEvent(e){if(!e)return;if("ready"===e.type)return this.ready=!0,void this.onReady?.();const t=this.pending;if(t){if("done"===e.type){if(e.id!==t.id)return;const i=n(t.events.concat(e));return this.pending=null,void t.resolve(i)}t.events.push(e)}}}
|