little-coder 1.4.1 → 1.4.3
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/.pi/extensions/benchmark-profiles/index.ts +1 -1
- package/.pi/extensions/branding/index.ts +2 -2
- package/.pi/extensions/browser/index.ts +1 -1
- package/.pi/extensions/browser-extract-retention/index.ts +1 -1
- package/.pi/extensions/browser-extract-retention/retention.test.ts +1 -1
- package/.pi/extensions/checkpoint/index.ts +1 -1
- package/.pi/extensions/evidence/index.ts +1 -1
- package/.pi/extensions/evidence-compact/index.ts +1 -1
- package/.pi/extensions/extra-tools/index.ts +1 -1
- package/.pi/extensions/finalize-warn/index.ts +1 -1
- package/.pi/extensions/hello/index.ts +1 -1
- package/.pi/extensions/knowledge-inject/index.ts +1 -1
- package/.pi/extensions/llama-cpp-provider/index.ts +1 -1
- package/.pi/extensions/output-parser/index.ts +1 -1
- package/.pi/extensions/permission-gate/index.ts +1 -1
- package/.pi/extensions/quality-monitor/index.ts +1 -1
- package/.pi/extensions/shell-session/index.ts +1 -1
- package/.pi/extensions/skill-inject/index.ts +1 -1
- package/.pi/extensions/thinking-budget/index.ts +1 -1
- package/.pi/extensions/tool-gating/index.ts +1 -1
- package/.pi/extensions/turn-cap/index.ts +1 -1
- package/.pi/extensions/write-guard/index.ts +1 -1
- package/CHANGELOG.md +31 -0
- package/README.md +4 -4
- package/bin/little-coder.mjs +87 -23
- package/package.json +7 -3
- package/vendor/node-domexception/index.js +9 -0
- package/vendor/node-domexception/package.json +10 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@
|
|
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 "@
|
|
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/@
|
|
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
|
|
|
@@ -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/@
|
|
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 "@
|
|
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 "@
|
|
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 "@
|
|
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 "@
|
|
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 "@
|
|
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 "@
|
|
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 "@
|
|
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 "@
|
|
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 "@
|
|
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 "@
|
|
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 "@
|
|
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 "@
|
|
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,37 @@
|
|
|
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.3] — 2026-05-19
|
|
6
|
+
|
|
7
|
+
Follow-up to v1.4.2: clean up two cosmetic regressions that the @earendil-works scope migration surfaced.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **Pi's `What's New` block no longer appears inside little-coder's TUI after a version bump.** Root cause: pi's interactive mode reads its own bundled `CHANGELOG.md` on startup and renders every entry strictly newer than the `lastChangelogVersion` field in `~/.pi/agent/settings.json` (`interactive-mode.js:getChangelogForDisplay`). v1.4.2 jumped the bundled pi from 0.68.1 to 0.75.3, so users who had previously launched any older little-coder saw pi's full 0.68 → 0.75 upstream changelog dumped *underneath* little-coder's own startup banner. That's wrong because little-coder is the surface and pi is the substrate — the chrome above shouldn't suddenly start advertising the substrate's release notes. The launcher (`bin/little-coder.mjs`) now pre-stamps `lastChangelogVersion` to the currently bundled pi version (resolved from `node_modules/@earendil-works/pi-coding-agent/package.json#version`, the same file we already read to find pi's cli.js, so there's no second source of truth) *before* pi starts. Pi then sees "user already saw this changelog" and the block never renders. The merge into `~/.pi/agent/settings.json` is non-destructive — `quietStartup: true` and every other existing key are preserved. Users who genuinely want pi's upstream changelog can still pull it up with `/changelog` inside the TUI.
|
|
11
|
+
- **`npm install -g little-coder` no longer prints `node-domexception@1.0.0` deprecation warning.** Root cause: a 5-hop transitive — `@earendil-works/pi-ai` → `@google/genai` → `google-auth-library` → `gaxios` → `node-fetch@3` → `fetch-blob@3` → `node-domexception@1.0.0`. The `node-domexception` package is just a 16-line shim that sets `globalThis.DOMException` when undefined, and native `DOMException` has been built into Node since 18 — so on our `Node >= 22.19` floor, the entire shim is dead code. Replaced it via `package.json#overrides` pointing at a bundled stub at `./vendor/node-domexception/` that exports `module.exports = globalThis.DOMException` directly. The stub ships in the npm tarball (`files` array now includes `vendor/`). Since npm's `overrides` field is honored when little-coder is the install root (which it is for `npm install -g little-coder`), the deprecated upstream package never reaches the user's tree, and npm prints no warning. Functional behavior is identical because the only call site (`fetch-blob/from.js:import DOMException from 'node-domexception'`) sees the same `globalThis.DOMException` it would have gotten from the upstream shim.
|
|
12
|
+
|
|
13
|
+
### Notes for upgraders
|
|
14
|
+
- The bundled stub lives at `vendor/node-domexception/` inside the published package — it's listed under `files` in `package.json`. If you'd added your own `overrides` field that touches `node-domexception` in a hand-rolled fork of little-coder, our entry will take precedence when you publish; in the unlikely case that breaks something for you, override it back in your fork's root `package.json`.
|
|
15
|
+
- The `lastChangelogVersion` pre-stamp is one-directional: it writes the *currently bundled* pi version into settings on every launch. If you'd like to see pi's upstream changelog for a future bump, `/changelog` inside the TUI is the unconditional path — it doesn't consult `lastChangelogVersion`.
|
|
16
|
+
- No CLI flag, models.json shape, skill-pack, extension API, or per-model profile changes. Little-coder's own startup banner, tagline, and keybind hints (the branding extension at `.pi/extensions/branding/`) are byte-for-byte unchanged from v1.4.2.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## [v1.4.2] — 2026-05-19
|
|
21
|
+
|
|
22
|
+
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.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- **`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.
|
|
26
|
+
- **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.
|
|
27
|
+
- **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`.
|
|
28
|
+
|
|
29
|
+
### Notes for upgraders
|
|
30
|
+
- **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.
|
|
31
|
+
- 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.
|
|
32
|
+
- 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.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
5
36
|
## [v1.4.1] — 2026-05-16
|
|
6
37
|
|
|
7
38
|
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
|
|
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 ≥
|
|
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 ≥
|
|
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://
|
|
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
|
|
package/bin/little-coder.mjs
CHANGED
|
@@ -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 (>=
|
|
21
|
-
const MIN_NODE = [
|
|
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
|
|
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
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
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);
|
|
@@ -106,15 +131,30 @@ if (process.env.PI_SKIP_VERSION_CHECK === undefined) {
|
|
|
106
131
|
process.env.PI_SKIP_VERSION_CHECK = "1";
|
|
107
132
|
}
|
|
108
133
|
|
|
109
|
-
// ---- 8. Force pi's global quietStartup
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
134
|
+
// ---- 8. Force pi's global quietStartup + pin lastChangelogVersion ----
|
|
135
|
+
// Two non-destructive merges into ~/.pi/agent/settings.json (or the dir pointed
|
|
136
|
+
// to by PI_CODING_AGENT_DIR):
|
|
137
|
+
//
|
|
138
|
+
// 1. quietStartup: true
|
|
139
|
+
// Pi's interactive mode otherwise dumps an [Extensions] / [Skills] /
|
|
140
|
+
// [Prompts] inventory on every launch. Pi reads global settings from
|
|
141
|
+
// <agentDir>/settings.json — NOT from our npm-installed package dir —
|
|
142
|
+
// so our shipped .pi/settings.json doesn't reach it. To see the
|
|
143
|
+
// inventory anyway, run `little-coder --verbose`.
|
|
144
|
+
//
|
|
145
|
+
// 2. lastChangelogVersion: <currently installed pi version>
|
|
146
|
+
// Pi reads its own bundled CHANGELOG.md on startup and renders a
|
|
147
|
+
// "What's New" block for every entry strictly newer than this stored
|
|
148
|
+
// version (interactive-mode.js:getChangelogForDisplay). That makes pi's
|
|
149
|
+
// upstream changelog show up inside little-coder's TUI every time we
|
|
150
|
+
// bump the bundled pi dep — which is jarring because little-coder is
|
|
151
|
+
// the surface, not pi. We pre-stamp this field to the version we just
|
|
152
|
+
// bundled BEFORE pi starts, so pi sees "user already saw this", and
|
|
153
|
+
// the block never renders. Users who genuinely want to read pi's
|
|
154
|
+
// upstream changelog can still do so with `/changelog` inside the TUI.
|
|
155
|
+
//
|
|
156
|
+
// Existing keys are preserved. We only write when the desired value differs
|
|
157
|
+
// from what's already on disk, so this is a no-op on warm launches.
|
|
118
158
|
try {
|
|
119
159
|
const agentDirEnv = process.env.PI_CODING_AGENT_DIR;
|
|
120
160
|
let agentDir;
|
|
@@ -139,8 +179,32 @@ try {
|
|
|
139
179
|
globalSettings = {};
|
|
140
180
|
}
|
|
141
181
|
}
|
|
182
|
+
|
|
183
|
+
// Read the bundled pi version. We resolve via the same package.json we used
|
|
184
|
+
// to find piEntry, so this stays consistent with whichever pi we actually
|
|
185
|
+
// spawn — no second source of truth.
|
|
186
|
+
let bundledPiVersion;
|
|
187
|
+
try {
|
|
188
|
+
const piPkgJson = JSON.parse(
|
|
189
|
+
readFileSync(join(piPkgRoot, "package.json"), "utf-8"),
|
|
190
|
+
);
|
|
191
|
+
if (typeof piPkgJson?.version === "string") bundledPiVersion = piPkgJson.version;
|
|
192
|
+
} catch {
|
|
193
|
+
// If we can't read pi's version, fall back to leaving lastChangelogVersion
|
|
194
|
+
// alone — pi will then show its own changelog on the next launch. Better
|
|
195
|
+
// than writing garbage into the user's settings.
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let mutated = false;
|
|
142
199
|
if (globalSettings.quietStartup !== true) {
|
|
143
200
|
globalSettings.quietStartup = true;
|
|
201
|
+
mutated = true;
|
|
202
|
+
}
|
|
203
|
+
if (bundledPiVersion && globalSettings.lastChangelogVersion !== bundledPiVersion) {
|
|
204
|
+
globalSettings.lastChangelogVersion = bundledPiVersion;
|
|
205
|
+
mutated = true;
|
|
206
|
+
}
|
|
207
|
+
if (mutated) {
|
|
144
208
|
writeFileSync(globalSettingsPath, JSON.stringify(globalSettings, null, 2));
|
|
145
209
|
}
|
|
146
210
|
} catch {
|
|
@@ -150,11 +214,11 @@ try {
|
|
|
150
214
|
}
|
|
151
215
|
|
|
152
216
|
// ---- 9. Spawn pi in the user's cwd ----
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const child = spawn(
|
|
217
|
+
// `process.execPath` is the same Node binary that's running this launcher, so
|
|
218
|
+
// pi inherits the exact runtime that already passed our >= 22.19.0 preflight.
|
|
219
|
+
// Passing piEntry as an argv element (not a shell string) avoids any
|
|
220
|
+
// shell-injection / space-in-path classes on every platform.
|
|
221
|
+
const child = spawn(process.execPath, [piEntry, ...piArgs], {
|
|
158
222
|
stdio: "inherit",
|
|
159
223
|
cwd: process.cwd(),
|
|
160
224
|
env: process.env,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "little-coder",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.3",
|
|
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": ">=
|
|
17
|
+
"node": ">=22.19.0"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"bin/",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
".pi/extensions/",
|
|
24
24
|
".pi/settings.json",
|
|
25
25
|
"models.json",
|
|
26
|
+
"vendor/",
|
|
26
27
|
"LICENSE",
|
|
27
28
|
"NOTICE",
|
|
28
29
|
"README.md",
|
|
@@ -35,10 +36,13 @@
|
|
|
35
36
|
"typecheck": "tsc --noEmit"
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
|
38
|
-
"@
|
|
39
|
+
"@earendil-works/pi-coding-agent": "^0.75.3",
|
|
39
40
|
"@sinclair/typebox": "^0.34.49",
|
|
40
41
|
"playwright": "^1.59.1"
|
|
41
42
|
},
|
|
43
|
+
"overrides": {
|
|
44
|
+
"node-domexception": "file:./vendor/node-domexception"
|
|
45
|
+
},
|
|
42
46
|
"devDependencies": {
|
|
43
47
|
"typescript": "^5.6.0",
|
|
44
48
|
"vitest": "^2.1.0"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Drop-in replacement for the deprecated `node-domexception@1.0.0` shim.
|
|
2
|
+
// Bundled with little-coder; wired in via `package.json#overrides` so the
|
|
3
|
+
// transitive `fetch-blob -> node-domexception` chain (pulled in via
|
|
4
|
+
// @earendil-works/pi-ai -> @google/genai -> google-auth-library -> gaxios ->
|
|
5
|
+
// node-fetch -> fetch-blob) doesn't emit a deprecation warning during
|
|
6
|
+
// `npm install -g little-coder`. Native `globalThis.DOMException` has been
|
|
7
|
+
// available since Node 18, and little-coder requires Node >= 22.19, so this
|
|
8
|
+
// is always defined at import time.
|
|
9
|
+
module.exports = globalThis.DOMException;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "node-domexception",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Bundled with little-coder. Returns Node's native globalThis.DOMException (built-in since Node 18). Replaces the deprecated upstream node-domexception@1.0.0 via npm overrides so `npm install -g little-coder` no longer prints its deprecation warning. We pin engines.node to >= 18 because that's where native DOMException landed; the launcher's own preflight enforces >= 22.19, so this is satisfied transitively.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=18.0.0"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT"
|
|
10
|
+
}
|