pi-repl-py 0.2.0 → 0.2.2

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/README.md CHANGED
@@ -43,18 +43,19 @@ clear notice. How the interpreter is resolved is in [docs/ARCHITECTURE.md](docs/
43
43
  - **A real `ipython` kernel**, not a hand-rolled `exec` loop.
44
44
  - **Shell and file IO as plain Python.** `!cmd` and `%%bash` run shell fire-and-forget,
45
45
  `subprocess.run(...)` brings the result back into a variable, and `open()` / `pathlib`
46
- read and write files no wrapper API to learn, and nothing extra to describe to the model.
46
+ read and write files. No wrapper API to learn, and nothing extra to describe to the model.
47
47
  - **Error survival.** A cell that throws reports the traceback and the kernel keeps going.
48
48
  - **An honest evaluator.** If it restarts, it names what state it could revive and what it lost, so you don't trust memory that's gone.
49
49
 
50
50
  ## Helpers
51
51
 
52
- A **helper** is a Python function you preload into every kernel. Drop a file in the one
53
- helpers directory, restart the session, and the function is callable from the workspace
54
- for example, `helpers/double.py` exposing `def double` becomes `double(...)`. It ships
55
- **empty** (shell and file IO are already plain Python), so a fresh install preloads nothing
56
- until you add one. Each helper's `helper_description` is shown to the model verbatim;
57
- the full contract lives in [docs/how-to-functions.md](docs/how-to-functions.md).
52
+ A **helper** is a `.py` file that gets exec'd into every kernel, so whatever it defines
53
+ (like functions, classes, constants, imports, or a module that manages a tricky piece of
54
+ complexity) is available in the workspace. Drop a file in the one helpers directory and
55
+ restart the session; e.g. `helpers/double.py` defining `def double(x)` becomes callable as
56
+ `double(...)`. It ships **empty** (shell and file IO are already plain Python), so a fresh
57
+ install preloads nothing until you add one. Each helper's `helper_description` is shown to
58
+ the model verbatim; the full contract lives in [docs/helpers.md](docs/helpers.md).
58
59
 
59
60
  Everything the extension keeps lives under one folder in your home directory:
60
61
 
@@ -65,7 +66,7 @@ Everything the extension keeps lives under one folder in your home directory:
65
66
  state/ per-session namespace snapshots
66
67
  ```
67
68
 
68
- The helpers directory is fixed at `~/.pi/agent/pi-repl/helpers` no config file.
69
+ The helpers directory is fixed at `~/.pi/agent/pi-repl/helpers`. No config file.
69
70
 
70
71
  Changing a helper (adding/removing a file, renaming one with a `_` prefix) needs a
71
72
  **session restart / `/reload`**: the prompt list is built when `execute` is registered and
@@ -90,4 +91,4 @@ The Python interpreter is auto-resolved (the venv, else `$PYTHON`/`python3`).
90
91
 
91
92
  ## License
92
93
 
93
- MIT see [LICENSE](LICENSE).
94
+ MIT. See [LICENSE](LICENSE).
@@ -121,7 +121,7 @@ The model discovers what is loaded by listing the namespace with ordinary Python
121
121
  ```
122
122
 
123
123
  For the full helper contract — the description, the docstring, disabling — see
124
- [how-to-functions.md](how-to-functions.md).
124
+ [helpers.md](helpers.md).
125
125
 
126
126
  ## Snapshots & honest resets
127
127
 
@@ -179,4 +179,4 @@ but working cell may run on).
179
179
  ## Reference documentation
180
180
 
181
181
  - Philosophy and design rationale: [philosophy.md](philosophy.md)
182
- - Adding a helper: [how-to-functions.md](how-to-functions.md)
182
+ - Adding a helper: [helpers.md](helpers.md)
@@ -1,12 +1,12 @@
1
1
  # How to write a helper
2
2
 
3
- A **helper** is a Python function you write once that becomes available to the agent in
4
- every `pi --repl` session. You drop a `.py` file into one folder, restart the session, and
5
- the function is callable from the workspace like a bookmark for code the agent keeps
6
- reaching for.
3
+ A **helper** is a `.py` file that gets exec'd into every kernel, so anything it defines —
4
+ functions, classes, constants, imports, or a module that owns a fragile or opaque piece of
5
+ work becomes part of the agent's workspace; you drop the file into one folder, restart
6
+ the session, and it's available. Like a bookmark for code the agent keeps reaching for.
7
7
 
8
- This guide shows the smallest helper that works, then explains the three parts every
9
- helper file has and the habits that make a helper useful.
8
+ This guide shows the smallest callable helper that works, then explains the parts of a
9
+ helper file and the habits that make a helper useful.
10
10
 
11
11
  ## Prerequisites
12
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-repl-py",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "description": "A pi extension with a single tool: execute, running a TypeScript host with a persistent Python (ipykernel) evaluator and a user-configurable toolbox of functions.",
6
6
  "keywords": [
@@ -2,30 +2,18 @@
2
2
  /**
3
3
  * postinstall: build the stable per-user Python venv the evaluator needs.
4
4
  *
5
- * The evaluator (src/engine/kernel.ts) drives a real ipykernel directly over
6
- * the Jupyter protocol; `ipykernel` is the only hard runtime dependency. When
7
- * this is installed as a pi package there is no repo-local `.venv` (gitignored
8
- * and excluded from the npm tarball), so we create one at a stable path the
9
- * engine also knows about:
5
+ * On a package install the venv is built at a stable path the engine knows:
10
6
  *
11
7
  * ~/.pi/agent/pi-repl/venv/bin/python3
12
8
  *
13
- * HELPERS live ONLY in the user-owned config dir:
14
- *
15
- * ~/.pi/agent/pi-repl/helpers/
16
- *
17
- * There is no helper/config folder anywhere in this package (no
18
- * src/engine/helpers, no templates/). The helpers dir is created if missing,
19
- * but no default helpers are seeded — the REPL itself already provides shell
20
- * (via `!cmd`, `%%bash`, `subprocess`) and file IO (via `open`, `pathlib`).
21
- * Users add their own helper .py files freely; existing files are never clobbered.
22
- *
23
- * Failures are non-fatal: if there's no system python3 or no network we print a
24
- * clear notice and let the engine fall back to '$PYTHON' or 'python3' at runtime.
9
+ * The venv is repaired in place: if it exists but ipykernel is not importable,
10
+ * this reflushes the venv and reinstalls rather than trusting a half-built one.
11
+ * Failures are NOT silent: a bad build exits non-zero so `npm install` / `pi
12
+ * install` visibly fails instead of leaving a broken evaluator.
25
13
  */
26
14
 
27
15
  import { execSync } from "node:child_process";
28
- import { existsSync, mkdirSync } from "node:fs";
16
+ import { mkdirSync } from "node:fs";
29
17
  import { homedir } from "node:os";
30
18
  import { join } from "node:path";
31
19
 
@@ -34,6 +22,17 @@ const PY = join(VENV_DIR, "bin", "python3");
34
22
  const DEPS = ["ipykernel"];
35
23
  const HELPERS_DIR = join(homedir(), ".pi", "agent", "pi-repl", "helpers");
36
24
 
25
+ // A venv that exists but can't import ipykernel is broken. Never trust the
26
+ // binary alone — a half-built venv otherwise looks "already up" forever.
27
+ function ipykernelOk() {
28
+ try {
29
+ execSync(`${PY} -c "import ipykernel"`, { stdio: "ignore" });
30
+ return true;
31
+ } catch {
32
+ return false;
33
+ }
34
+ }
35
+
37
36
  function log(m) {
38
37
  process.stdout.write(`[pi-repl] ${m}\n`);
39
38
  }
@@ -65,29 +64,41 @@ function seedHelpersDir() {
65
64
 
66
65
  function main() {
67
66
  seedHelpersDir();
68
- if (existsSync(PY)) {
69
- log(`venv already present at ${VENV_DIR}`);
67
+ if (ipykernelOk()) {
68
+ log(`venv ready (ipykernel present) at ${VENV_DIR}`);
70
69
  return;
71
70
  }
72
71
  const systemPython = findSystemPython();
73
72
  if (!systemPython) {
74
- warn(
73
+ fail(
75
74
  `no python3 found on PATH; could not create the evaluator venv. ` +
76
- `Install python3 and run '${PY.slice(-60)} -m venv' manually, or set $PYTHON to point at one.`
75
+ `Install python3 and run '${PY.slice(-60)} -m venv' manually.`
77
76
  );
78
77
  return;
79
78
  }
80
- log(`creating evaluator venv at ${VENV_DIR} (uses ${systemPython})...`);
79
+ log(`building evaluator venv at ${VENV_DIR} (uses ${systemPython})...`);
81
80
  try {
82
81
  mkdirSync(join(VENV_DIR, ".."), { recursive: true });
83
- execSync(`${systemPython} -m venv ${VENV_DIR}`, { stdio: "inherit" });
82
+ // flush a half-built venv so pip starts from a clean, knowable slate
83
+ execSync(`${systemPython} -m venv --clear ${VENV_DIR}`, { stdio: "inherit" });
84
84
  execSync(`${PY} -m pip install --upgrade pip`, { stdio: "inherit" });
85
85
  execSync(`${PY} -m pip install ${DEPS.join(" ")}`, { stdio: "inherit" });
86
+ if (!ipykernelOk()) {
87
+ fail(`ipykernel still not importable after install; the evaluator won't start.`);
88
+ return;
89
+ }
86
90
  log("done. The pi-repl evaluator will use this venv.");
87
91
  } catch (error) {
88
- warn(`could not build the evaluator venv (${error && error.message ? error.message : error}). `);
89
- warn("You must install ipykernel in that venv before the evaluator runs.");
92
+ // A real failure must not exit 0 with a broken venv. npm/pi will see the
93
+ // nonzero exit and report the install as failed instead of silently
94
+ // handing the user a dead evaluator.
95
+ fail(`could not build the evaluator venv (${error && error.message ? error.message : error}). `);
90
96
  }
91
97
  }
92
98
 
99
+ function fail(m) {
100
+ process.stderr.write(`[pi-repl] ERROR: ${m}\n`);
101
+ process.exit(1);
102
+ }
103
+
93
104
  main();