little-coder 1.4.1 → 1.4.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.
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI, Theme } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent";
2
2
  import { readFileSync } from "node:fs";
3
3
  import { basename, dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
@@ -16,7 +16,7 @@ import { fileURLToPath } from "node:url";
16
16
  // override quietStartup and see the resource list.
17
17
  //
18
18
  // Implementation pattern follows the bundled pi example at
19
- // `node_modules/@mariozechner/pi-coding-agent/examples/extensions/custom-header.ts` —
19
+ // `node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-header.ts` —
20
20
  // the factory returns a duck-typed Component (`render(width): string[]` +
21
21
  // `invalidate()`), so no deep imports from pi-tui are needed.
22
22
 
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { Type } from "@sinclair/typebox";
3
3
 
4
4
  // Port of local/tools/browser.py. Playwright-powered Browser* tools with
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { getSessionStore } from "../evidence/index.ts";
3
3
 
4
4
  // Post-turn pruning of BrowserExtract tool-result messages.
@@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest";
2
2
  import { buildPlaceholder, pruneMessages } from "./index.ts";
3
3
 
4
4
  // Canned message shapes mirror pi's AgentMessage / ToolResultMessage.
5
- // See node_modules/@mariozechner/pi-ai/dist/types.d.ts for the real types.
5
+ // See node_modules/@earendil-works/pi-ai/dist/types.d.ts for the real types.
6
6
 
7
7
  function userMsg(text: string) {
8
8
  return { role: "user", content: text };
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { homedir } from "node:os";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { Type } from "@sinclair/typebox";
3
3
  import { randomBytes } from "node:crypto";
4
4
 
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { getSessionStore } from "../evidence/index.ts";
3
3
 
4
4
  // Port of compaction.py's Evidence-preservation contract.
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { Type } from "@sinclair/typebox";
3
3
  import { glob as globSync } from "node:fs/promises";
4
4
 
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  // Pre-cap finalize-warn: when the agent has WARN_REMAINING turns left
4
4
  // (this turn included), inject a follow-up user message reminding it to
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  export default function (pi: ExtensionAPI) {
4
4
  pi.on("session_start", async (_event, ctx) => {
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { readdirSync, readFileSync, existsSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
@@ -1,6 +1,6 @@
1
1
  import { dirname, resolve } from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
3
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
4
  import { loadProviders } from "./config.ts";
5
5
 
6
6
  // Data-driven provider registration. Reads:
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { parseTextToolCalls } from "./parser.ts";
3
3
 
4
4
  // Detects malformed/fenced tool calls in assistant text and nudges the model
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  // Port of tools.py::_SAFE_PREFIXES + agent.py::_check_permission. Bash
4
4
  // commands not matching the whitelist are blocked in "auto" mode. In
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { assessResponse, buildCorrectionMessage, type ToolCall } from "./quality.ts";
3
3
 
4
4
  // Port of local/quality.py. Hooks turn_end, inspects the assistant message
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { Type } from "@sinclair/typebox";
3
3
  import { execSync } from "node:child_process";
4
4
  import { formatOutput, DEFAULT_TIMEOUT } from "./helpers.ts";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { readdirSync, readFileSync, existsSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  // Port of the thinking-budget cap + partial-trace reuse logic from
4
4
  // providers.py. little-coder's Python implementation aborts the stream
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  // Port of agent.py's _allowed_tools gate. When LITTLE_CODER_ALLOWED_TOOLS
4
4
  // is set (comma-separated), any tool_call not in the list is blocked with
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  // Port of agent.py's max_turns early-break. Counts turn_start events per
4
4
  // agent_start span; when the count exceeds LITTLE_CODER_MAX_TURNS (or the
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { Type } from "@sinclair/typebox";
3
3
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
4
4
  import { dirname, isAbsolute, join } from "node:path";
package/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  All notable changes to little-coder are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and little-coder's public interface (CLI, providers, tools, skills) follows semver starting at `v0.0.1` post-rename.
4
4
 
5
+ ## [v1.4.2] — 2026-05-19
6
+
7
+ Bundled-pi maintenance release. Closes [#22](https://github.com/itayinbarr/little-coder/issues/22), [#23](https://github.com/itayinbarr/little-coder/issues/23), [#25](https://github.com/itayinbarr/little-coder/issues/25). The pi runtime moves from `@mariozechner/pi-coding-agent@^0.68.1` to `@earendil-works/pi-coding-agent@^0.75.3` — same author, same project, new npm scope — which makes the deprecation warnings disappear, pulls in pi's recent Windows / undici / cmd-shim fixes, and (because pi 0.75 raised its floor) bumps the supported Node range to ≥ 22.19. No CLI flag, settings, extension API, or skill-pack changes.
8
+
9
+ ### Fixed
10
+ - **`npm install -g little-coder` no longer emits `@mariozechner/pi-*` deprecation warnings ([#25](https://github.com/itayinbarr/little-coder/issues/25)).** Upstream pi published the new scope as `@earendil-works/pi-coding-agent` (the `@mariozechner/*` packages remain on npm only to print the migration notice). The little-coder `package.json` dependency entry, all 21 extension import statements under `.pi/extensions/*/index.ts`, the retention-test doc comment, and the README attribution have been migrated. The public `ExtensionAPI`, `Theme`, hook event names (`before_agent_start`, `context`, `before_provider_request`, `tool_call`, `tool_result`, `turn_end`, `session_compact`, `session_start`), `pi.ui.setHeader()` / `pi.ui.setTitle()`, `registerProvider()`, and CLI flags (`--no-context-files`, `--no-extensions`, `--system-prompt`, `--extension`, `--mode rpc`, `--list-models`, `--verbose`, `--offline`) all keep their previous signatures — the rename is purely the npm scope. Full `npm run typecheck` + 152-test vitest suite passes against the new scope.
11
+ - **Windows startup no longer fails with `'C:\Program' is not recognized as an internal or external command` ([#23](https://github.com/itayinbarr/little-coder/issues/23)).** Root cause: `bin/little-coder.mjs` was invoking `node_modules/.bin/pi.cmd` via `cmd.exe /c …`. When npm's prefix or Node's install path contained spaces (the default Windows location is `C:\Program Files\nodejs\`), the chain of nested `.cmd` shims could tokenize on the first space and execute `C:\Program` as a command name. The launcher now resolves pi's JS entry by reading `node_modules/@earendil-works/pi-coding-agent/package.json#bin.pi` and spawns `process.execPath` (the same Node that's already running) with that absolute path as an argv element. Node's `child_process.spawn` handles Windows argv quoting itself, so there is no shell tokenization at any layer — and the same spawn line works on Linux, macOS, and Windows (the previous `isWindows ? cmd.exe : piBin` branch is gone). This also picks up pi 0.75.2's own Windows fixes for cross-spawn / npm-family commands.
12
+ - **Node version requirement bumped to ≥ 22.19.0 ([#22](https://github.com/itayinbarr/little-coder/issues/22)).** The `glob`-cannot-be-used error users hit on Node 20.x came from the bundled pi runtime depending on `glob@^13`, which itself requires Node ≥ 20.19 / 22 to run correctly. Upstream pi 0.75.0 raised its hard minimum to 22.19.0, so the bundled-pi update forces this floor onto us regardless. The launcher's `MIN_NODE` preflight, `package.json#engines.node`, `install.sh`'s version check, and the README install / troubleshooting prose are all moved together. Easiest fix: `nvm install 22 && nvm use 22`.
13
+
14
+ ### Notes for upgraders
15
+ - **You must be on Node ≥ 22.19.0 to upgrade.** If `node --version` is below that, `npm install -g little-coder@1.4.2` will still install but the launcher's preflight will refuse to start pi and print the nvm hint. `npm install -g little-coder@1.4.1` keeps working on Node 20.6+ if you genuinely cannot move yet.
16
+ - The model list, `models.json` shape, `.pi/settings.json` keys (`quietStartup`, per-model context/thinking-budget/temperature profiles, benchmark_overrides), and skill-pack are untouched. Existing `LITTLE_CODER_BASH_ALLOW`, `LITTLE_CODER_PERMISSION_MODE`, `LITTLE_CODER_MODELS_FILE`, `LLAMACPP_BASE_URL` / `OLLAMA_BASE_URL` / `LMSTUDIO_BASE_URL` / `*_API_KEY` env vars all keep their meaning.
17
+ - If you'd hand-written an extension under your local `.pi/extensions/` that imports `from "@mariozechner/pi-coding-agent"`, change the import to `from "@earendil-works/pi-coding-agent"` and re-run. The old scope's last published version was 0.73.1 — it works against an installed `@earendil-works/pi-coding-agent` only via the legacy `@sinclair/typebox` alias that pi 0.69+ keeps for compatibility.
18
+
19
+ ---
20
+
5
21
  ## [v1.4.1] — 2026-05-16
6
22
 
7
23
  Wire fix for the v1.4.0 startup rebrand. The `[Extensions]` block was still showing for users running from outside the repo root.
package/README.md CHANGED
@@ -16,7 +16,7 @@ If you've never used pi, it's useful to skim [pi.dev](https://pi.dev) first —
16
16
 
17
17
  ## Install
18
18
 
19
- One-line install (Node.js 20.6+ required):
19
+ One-line install (Node.js 22.19+ required):
20
20
 
21
21
  ```bash
22
22
  curl -fsSL https://raw.githubusercontent.com/itayinbarr/little-coder/main/install.sh | bash
@@ -36,7 +36,7 @@ bun add -g little-coder
36
36
 
37
37
  That's the whole install. No clone, no `npm install` in a workspace, no PATH fiddling. `little-coder` is now on your PATH and works from any directory.
38
38
 
39
- > **Note for `bun add -g` users.** The launcher (`bin/little-coder.mjs`) is a Node.js script with `#!/usr/bin/env node` at the top, so Node ≥ 20.6 still has to be on your PATH for the binary to start — bun is fine for installing/updating the package, but the runtime is Node. If you want a fully node-less setup, replace the shebang in `$(bun pm bin -g)/little-coder` with `#!/usr/bin/env bun`.
39
+ > **Note for `bun add -g` users.** The launcher (`bin/little-coder.mjs`) is a Node.js script with `#!/usr/bin/env node` at the top, so Node ≥ 22.19 still has to be on your PATH for the binary to start — bun is fine for installing/updating the package, but the runtime is Node. If you want a fully node-less setup, replace the shebang in `$(bun pm bin -g)/little-coder` with `#!/usr/bin/env bun`.
40
40
 
41
41
  ## Run
42
42
 
@@ -264,7 +264,7 @@ That spans short coding exercises (Polyglot), interactive shell-bound tasks (Ter
264
264
 
265
265
  **Extension load failures on startup** — run `little-coder --list-models --verbose`; extension errors surface there. If the install looks corrupt: `npm uninstall -g little-coder && npm install -g little-coder`.
266
266
 
267
- **Node version too old** — little-coder needs Node ≥ 20.6.0. Check with `node --version`. Easiest fix: `nvm install 20 && nvm use 20`.
267
+ **Node version too old** — little-coder needs Node ≥ 22.19.0 (matching the minimum of the bundled `@earendil-works/pi-coding-agent` v0.75+). Check with `node --version`. Easiest fix: `nvm install 22 && nvm use 22`.
268
268
 
269
269
  ---
270
270
 
@@ -367,7 +367,7 @@ The paper ran `ollama/qwen3.5` through the Python little-coder at commit **`1d62
367
367
 
368
368
  little-coder v0.0.x was a derivative work of [CheetahClaws / ClawSpring](https://github.com/SafeRL-Lab/clawspring) by SafeRL-Lab, Apache 2.0. That upstream provided the Python agent substrate, tool system, multi-provider support, and REPL.
369
369
 
370
- little-coder v0.1.0+ replaces that substrate with **[pi](https://github.com/badlogic/pi-mono)** (`@mariozechner/pi-coding-agent`) by Mario Zechner Apache 2.0 / MIT. pi provides the agent loop, provider abstraction, TUI, and extension model. little-coder rebuilds its small-model adaptations on top of pi as extensions.
370
+ little-coder v0.1.0+ replaces that substrate with **[pi](https://pi.dev)** by Mario Zechner — Apache 2.0 / MIT. The npm package was renamed from `@mariozechner/pi-coding-agent` to `@earendil-works/pi-coding-agent` in upstream's 0.74 release; little-coder v1.4.2+ ships with the new package. pi provides the agent loop, provider abstraction, TUI, and extension model. little-coder rebuilds its small-model adaptations on top of pi as extensions.
371
371
 
372
372
  All little-coder-specific mechanisms — Write-vs-Edit invariant, skill / knowledge injection, thinking-budget cap, output-parser, quality-monitor, per-model profiles, per-benchmark overrides, ShellSession / Browser / Evidence tool families, evidence-aware compaction — are preserved across versions.
373
373
 
@@ -17,8 +17,8 @@ import { dirname, join, resolve } from "node:path";
17
17
  import { fileURLToPath } from "node:url";
18
18
  import { checkForUpdate } from "./update-check.mjs";
19
19
 
20
- // ---- 1. Node version preflight (>= 20.6.0, matching pi.dev) ----
21
- const MIN_NODE = [20, 6, 0];
20
+ // ---- 1. Node version preflight (>= 22.19.0, matching pi.dev) ----
21
+ const MIN_NODE = [22, 19, 0];
22
22
  const cur = process.versions.node.split(".").map((n) => parseInt(n, 10));
23
23
  const tooOld =
24
24
  cur[0] < MIN_NODE[0] ||
@@ -27,7 +27,7 @@ const tooOld =
27
27
  if (tooOld) {
28
28
  console.error(
29
29
  `little-coder requires Node.js >= ${MIN_NODE.join(".")} (you have ${process.versions.node}).\n` +
30
- `Install a newer Node from https://nodejs.org or via nvm: 'nvm install 20'.`,
30
+ `Install a newer Node from https://nodejs.org or via nvm: 'nvm install 22'.`,
31
31
  );
32
32
  process.exit(1);
33
33
  }
@@ -36,13 +36,38 @@ if (tooOld) {
36
36
  const here = dirname(fileURLToPath(import.meta.url));
37
37
  const pkgRoot = resolve(here, "..");
38
38
 
39
- // ---- 3. Resolve the bundled pi binary ----
40
- const isWindows = process.platform === "win32";
41
- const piBinBase = join(pkgRoot, "node_modules", ".bin", "pi");
42
- const piBin = isWindows && existsSync(`${piBinBase}.cmd`) ? `${piBinBase}.cmd` : piBinBase;
43
- if (!existsSync(piBin)) {
39
+ // ---- 3. Resolve the bundled pi CLI entry point ----
40
+ // We invoke pi's JS entry directly under the current Node binary instead of
41
+ // the `node_modules/.bin/pi` shim. Two reasons:
42
+ // 1. On Windows, `.bin/pi.cmd` is an npm-generated batch shim. When it (or
43
+ // anything it transitively invokes) is launched from a path containing
44
+ // spaces — most notably the default Node install location
45
+ // `C:\Program Files\nodejs\` — cmd's whitespace tokenization can split
46
+ // the path at the first space and produce errors like
47
+ // `'C:\Program' is not recognized as an internal or external command`
48
+ // (see issue #23). Spawning `process.execPath` with the resolved cli.js
49
+ // path as an argv element sidesteps cmd entirely — Node's spawn handles
50
+ // Windows argv quoting itself.
51
+ // 2. We no longer need a separate `cmd.exe /c …` branch, so the same
52
+ // spawn path works identically on Linux, macOS, and Windows.
53
+ const piPkgRoot = join(pkgRoot, "node_modules", "@earendil-works", "pi-coding-agent");
54
+ let piEntry;
55
+ try {
56
+ const piPkgJson = JSON.parse(readFileSync(join(piPkgRoot, "package.json"), "utf-8"));
57
+ const binRel = typeof piPkgJson?.bin === "string" ? piPkgJson.bin : piPkgJson?.bin?.pi;
58
+ if (typeof binRel !== "string") throw new Error("pi package.json has no bin.pi entry");
59
+ piEntry = resolve(piPkgRoot, binRel);
60
+ } catch (err) {
44
61
  console.error(
45
- `little-coder: cannot find pi at ${piBin}.\n` +
62
+ `little-coder: cannot resolve pi cli entry under ${piPkgRoot}.\n` +
63
+ `Underlying error: ${err?.message ?? err}\n` +
64
+ `Try reinstalling: npm install -g little-coder`,
65
+ );
66
+ process.exit(1);
67
+ }
68
+ if (!existsSync(piEntry)) {
69
+ console.error(
70
+ `little-coder: cannot find pi at ${piEntry}.\n` +
46
71
  `Try reinstalling: npm install -g little-coder`,
47
72
  );
48
73
  process.exit(1);
@@ -150,11 +175,11 @@ try {
150
175
  }
151
176
 
152
177
  // ---- 9. Spawn pi in the user's cwd ----
153
- const [spawnCmd, spawnArgs] = isWindows
154
- ? ["cmd.exe", ["/c", piBin, ...piArgs]]
155
- : [piBin, piArgs];
156
-
157
- const child = spawn(spawnCmd, spawnArgs, {
178
+ // `process.execPath` is the same Node binary that's running this launcher, so
179
+ // pi inherits the exact runtime that already passed our >= 22.19.0 preflight.
180
+ // Passing piEntry as an argv element (not a shell string) avoids any
181
+ // shell-injection / space-in-path classes on every platform.
182
+ const child = spawn(process.execPath, [piEntry, ...piArgs], {
158
183
  stdio: "inherit",
159
184
  cwd: process.cwd(),
160
185
  env: process.env,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "little-coder",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "A pi-based coding agent optimized for small local language models. Reproduces the whitepaper's scaffold-model-fit adaptations as pi extensions.",
5
5
  "homepage": "https://github.com/itayinbarr/little-coder",
6
6
  "repository": {
@@ -14,7 +14,7 @@
14
14
  "little-coder": "bin/little-coder.mjs"
15
15
  },
16
16
  "engines": {
17
- "node": ">=20.6.0"
17
+ "node": ">=22.19.0"
18
18
  },
19
19
  "files": [
20
20
  "bin/",
@@ -35,7 +35,7 @@
35
35
  "typecheck": "tsc --noEmit"
36
36
  },
37
37
  "dependencies": {
38
- "@mariozechner/pi-coding-agent": "^0.68.1",
38
+ "@earendil-works/pi-coding-agent": "^0.75.3",
39
39
  "@sinclair/typebox": "^0.34.49",
40
40
  "playwright": "^1.59.1"
41
41
  },