little-coder 1.3.0 → 1.4.1

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.
@@ -0,0 +1,93 @@
1
+ import type { ExtensionAPI, Theme } from "@mariozechner/pi-coding-agent";
2
+ import { readFileSync } from "node:fs";
3
+ import { basename, dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ // Replace pi's built-in startup header + terminal title with little-coder
7
+ // branding. The interactive TUI's "pi vX.Y.Z" logo, the "Pi can explain its
8
+ // own features..." onboarding line, and the "π - <cwd>" terminal title all
9
+ // come from pi's APP_NAME / built-in header; this extension swaps them for
10
+ // little-coder's own identity using the public ExtensionUIContext hooks.
11
+ //
12
+ // Pairs with `.pi/settings.json` setting `"quietStartup": true`, which
13
+ // suppresses pi's built-in header AND the loaded-resources dump (the long
14
+ // list of extension paths, skills, prompts, themes that used to flood the
15
+ // screen on launch). Power users can still run `little-coder --verbose` to
16
+ // override quietStartup and see the resource list.
17
+ //
18
+ // Implementation pattern follows the bundled pi example at
19
+ // `node_modules/@mariozechner/pi-coding-agent/examples/extensions/custom-header.ts` —
20
+ // the factory returns a duck-typed Component (`render(width): string[]` +
21
+ // `invalidate()`), so no deep imports from pi-tui are needed.
22
+
23
+ const TAGLINE = "A coding agent tuned for small local models";
24
+
25
+ function readVersion(): string {
26
+ // .pi/extensions/branding/index.ts → up 3 → package root (where package.json lives).
27
+ // The same path math works in the local checkout (loaded via tsx) and in the
28
+ // installed npm package layout (node_modules/little-coder/.pi/extensions/branding/).
29
+ try {
30
+ const here = dirname(fileURLToPath(import.meta.url));
31
+ const pkgPath = join(here, "..", "..", "..", "package.json");
32
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
33
+ if (typeof pkg?.version === "string" && pkg.version.length > 0) return pkg.version;
34
+ } catch {
35
+ // best-effort; fall through
36
+ }
37
+ return "0.0.0";
38
+ }
39
+
40
+ const VERSION = readVersion();
41
+
42
+ function buildHeader(theme: Theme): string[] {
43
+ const logo =
44
+ theme.bold(theme.fg("accent", "little-coder")) +
45
+ theme.fg("dim", ` v${VERSION}`);
46
+ const tagline = theme.fg("muted", TAGLINE);
47
+ const dim = (s: string) => theme.fg("dim", s);
48
+ const sep = theme.fg("muted", " · ");
49
+ const hints = [
50
+ `${dim("esc")} interrupt`,
51
+ `${dim("ctrl-l/ctrl-c")} clear/exit`,
52
+ `${dim("/")} commands`,
53
+ `${dim("!")} bash`,
54
+ `${dim("ctrl-r")} more`,
55
+ ].join(sep);
56
+ return ["", logo, tagline, "", hints, ""];
57
+ }
58
+
59
+ function setTitleForCwd(setTitle: (t: string) => void, cwd: string): void {
60
+ setTitle(`little-coder - ${basename(cwd)}`);
61
+ }
62
+
63
+ export default function (pi: ExtensionAPI) {
64
+ // session_start fires on initial load AND on every session switch.
65
+ // Pi's updateTerminalTitle() runs in init() *after* session_start, so our
66
+ // setTitle here gets clobbered back to "π - <cwd>". We reassert the title
67
+ // on turn_start and turn_end too — pi calls updateTerminalTitle at the same
68
+ // points (interactive-mode.js:1179, 1346, 3971), so re-setting on every
69
+ // turn keeps our "little-coder - <cwd>" winning for the duration of a
70
+ // session.
71
+ pi.on("session_start", async (_event, ctx) => {
72
+ if (!ctx.hasUI) return;
73
+
74
+ ctx.ui.setHeader((_tui, theme) => ({
75
+ render(_width: number): string[] {
76
+ return buildHeader(theme);
77
+ },
78
+ invalidate() {},
79
+ }));
80
+
81
+ setTitleForCwd(ctx.ui.setTitle.bind(ctx.ui), ctx.cwd);
82
+ });
83
+
84
+ pi.on("turn_start", async (_event, ctx) => {
85
+ if (!ctx.hasUI) return;
86
+ setTitleForCwd(ctx.ui.setTitle.bind(ctx.ui), ctx.cwd);
87
+ });
88
+
89
+ pi.on("turn_end", async (_event, ctx) => {
90
+ if (!ctx.hasUI) return;
91
+ setTitleForCwd(ctx.ui.setTitle.bind(ctx.ui), ctx.cwd);
92
+ });
93
+ }
package/.pi/settings.json CHANGED
@@ -1,4 +1,5 @@
1
1
  {
2
+ "quietStartup": true,
2
3
  "compaction": { "enabled": true },
3
4
  "retry": { "enabled": true, "maxRetries": 2 },
4
5
  "little_coder": {
package/CHANGELOG.md CHANGED
@@ -2,6 +2,39 @@
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.1] — 2026-05-16
6
+
7
+ Wire fix for the v1.4.0 startup rebrand. The `[Extensions]` block was still showing for users running from outside the repo root.
8
+
9
+ ### Fixed
10
+ - **`quietStartup` is now actually applied for end users.** v1.4.0 set `"quietStartup": true` in our shipped `.pi/settings.json`, but pi reads global settings from `~/.pi/agent/settings.json` (or the dir pointed to by `PI_CODING_AGENT_DIR`) — not from the npm package's internal `.pi/`. So users running little-coder from anywhere outside the repo root still saw pi's full extension/skill/prompt inventory on startup. The launcher (`bin/little-coder.mjs`) now non-destructively merges `quietStartup: true` into the user's actual global pi settings on every launch, preserving any other keys. To see the inventory anyway: `little-coder --verbose`.
11
+ - **Terminal title now reasserts on `turn_start` / `turn_end`.** Pi's `updateTerminalTitle()` fires multiple times during a session (init, provider count update, session-name change) and was clobbering our `setTitle("little-coder - <cwd>")` back to `π - <cwd>`. The branding extension now re-applies the title on every turn boundary, so after the first prompt the title stays correct.
12
+
13
+ ### Notes for upgraders
14
+ - Existing keys in your `~/.pi/agent/settings.json` are preserved. The launcher only writes `quietStartup` if it isn't already `true`. If you'd previously set `"quietStartup": false` deliberately, you'll see the launcher overwrite it back to `true` — set `--verbose` per-invocation to see the inventory without disabling the global default.
15
+ - No CLI flag, skill-pack, or API changes.
16
+
17
+ ---
18
+
19
+ ## [v1.4.0] — 2026-05-16
20
+
21
+ Startup UI rebrand. The TUI's opening frame now reads as **little-coder**, not as pi. Pi remains the substrate; the chrome above it just stops pretending it's the product.
22
+
23
+ ### Added
24
+ - **New `.pi/extensions/branding/` extension.** Calls `pi.ui.setHeader()` and `pi.ui.setTitle()` on every `session_start` event to install a little-coder banner: `little-coder vX.Y.Z` (logo) + `A coding agent tuned for small local models` (tagline, verbatim from the README opening line) + a compact keybinding-hint row. The terminal title goes from `π - <cwd>` to `little-coder - <cwd>`. Implementation pattern follows pi's bundled `examples/extensions/custom-header.ts` — the factory returns a duck-typed Component (`render(width): string[]`), so no deep imports of pi-tui internals are required.
25
+ - **Startup screenshot in README.** A real `docs/assets/startup.svg` captured from a live `little-coder` startup, rendered via [charm.sh `freeze`](https://github.com/charmbracelet/freeze). Embedded near the top of the README so the first thing a visitor sees is the actual product, not a description of it.
26
+
27
+ ### Changed
28
+ - **`.pi/settings.json` now ships `"quietStartup": true`.** This is what suppresses pi's built-in loaded-resources block — the long list of extension paths, skills, prompts, themes that previously flooded the screen on every launch. Power users who want the inventory back can pass `little-coder --verbose`, which sets pi's `verbose: true` and overrides `quietStartup`.
29
+ - **Pi's "Pi can explain its own features..." onboarding string is gone.** The branding extension's `setHeader` replaces pi's built-in header entirely, so the line never renders.
30
+
31
+ ### Notes for upgraders
32
+ - No API, settings, or skill-pack breaks. CLI flags unchanged.
33
+ - If you'd customized pi's startup output via your own `models.json` / `.pi/settings.json` override, your changes still apply — the only new top-level key in shipped `.pi/settings.json` is `quietStartup`, and pi's override semantics preserve per-key user values.
34
+ - To restore the original pi-style startup (the `pi vX.Y.Z` logo and the loaded-resources list), run `little-coder --verbose`. There's no way to disable the branding extension from the user side short of editing the installed package, but the rebrand is purely the startup frame — no functional difference.
35
+
36
+ ---
37
+
5
38
  ## [v1.3.0] — 2026-05-16
6
39
 
7
40
  First functional release of Phase 2 (iterative improvement on real-world coding tasks). Three concrete sharp edges that surfaced while actually using the Mac → Linux LAN setup, plus a quality-of-life cleanup on the pi update banner. Minor version bump because three of the four changes are new behavior, all backwards-compatible.
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  **A coding agent tuned for small local models, built on top of [pi](https://pi.dev).**
4
4
 
5
+ ![little-coder startup view](docs/assets/startup.svg)
6
+
5
7
  The research story behind all this — why scaffold–model fit matters, how a 9.7 B Qwen beat frontier entries on Aider Polyglot, and what the load-bearing mechanisms actually do — is written up on Substack: **[*Honey, I Shrunk the Coding Agent*](https://open.substack.com/pub/itayinbarr/p/honey-i-shrunk-the-coding-agent)**. Start there if you want the "why"; stay here for the "how".
6
8
 
7
9
  ## How it relates to pi
@@ -290,9 +292,10 @@ The benchmarks harness (`benchmarks/`) is dev-only and not shipped with the npm
290
292
  little-coder/
291
293
  ├── .pi/
292
294
  │ ├── settings.json # per-model profiles + benchmark_overrides (terminal_bench, gaia)
293
- │ └── extensions/ # 20 TypeScript extensions, auto-discovered by pi
295
+ │ └── extensions/ # 21 TypeScript extensions, auto-discovered by pi
296
+ │ ├── branding/ # little-coder startup header + terminal title (replaces pi's built-in)
294
297
  │ ├── llama-cpp-provider/ # data-driven provider registration from models.json — ships llamacpp, ollama, lmstudio (+ user override file)
295
- │ ├── write-guard/ # Write refuses on existing files the whitepaper invariant
298
+ │ ├── write-guard/ # Write refuses on existing files; rewrites root-bare /foo.md paths to cwd
296
299
  │ ├── extra-tools/ # glob, webfetch, websearch (pi ships grep/find)
297
300
  │ ├── skill-inject/ # per-turn tool-skill selection (error > recency > intent)
298
301
  │ ├── knowledge-inject/ # algorithm cheat-sheet scoring (word=1.0, bigram=2.0, threshold=2.0)
@@ -4,7 +4,15 @@
4
4
  // custom extension wired in — works from any working directory.
5
5
 
6
6
  import { spawn } from "node:child_process";
7
- import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
7
+ import {
8
+ existsSync,
9
+ mkdirSync,
10
+ readdirSync,
11
+ readFileSync,
12
+ statSync,
13
+ writeFileSync,
14
+ } from "node:fs";
15
+ import { homedir } from "node:os";
8
16
  import { dirname, join, resolve } from "node:path";
9
17
  import { fileURLToPath } from "node:url";
10
18
  import { checkForUpdate } from "./update-check.mjs";
@@ -98,7 +106,50 @@ if (process.env.PI_SKIP_VERSION_CHECK === undefined) {
98
106
  process.env.PI_SKIP_VERSION_CHECK = "1";
99
107
  }
100
108
 
101
- // ---- 8. Spawn pi in the user's cwd ----
109
+ // ---- 8. Force pi's global quietStartup so the loaded-resources block stays hidden ----
110
+ // Pi's interactive mode dumps an [Extensions] / [Skills] / [Prompts] block on
111
+ // every launch unless `quietStartup: true` is set in its global settings
112
+ // (~/.pi/agent/settings.json). Our shipped .pi/settings.json doesn't reach pi
113
+ // because pi reads from <cwd>/.pi/settings.json (project) or <agentDir>/settings.json
114
+ // (global), neither of which is our npm-installed package dir. So the launcher
115
+ // non-destructively merges quietStartup: true into the user's actual global
116
+ // settings file. Existing keys are preserved. To see the full inventory, run
117
+ // `little-coder --verbose` — pi's verbose flag overrides quietStartup.
118
+ try {
119
+ const agentDirEnv = process.env.PI_CODING_AGENT_DIR;
120
+ let agentDir;
121
+ if (agentDirEnv && agentDirEnv.trim().length > 0) {
122
+ agentDir = agentDirEnv === "~"
123
+ ? homedir()
124
+ : agentDirEnv.startsWith("~/")
125
+ ? homedir() + agentDirEnv.slice(1)
126
+ : agentDirEnv;
127
+ } else {
128
+ agentDir = join(homedir(), ".pi", "agent");
129
+ }
130
+ mkdirSync(agentDir, { recursive: true });
131
+ const globalSettingsPath = join(agentDir, "settings.json");
132
+ let globalSettings = {};
133
+ if (existsSync(globalSettingsPath)) {
134
+ try {
135
+ const parsed = JSON.parse(readFileSync(globalSettingsPath, "utf-8"));
136
+ if (parsed && typeof parsed === "object") globalSettings = parsed;
137
+ } catch {
138
+ // Corrupted JSON — start fresh rather than throw. Pi would have rejected it too.
139
+ globalSettings = {};
140
+ }
141
+ }
142
+ if (globalSettings.quietStartup !== true) {
143
+ globalSettings.quietStartup = true;
144
+ writeFileSync(globalSettingsPath, JSON.stringify(globalSettings, null, 2));
145
+ }
146
+ } catch {
147
+ // Best-effort. If we can't write the settings (read-only HOME, etc.) pi
148
+ // falls back to its built-in defaults — the [Extensions] block will show
149
+ // but everything else still works.
150
+ }
151
+
152
+ // ---- 9. Spawn pi in the user's cwd ----
102
153
  const [spawnCmd, spawnArgs] = isWindows
103
154
  ? ["cmd.exe", ["/c", piBin, ...piArgs]]
104
155
  : [piBin, piArgs];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "little-coder",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
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": {