little-coder 1.9.2 → 1.9.4
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/subagent/index.ts +16 -4
- package/.pi/extensions/subagent/issue-51-repro.test.ts +54 -0
- package/CHANGELOG.md +20 -0
- package/README.md +1 -0
- package/bin/extras.mjs +56 -0
- package/bin/extras.test.mjs +119 -0
- package/bin/little-coder.mjs +16 -0
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type SubCoderResult,
|
|
9
9
|
} from "./spawn.ts";
|
|
10
10
|
import { SubCoderTracker } from "./tracker.ts";
|
|
11
|
+
import { truncateLineToWidth } from "../_shared/width.ts";
|
|
11
12
|
|
|
12
13
|
// The `dispatch` tool: the main little-coder spawns isolated child little-coder
|
|
13
14
|
// sessions ("sub-coders") to research a focused question — they read the repo
|
|
@@ -190,11 +191,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
190
191
|
});
|
|
191
192
|
}
|
|
192
193
|
|
|
193
|
-
/** A minimal pi-tui Component backed by precomputed lines.
|
|
194
|
-
|
|
194
|
+
/** A minimal pi-tui Component backed by precomputed lines.
|
|
195
|
+
*
|
|
196
|
+
* pi paints custom tool-result panels with a 1-char left margin + background-
|
|
197
|
+
* frame fill, so any line we hand back that's wider than `width - 1` overflows
|
|
198
|
+
* the terminal and crashes pi-tui (issue #51 / reopen of #48 — the dispatch
|
|
199
|
+
* renderer fed an unbounded sub-coder report sentence into the panel, and on
|
|
200
|
+
* `--resume` the same renderer paints session history, so the crash recurred
|
|
201
|
+
* even after v1.9.2 capped the *live* tracker). We respect the pi-supplied
|
|
202
|
+
* `width` here and truncate every line to `width - 2`, leaving a 2-char
|
|
203
|
+
* safety margin so wide unicode chars in the report can't sneak past our
|
|
204
|
+
* char-count-based visibleWidth approximation. */
|
|
205
|
+
export function makeComponent(lines: string[]) {
|
|
195
206
|
return {
|
|
196
|
-
render(
|
|
197
|
-
|
|
207
|
+
render(width: number): string[] {
|
|
208
|
+
const cap = Math.max(1, width - 2);
|
|
209
|
+
return lines.map((l) => truncateLineToWidth(l, cap));
|
|
198
210
|
},
|
|
199
211
|
invalidate() {},
|
|
200
212
|
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { makeComponent } from "./index.ts";
|
|
3
|
+
|
|
4
|
+
// Live regression for issue #51 (and the #48 reopen — same root cause from a
|
|
5
|
+
// different code path). pi paints the dispatch tool-result panel with a 1-char
|
|
6
|
+
// background-color left margin + fill; any line we return wider than
|
|
7
|
+
// `width - 1` overflows pi-tui and crashes the session, including on
|
|
8
|
+
// `--resume` because pi re-renders saved tool results from session history.
|
|
9
|
+
//
|
|
10
|
+
// The user's crash log showed a 134-char sub-coder report sentence rendered
|
|
11
|
+
// at terminal width 133 → 135 > 133. This test drives makeComponent at the
|
|
12
|
+
// same width with the same shape and asserts no emitted line exceeds.
|
|
13
|
+
|
|
14
|
+
const stripAnsi = (s: string) => s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
|
|
15
|
+
const visibleWidth = (s: string) => stripAnsi(s).length;
|
|
16
|
+
|
|
17
|
+
describe("issue #51 — dispatch renderResult doesn't overflow", () => {
|
|
18
|
+
it("caps a wide sub-coder report line to fit the pi-supplied width", () => {
|
|
19
|
+
const wideSentence =
|
|
20
|
+
"There is **no `rate_limits` table**. The entire file defines a single class, `ConversationStore`, which manages only one SQLite table:";
|
|
21
|
+
// Sanity: this is the exact 134-char shape from the user's crash log.
|
|
22
|
+
expect(wideSentence.length).toBeGreaterThan(133);
|
|
23
|
+
const comp = makeComponent([
|
|
24
|
+
"✓ Storage schema",
|
|
25
|
+
"**Report: `bot/storage.py` Schema Analysis**",
|
|
26
|
+
"",
|
|
27
|
+
wideSentence,
|
|
28
|
+
"",
|
|
29
|
+
" …",
|
|
30
|
+
"(Ctrl+O to expand)",
|
|
31
|
+
]);
|
|
32
|
+
const out = comp.render(133);
|
|
33
|
+
const max = Math.max(...out.map((l) => visibleWidth(l)));
|
|
34
|
+
expect(max).toBeLessThanOrEqual(133);
|
|
35
|
+
// The truncated wide sentence keeps its prefix verbatim — it's not blanked
|
|
36
|
+
// out, just clipped with an ellipsis so the user can still read most of it.
|
|
37
|
+
const truncated = out[3];
|
|
38
|
+
expect(stripAnsi(truncated).startsWith("There is **no")).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("survives a narrow terminal (40 cols) without throwing", () => {
|
|
42
|
+
const comp = makeComponent([
|
|
43
|
+
"very long content " + "x".repeat(500),
|
|
44
|
+
"another long line " + "y".repeat(200),
|
|
45
|
+
]);
|
|
46
|
+
const out = comp.render(40);
|
|
47
|
+
expect(Math.max(...out.map((l) => visibleWidth(l)))).toBeLessThanOrEqual(40);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("preserves short lines unchanged", () => {
|
|
51
|
+
const comp = makeComponent(["short", "tiny"]);
|
|
52
|
+
expect(comp.render(133)).toEqual(["short", "tiny"]);
|
|
53
|
+
});
|
|
54
|
+
});
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
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.9.4] — 2026-06-18
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Dispatch tool-result panel overflows the terminal on wide report lines** ([#51](https://github.com/itayinbarr/little-coder/issues/51), reopen of [#48](https://github.com/itayinbarr/little-coder/issues/48)). v1.9.2 capped every line the *live* sub-coder tracker emitted, but the **dispatch tool's result renderer** (`subagent/index.ts`'s `makeComponent`) was still ignoring the `width` arg pi passes to `render(width)` — it returned the precomputed lines verbatim. pi paints the tool-result panel with a 1-char background-color left margin, so any sub-coder report sentence wider than `terminal_width - 1` overflowed pi-tui. Crash log line 453 was a 134-char markdown sentence rendered at terminal width 133 → 135 > 133. The same path runs on **`--resume`** (pi re-paints saved tool results from session history), so v1.9.2 users still hit it after upgrading whenever they resumed a session with a wide dispatch report saved — that's why @steverhoades caught the regression. `makeComponent` now truncates every emitted line to `width - 2` using the existing `_shared/width.ts` utility (2-char safety margin for wide unicode under our char-count-based `visibleWidth` approximation), so the dispatch panel can no longer crash a session — live, on resume, or anywhere else. New `subagent/issue-51-repro.test.ts` drives `makeComponent` with the user's exact 134-char content shape at width 133 and asserts no emitted line exceeds, plus a narrow-terminal (40-col) survival check.
|
|
9
|
+
|
|
10
|
+
### Notes for upgraders
|
|
11
|
+
- No CLI-flag or public-API changes. If you saw `Rendered line N exceeds terminal width` on v1.9.2 / 1.9.3 — especially while *resuming* a session — 1.9.4 fixes it. If you still see it after upgrading, the offending line in `~/.pi/agent/pi-crash.log` should let us spot the source; reopen #51 or #48 with the log attached.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## [v1.9.3] — 2026-06-18
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **`LITTLE_CODER_EXTRA_EXTENSIONS` env var: layer third-party pi extensions onto the bundled set without forking the installed package** ([#46](https://github.com/itayinbarr/little-coder/issues/46)). Path-delimited list (`:` on POSIX, `;` on Windows — `node:path.delimiter`) of extension paths. Each entry can be a direct file (e.g. a `pi-ponytail`-style `extensions/ponytail.js`) or a directory containing `index.ts` / `index.js` (the launcher prefers `.ts`). A leading `~/` is expanded; missing paths log a one-line warning to stderr and are skipped (a typo in the env var doesn't kill the session). Survives upgrades — drop the env var into your shell rc once and every `little-coder` run picks up the extras. Example: `LITTLE_CODER_EXTRA_EXTENSIONS=~/.local/lib/node_modules/pi-ponytail/extensions/ponytail.js little-coder`. Parsing rules live in `bin/extras.mjs` so they're unit-testable in isolation (9 cases covering direct-file / dir-index-resolution / `index.ts`-preference / missing-path warning / `~/` expansion / multiple entries / whitespace trimming). The launcher-level integration is exercised end-to-end (warning prints for a bad path; valid paths pass through silently to pi as `--extension <entry>` flags). Closest siblings — third-party skill bundles — are not yet covered; `skill-inject` still discovers only `<pkgRoot>/skills/tools/*.md`, and a follow-up will add the same kind of override.
|
|
19
|
+
|
|
20
|
+
### Notes for upgraders
|
|
21
|
+
- No CLI-flag or public-API changes. The new env var is opt-in: unset = identical behavior to v1.9.2. If you were carrying a custom wrapper extension inside the installed npm package (which gets wiped on upgrade), you can drop it and use the env var instead.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
5
25
|
## [v1.9.2] — 2026-06-18
|
|
6
26
|
|
|
7
27
|
### Fixed
|
package/README.md
CHANGED
|
@@ -67,6 +67,7 @@ The agent uses the directory you launched it from as its working directory — `
|
|
|
67
67
|
- **Sub-coders (`dispatch`)** — little-coder can spawn isolated child sessions to research a question (read the repo + browse online, read-only) and report back concisely, without cluttering the main conversation. A live panel above the input tracks them. Tune parallelism with `LITTLE_CODER_SUBCODER_CONCURRENCY` (default 2).
|
|
68
68
|
- **Sessions** — each session is auto-named from your first prompt (rename with `/name`) and shown in the terminal tab title. Use `/resume` to list and reopen past sessions for the current directory.
|
|
69
69
|
- **Read-before-edit** — editing a file requires reading it first, so edits match the file's exact current text.
|
|
70
|
+
- **Third-party extensions (`LITTLE_CODER_EXTRA_EXTENSIONS`)** — path-delimited list (`:` on POSIX, `;` on Windows) of extension paths to layer on top of the bundled set. Each entry can be a direct file (e.g. a `pi-ponytail`-style `extensions/ponytail.js`) or a directory containing `index.ts` / `index.js`. `~/` is expanded; missing paths log a warning and are skipped. Survives upgrades, no patching the installed package. Example: `LITTLE_CODER_EXTRA_EXTENSIONS=~/.local/lib/node_modules/pi-ponytail/extensions/ponytail.js little-coder`. (Single-file extensions can still use `little-coder -e <path>` for one-off loads.)
|
|
70
71
|
|
|
71
72
|
For local providers (llama.cpp, Ollama, LM Studio) pi expects *some* value in the API-key env even though local servers ignore it:
|
|
72
73
|
|
package/bin/extras.mjs
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Helpers for layering third-party pi extensions onto little-coder's bundled
|
|
2
|
+
// set. Extracted from the launcher so the parsing rules — path-delimited list,
|
|
3
|
+
// `~/` expansion, directory-with-index resolution, missing-path warning — are
|
|
4
|
+
// directly unit-testable without spawning the whole CLI.
|
|
5
|
+
|
|
6
|
+
import { existsSync, statSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { delimiter, join } from "node:path";
|
|
9
|
+
|
|
10
|
+
// Given the value of LITTLE_CODER_EXTRA_EXTENSIONS (path-delimited list of
|
|
11
|
+
// extension paths), return the resolved entry files that should be passed to pi
|
|
12
|
+
// as `--extension <entry>` flags. Skips empty segments, expands a leading `~/`,
|
|
13
|
+
// resolves a directory entry to its `index.ts` (preferred) or `index.js`, and
|
|
14
|
+
// records a one-line warning for each missing/unusable path so a typo in the
|
|
15
|
+
// env var doesn't kill the session — it just doesn't load that extension.
|
|
16
|
+
export function parseExtraExtensions(
|
|
17
|
+
envValue,
|
|
18
|
+
{ home = homedir(), exists = existsSync, stat = statSync } = {},
|
|
19
|
+
) {
|
|
20
|
+
const entries = [];
|
|
21
|
+
const warnings = [];
|
|
22
|
+
for (const raw of String(envValue ?? "").split(delimiter)) {
|
|
23
|
+
const trimmed = raw.trim();
|
|
24
|
+
if (!trimmed) continue;
|
|
25
|
+
const expanded = trimmed === "~"
|
|
26
|
+
? home
|
|
27
|
+
: trimmed.startsWith("~/")
|
|
28
|
+
? home + trimmed.slice(1)
|
|
29
|
+
: trimmed;
|
|
30
|
+
if (!exists(expanded)) {
|
|
31
|
+
warnings.push(
|
|
32
|
+
`little-coder: LITTLE_CODER_EXTRA_EXTENSIONS path not found, skipping: ${expanded}`,
|
|
33
|
+
);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
let entry = expanded;
|
|
37
|
+
try {
|
|
38
|
+
if (stat(expanded).isDirectory()) {
|
|
39
|
+
const candidates = [join(expanded, "index.ts"), join(expanded, "index.js")];
|
|
40
|
+
const found = candidates.find((p) => exists(p));
|
|
41
|
+
if (!found) {
|
|
42
|
+
warnings.push(
|
|
43
|
+
`little-coder: LITTLE_CODER_EXTRA_EXTENSIONS dir has no index.ts/index.js, skipping: ${expanded}`,
|
|
44
|
+
);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
entry = found;
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
// unreadable / racing stat — skip silently
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
entries.push(entry);
|
|
54
|
+
}
|
|
55
|
+
return { entries, warnings };
|
|
56
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { delimiter, join } from "node:path";
|
|
5
|
+
import { parseExtraExtensions } from "./extras.mjs";
|
|
6
|
+
|
|
7
|
+
function setupTmp() {
|
|
8
|
+
return mkdtempSync(join(tmpdir(), "lc-extras-"));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe("parseExtraExtensions", () => {
|
|
12
|
+
it("returns no entries when env is unset / empty", () => {
|
|
13
|
+
expect(parseExtraExtensions(undefined).entries).toEqual([]);
|
|
14
|
+
expect(parseExtraExtensions("").entries).toEqual([]);
|
|
15
|
+
expect(parseExtraExtensions(delimiter + delimiter).entries).toEqual([]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("forwards a direct file path verbatim", () => {
|
|
19
|
+
const dir = setupTmp();
|
|
20
|
+
try {
|
|
21
|
+
const file = join(dir, "ponytail.js");
|
|
22
|
+
writeFileSync(file, "export default function(){}");
|
|
23
|
+
const { entries, warnings } = parseExtraExtensions(file);
|
|
24
|
+
expect(entries).toEqual([file]);
|
|
25
|
+
expect(warnings).toEqual([]);
|
|
26
|
+
} finally {
|
|
27
|
+
rmSync(dir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("resolves a directory entry to its index.ts (preferred)", () => {
|
|
32
|
+
const dir = setupTmp();
|
|
33
|
+
try {
|
|
34
|
+
const extDir = join(dir, "ponytail");
|
|
35
|
+
mkdirSync(extDir);
|
|
36
|
+
writeFileSync(join(extDir, "index.ts"), "");
|
|
37
|
+
writeFileSync(join(extDir, "index.js"), "");
|
|
38
|
+
const { entries } = parseExtraExtensions(extDir);
|
|
39
|
+
expect(entries).toEqual([join(extDir, "index.ts")]);
|
|
40
|
+
} finally {
|
|
41
|
+
rmSync(dir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("falls back to index.js when index.ts is absent", () => {
|
|
46
|
+
const dir = setupTmp();
|
|
47
|
+
try {
|
|
48
|
+
const extDir = join(dir, "ponytail");
|
|
49
|
+
mkdirSync(extDir);
|
|
50
|
+
writeFileSync(join(extDir, "index.js"), "");
|
|
51
|
+
const { entries } = parseExtraExtensions(extDir);
|
|
52
|
+
expect(entries).toEqual([join(extDir, "index.js")]);
|
|
53
|
+
} finally {
|
|
54
|
+
rmSync(dir, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("warns and skips a directory without index.ts/index.js", () => {
|
|
59
|
+
const dir = setupTmp();
|
|
60
|
+
try {
|
|
61
|
+
const extDir = join(dir, "empty");
|
|
62
|
+
mkdirSync(extDir);
|
|
63
|
+
const { entries, warnings } = parseExtraExtensions(extDir);
|
|
64
|
+
expect(entries).toEqual([]);
|
|
65
|
+
expect(warnings).toHaveLength(1);
|
|
66
|
+
expect(warnings[0]).toContain("no index.ts/index.js");
|
|
67
|
+
} finally {
|
|
68
|
+
rmSync(dir, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("warns and skips a missing path (typo doesn't kill the session)", () => {
|
|
73
|
+
const { entries, warnings } = parseExtraExtensions("/tmp/does-not-exist-zzz-xyz-123");
|
|
74
|
+
expect(entries).toEqual([]);
|
|
75
|
+
expect(warnings).toHaveLength(1);
|
|
76
|
+
expect(warnings[0]).toContain("path not found");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("expands a leading ~/ using the supplied home", () => {
|
|
80
|
+
const dir = setupTmp();
|
|
81
|
+
try {
|
|
82
|
+
const extDir = join(dir, "fake-home", "ext");
|
|
83
|
+
mkdirSync(extDir, { recursive: true });
|
|
84
|
+
writeFileSync(join(extDir, "index.js"), "");
|
|
85
|
+
const { entries } = parseExtraExtensions("~/ext", {
|
|
86
|
+
home: join(dir, "fake-home"),
|
|
87
|
+
});
|
|
88
|
+
expect(entries).toEqual([join(extDir, "index.js")]);
|
|
89
|
+
} finally {
|
|
90
|
+
rmSync(dir, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("layers multiple extensions from one path-delimited list", () => {
|
|
95
|
+
const dir = setupTmp();
|
|
96
|
+
try {
|
|
97
|
+
const a = join(dir, "a.js");
|
|
98
|
+
writeFileSync(a, "");
|
|
99
|
+
const b = join(dir, "b.js");
|
|
100
|
+
writeFileSync(b, "");
|
|
101
|
+
const { entries } = parseExtraExtensions([a, b].join(delimiter));
|
|
102
|
+
expect(entries).toEqual([a, b]);
|
|
103
|
+
} finally {
|
|
104
|
+
rmSync(dir, { recursive: true });
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("trims whitespace around list entries", () => {
|
|
109
|
+
const dir = setupTmp();
|
|
110
|
+
try {
|
|
111
|
+
const a = join(dir, "a.js");
|
|
112
|
+
writeFileSync(a, "");
|
|
113
|
+
const { entries } = parseExtraExtensions(` ${a} `);
|
|
114
|
+
expect(entries).toEqual([a]);
|
|
115
|
+
} finally {
|
|
116
|
+
rmSync(dir, { recursive: true });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
package/bin/little-coder.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import { homedir } from "node:os";
|
|
|
17
17
|
import { dirname, join, resolve } from "node:path";
|
|
18
18
|
import { fileURLToPath } from "node:url";
|
|
19
19
|
import { checkForUpdate } from "./update-check.mjs";
|
|
20
|
+
import { parseExtraExtensions } from "./extras.mjs";
|
|
20
21
|
|
|
21
22
|
// ---- 1. Node version preflight (>= 22.19.0, matching pi.dev) ----
|
|
22
23
|
const MIN_NODE = [22, 19, 0];
|
|
@@ -110,6 +111,21 @@ if (existsSync(extDir)) {
|
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
// ---- 4b. Third-party extensions via LITTLE_CODER_EXTRA_EXTENSIONS ----
|
|
115
|
+
// Path-delimited list (`:` on POSIX, `;` on Windows — node:path.delimiter)
|
|
116
|
+
// of extra extension paths to load alongside the bundled ones. Each entry can
|
|
117
|
+
// be either a direct file path (e.g. a pi-ponytail-style `extensions/ponytail.js`)
|
|
118
|
+
// or a directory containing `index.ts` / `index.js`. Survives upgrades and
|
|
119
|
+
// avoids the "fork the installed npm package" workaround that issue #46 hit.
|
|
120
|
+
// Parsing rules — ~/ expansion, directory-with-index resolution, one-line
|
|
121
|
+
// warning for missing/unusable entries — live in ./extras.mjs so they're
|
|
122
|
+
// unit-testable in isolation.
|
|
123
|
+
{
|
|
124
|
+
const { entries, warnings } = parseExtraExtensions(process.env.LITTLE_CODER_EXTRA_EXTENSIONS);
|
|
125
|
+
for (const w of warnings) console.error(w);
|
|
126
|
+
for (const entry of entries) extArgs.push("--extension", entry);
|
|
127
|
+
}
|
|
128
|
+
|
|
113
129
|
// ---- 5. Update check (best-effort, blocks on TTY prompt only) ----
|
|
114
130
|
let currentVersion = "0.0.0";
|
|
115
131
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "little-coder",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.4",
|
|
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": {
|