@mammothb/pi-eval 1.0.0 → 2.0.0

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
@@ -11,19 +11,33 @@ between calls.
11
11
 
12
12
  ### Tool parameters
13
13
 
14
- | Parameter | Type | Default | Description |
15
- | ------------------ | ------ | ---------- | ----------- |
16
- | `language` | string | _(required)_ | Programming language: `"javascript"` or `"python"` |
17
- | `code` | string | _(required)_ | Code to execute |
18
- | `nodeModulesPath` | string | — | Path to node_modules for `require()` resolution |
19
- | `pythonPath` | string | — | Path to python3 binary (e.g., `.venv/bin/python3`) |
14
+ | Parameter | Type | Default | Description |
15
+ | ---------- | ------ | ----------- | ----------- |
16
+ | `language` | string | _(required)_ | Programming language: `"javascript"` or `"python"` |
17
+ | `code` | string | _(required)_ | Code to execute |
18
+
19
+ ### Runtime configuration
20
+
21
+ Python binary and Node.js module paths are configured via JSON files, not per-call parameters:
22
+
23
+ - **Global**: `~/.pi/agent/pi-eval.json`
24
+ - **Project**: `.pi/pi-eval.json` (overrides global)
25
+
26
+ ```json
27
+ {
28
+ "pythonPath": ".venv/bin/python3",
29
+ "nodeModulesPath": "./node_modules"
30
+ }
31
+ ```
32
+
33
+ Relative paths in project configs are resolved relative to the project root.
20
34
 
21
35
  ### Features
22
36
 
23
37
  - **JavaScript**: Writes code to a temp file, spawns `node` as a subprocess.
24
38
  Console output is captured as labeled `STDOUT:` / `STDERR:` sections.
39
+ Set `nodeModulesPath` in config to make project packages available via `require()`.
25
40
  - **Python**: Spawns `python3` with `-c`, capturing stdout/stderr identically.
26
- Supports virtual environments via the `pythonPath` parameter.
27
- - **Safety**: 30-second timeout, 1 MB output cap, abort-on-Escape support.
28
- - **Dependency isolation**: Use `nodeModulesPath` to resolve packages from a
29
- project's `node_modules/`. Use `pythonPath` to target a venv.
41
+ By default, searches `PATH` for `python3` (falling back to `/usr/bin/python3`,
42
+ `/usr/local/bin/python3`). Set `pythonPath` in config to target a venv or custom binary.
43
+ - **Safety**: 30-second timeout, 1 MB output cap, Escape to cancel a running evaluation.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mammothb/pi-eval",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "A pi extension that adds an eval tool for executing JavaScript and Python code in isolated subprocesses",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -15,6 +15,9 @@
15
15
  "./index.ts"
16
16
  ]
17
17
  },
18
+ "dependencies": {
19
+ "@mammothb/pi-shared": "1.0.0"
20
+ },
18
21
  "devDependencies": {
19
22
  "typebox": "^1.2.0"
20
23
  },
package/src/config.ts ADDED
@@ -0,0 +1,67 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
4
+ import { expandTilde } from "@mammothb/pi-shared";
5
+
6
+ export interface EvalConfig {
7
+ /**
8
+ * Path to a python3 binary (e.g., '.venv/bin/python3' for venvs).
9
+ * When set, this binary is used for all Python evaluations instead of
10
+ * searching PATH for 'python3'.
11
+ */
12
+ pythonPath?: string;
13
+ /**
14
+ * Path to a node_modules directory. When set, NODE_PATH is passed
15
+ * to the Node.js subprocess so require() resolves from this directory.
16
+ * Use './node_modules' for project-local packages.
17
+ */
18
+ nodeModulesPath?: string;
19
+ }
20
+
21
+ export const DEFAULT_CONFIG: EvalConfig = {};
22
+
23
+ /**
24
+ * Load config from JSON files. Project config (`.pi/pi-eval.json`)
25
+ * overrides global config (`~/.pi/agent/pi-eval.json`).
26
+ *
27
+ * Returns the default config if no config files exist.
28
+ */
29
+ export function loadConfig(cwd: string): EvalConfig {
30
+ const globalPath = join(getAgentDir(), "pi-eval.json");
31
+ const projectPath = join(cwd, ".pi", "pi-eval.json");
32
+
33
+ let global: Partial<EvalConfig> | undefined;
34
+ let project: Partial<EvalConfig> | undefined;
35
+
36
+ if (existsSync(globalPath)) {
37
+ try {
38
+ global = JSON.parse(readFileSync(globalPath, "utf-8"));
39
+ } catch (err) {
40
+ console.error(`Failed to load global config from ${globalPath}: ${err}`);
41
+ }
42
+ }
43
+
44
+ if (existsSync(projectPath)) {
45
+ try {
46
+ project = JSON.parse(readFileSync(projectPath, "utf-8"));
47
+ } catch (err) {
48
+ console.error(
49
+ `Failed to load project config from ${projectPath}: ${err}`,
50
+ );
51
+ }
52
+ }
53
+
54
+ const merged = {
55
+ ...DEFAULT_CONFIG,
56
+ ...global,
57
+ ...project,
58
+ };
59
+
60
+ return {
61
+ ...merged,
62
+ pythonPath: merged.pythonPath ? expandTilde(merged.pythonPath) : undefined,
63
+ nodeModulesPath: merged.nodeModulesPath
64
+ ? expandTilde(merged.nodeModulesPath)
65
+ : undefined,
66
+ };
67
+ }
package/src/eval.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
2
2
  import { Type } from "typebox";
3
+ import { loadConfig } from "./config.js";
3
4
  import { executeJavaScript } from "./javascript.js";
4
5
  import { executePython } from "./python.js";
5
6
  import {
@@ -13,21 +14,6 @@ export const TIMEOUT_MS = 30_000;
13
14
  const Parameters = Type.Object({
14
15
  language: Type.Union([Type.Literal("javascript"), Type.Literal("python")]),
15
16
  code: Type.String({ description: "Code to execute" }),
16
- pythonPath: Type.Optional(
17
- Type.String({
18
- description:
19
- "Path to python3 binary (e.g., '.venv/bin/python3' for venvs). " +
20
- "Defaults to 'python3'.",
21
- }),
22
- ),
23
- nodeModulesPath: Type.Optional(
24
- Type.String({
25
- description:
26
- "Path to a node_modules directory. When set, NODE_PATH is passed " +
27
- "to the subprocess so require() resolves from this directory. " +
28
- "Use './node_modules' for project-local packages.",
29
- }),
30
- ),
31
17
  });
32
18
 
33
19
  export function createEvalTool(): ToolDefinition<
@@ -42,13 +28,13 @@ export function createEvalTool(): ToolDefinition<
42
28
  - Each call is a fresh subprocess — no state persists between calls
43
29
  - 30-second timeout; press Escape to cancel a running evaluation
44
30
  - Working directory is the agent's current working directory (like bash)
45
- - Use nodeModulesPath to resolve require() from a project directory
46
- - Use pythonPath to target a virtual environment`,
31
+ - Set pythonPath or nodeModulesPath in ~/.pi/agent/pi-eval.json (global) or .pi/pi-eval.json (project) to configure the runtime for all eval calls`,
47
32
  promptSnippet:
48
33
  "Execute JavaScript or Python code in an isolated subprocess",
49
34
  parameters: Parameters,
50
35
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
51
- const { language, code, pythonPath, nodeModulesPath } = params;
36
+ const { language, code } = params;
37
+ const config = loadConfig(ctx.cwd);
52
38
 
53
39
  // Validate language (belt-and-suspenders: TypeBox schema already constrains it,
54
40
  // but a raw API call could bypass validation)
@@ -69,13 +55,19 @@ export function createEvalTool(): ToolDefinition<
69
55
  }
70
56
 
71
57
  if (language === "python") {
72
- return executePython(code, pythonPath, signal, timeoutSignal, ctx.cwd);
58
+ return executePython(
59
+ code,
60
+ config.pythonPath,
61
+ signal,
62
+ timeoutSignal,
63
+ ctx.cwd,
64
+ );
73
65
  }
74
66
 
75
67
  // ── JavaScript execution via temp file + node subprocess ──
76
68
  return executeJavaScript(
77
69
  code,
78
- nodeModulesPath,
70
+ config.nodeModulesPath,
79
71
  signal,
80
72
  timeoutSignal,
81
73
  ctx.cwd,