little-coder 1.0.0 → 1.0.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.
- package/CHANGELOG.md +12 -0
- package/bin/little-coder.mjs +22 -4
- package/bin/update-check.mjs +167 -0
- package/bin/update-check.test.mjs +164 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
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.0.1] — 2026-04-28
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Update check on startup.** When a newer `little-coder` is on npm, the launcher tells you and (in interactive mode) offers to update on the spot.
|
|
9
|
+
- **Interactive TTY:** prompt `Update now? [Y/n]` — Enter or `y` runs `npm install -g little-coder@latest` and asks you to re-run; `n` skips for this session.
|
|
10
|
+
- **Non-TTY (CI, scripts, pipes, `--print` pipelines):** prints a one-line stderr notice with the install command, never prompts.
|
|
11
|
+
- **Skipped automatically** for `--help`, `--version`, `--list-models`, `--export`, `--mode rpc`, `--mode json`, when `CI=true`, and for the new `--no-update-check` flag / `LITTLE_CODER_NO_UPDATE_CHECK=1` env opt-out.
|
|
12
|
+
- **Cached** at `${XDG_CACHE_HOME:-~/.cache}/little-coder/version-check.json` with a 12 h TTL — at most one network call per day.
|
|
13
|
+
- **Best-effort:** 2 s fetch timeout, all errors swallowed silently. Update check never blocks the agent if the registry is slow or unreachable.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
5
17
|
## [v1.0.0] — 2026-04-28
|
|
6
18
|
|
|
7
19
|
Distribution + stability release. Hi everywhere, bye `./node_modules/.bin/pi`.
|
package/bin/little-coder.mjs
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
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, statSync } from "node:fs";
|
|
7
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
8
8
|
import { dirname, join, resolve } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { checkForUpdate } from "./update-check.mjs";
|
|
10
11
|
|
|
11
12
|
// ---- 1. Node version preflight (>= 20.6.0, matching pi.dev) ----
|
|
12
13
|
const MIN_NODE = [20, 6, 0];
|
|
@@ -54,20 +55,37 @@ if (existsSync(extDir)) {
|
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
// ---- 5.
|
|
58
|
+
// ---- 5. Update check (best-effort, blocks on TTY prompt only) ----
|
|
59
|
+
let currentVersion = "0.0.0";
|
|
60
|
+
try {
|
|
61
|
+
const pkgJson = JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf-8"));
|
|
62
|
+
if (typeof pkgJson?.version === "string") currentVersion = pkgJson.version;
|
|
63
|
+
} catch {
|
|
64
|
+
// ignore — update-check just won't fire if we can't read the version
|
|
65
|
+
}
|
|
66
|
+
const exitAfterCheck = await checkForUpdate(currentVersion);
|
|
67
|
+
if (exitAfterCheck) {
|
|
68
|
+
// Successful update happened; user needs to re-run the new binary.
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---- 6. Compose pi argv ----
|
|
58
73
|
// --no-context-files : ignore the user's AGENTS.md / CLAUDE.md so OURS wins
|
|
59
74
|
// --no-extensions : skip pi's auto-discovery from cwd; explicit -e flags still load
|
|
60
75
|
// --system-prompt : load <pkgRoot>/AGENTS.md regardless of cwd
|
|
76
|
+
//
|
|
77
|
+
// Strip our own flags before forwarding to pi so it doesn't reject them.
|
|
78
|
+
const userArgs = process.argv.slice(2).filter((a) => a !== "--no-update-check");
|
|
61
79
|
const agentsMd = join(pkgRoot, "AGENTS.md");
|
|
62
80
|
const piArgs = [
|
|
63
81
|
"--no-context-files",
|
|
64
82
|
"--no-extensions",
|
|
65
83
|
...(existsSync(agentsMd) ? ["--system-prompt", agentsMd] : []),
|
|
66
84
|
...extArgs,
|
|
67
|
-
...
|
|
85
|
+
...userArgs,
|
|
68
86
|
];
|
|
69
87
|
|
|
70
|
-
// ----
|
|
88
|
+
// ---- 7. Spawn pi in the user's cwd ----
|
|
71
89
|
const child = spawn(piBin, piArgs, {
|
|
72
90
|
stdio: "inherit",
|
|
73
91
|
cwd: process.cwd(),
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// little-coder update check.
|
|
2
|
+
// Polls the npm registry for a newer published version and (in TTY mode)
|
|
3
|
+
// offers to install it before the agent starts. Cached so we don't call out
|
|
4
|
+
// on every invocation. Best-effort throughout: if anything fails, we skip
|
|
5
|
+
// silently — never block the agent over a version check.
|
|
6
|
+
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { createInterface } from "node:readline";
|
|
12
|
+
|
|
13
|
+
const REGISTRY = "https://registry.npmjs.org/little-coder/latest";
|
|
14
|
+
const CACHE_TTL_MS = 12 * 60 * 60 * 1000; // 12 h
|
|
15
|
+
const FETCH_TIMEOUT_MS = 2000;
|
|
16
|
+
|
|
17
|
+
export function cachePath() {
|
|
18
|
+
const xdg = process.env.XDG_CACHE_HOME && process.env.XDG_CACHE_HOME.trim();
|
|
19
|
+
const base = xdg ? xdg : join(homedir(), ".cache");
|
|
20
|
+
return join(base, "little-coder", "version-check.json");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function readCache(now = Date.now()) {
|
|
24
|
+
try {
|
|
25
|
+
const path = cachePath();
|
|
26
|
+
if (!existsSync(path)) return null;
|
|
27
|
+
const data = JSON.parse(readFileSync(path, "utf-8"));
|
|
28
|
+
if (typeof data.checkedAt !== "number" || typeof data.latest !== "string") return null;
|
|
29
|
+
if (now - data.checkedAt > CACHE_TTL_MS) return null;
|
|
30
|
+
return data;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function writeCache(latest, now = Date.now()) {
|
|
37
|
+
try {
|
|
38
|
+
const path = cachePath();
|
|
39
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
40
|
+
writeFileSync(path, JSON.stringify({ checkedAt: now, latest }));
|
|
41
|
+
} catch {
|
|
42
|
+
// best-effort; permission errors etc. are not fatal
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Compare semver strings. Only handles X.Y.Z[+pre]. Returns 1 if a > b,
|
|
47
|
+
// -1 if a < b, 0 if equal. Pre-release suffixes are treated as < release.
|
|
48
|
+
export function compareSemver(a, b) {
|
|
49
|
+
const parse = (v) => {
|
|
50
|
+
const [core, pre] = String(v).split("-", 2);
|
|
51
|
+
const parts = core.split(".").map((n) => parseInt(n, 10));
|
|
52
|
+
return {
|
|
53
|
+
major: parts[0] || 0,
|
|
54
|
+
minor: parts[1] || 0,
|
|
55
|
+
patch: parts[2] || 0,
|
|
56
|
+
pre: pre || "",
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
const pa = parse(a);
|
|
60
|
+
const pb = parse(b);
|
|
61
|
+
if (pa.major !== pb.major) return pa.major > pb.major ? 1 : -1;
|
|
62
|
+
if (pa.minor !== pb.minor) return pa.minor > pb.minor ? 1 : -1;
|
|
63
|
+
if (pa.patch !== pb.patch) return pa.patch > pb.patch ? 1 : -1;
|
|
64
|
+
// Equal core: a release beats a pre-release.
|
|
65
|
+
if (pa.pre === pb.pre) return 0;
|
|
66
|
+
if (pa.pre === "") return 1;
|
|
67
|
+
if (pb.pre === "") return -1;
|
|
68
|
+
return pa.pre > pb.pre ? 1 : -1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function fetchLatest() {
|
|
72
|
+
const ctrl = new AbortController();
|
|
73
|
+
const t = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
|
|
74
|
+
try {
|
|
75
|
+
const res = await fetch(REGISTRY, { signal: ctrl.signal });
|
|
76
|
+
if (!res.ok) return null;
|
|
77
|
+
const json = await res.json();
|
|
78
|
+
return typeof json?.version === "string" ? json.version : null;
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
} finally {
|
|
82
|
+
clearTimeout(t);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Decide whether to skip the check entirely. Errs toward NOT prompting in
|
|
87
|
+
// any context that smells programmatic.
|
|
88
|
+
export function shouldSkip(argv = process.argv.slice(2), env = process.env, stdout = process.stdout) {
|
|
89
|
+
if (env.LITTLE_CODER_NO_UPDATE_CHECK === "1") return true;
|
|
90
|
+
if (env.CI === "true" || env.CI === "1") return true;
|
|
91
|
+
for (let i = 0; i < argv.length; i++) {
|
|
92
|
+
const a = argv[i];
|
|
93
|
+
if (a === "--no-update-check") return true;
|
|
94
|
+
if (a === "--help" || a === "-h") return true;
|
|
95
|
+
if (a === "--version" || a === "-v") return true;
|
|
96
|
+
if (a === "--list-models") return true;
|
|
97
|
+
if (a === "--export") return true;
|
|
98
|
+
if (a === "--mode") {
|
|
99
|
+
const next = argv[i + 1];
|
|
100
|
+
if (next === "rpc" || next === "json") return true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Non-TTY runs: scripts, pipes, --print pipelines. Notice only, no prompt.
|
|
104
|
+
if (!stdout.isTTY) return "notice-only";
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function promptYesNo(question) {
|
|
109
|
+
return new Promise((resolve) => {
|
|
110
|
+
if (!process.stdin.isTTY) {
|
|
111
|
+
resolve(false);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
115
|
+
rl.question(question, (answer) => {
|
|
116
|
+
rl.close();
|
|
117
|
+
const a = (answer ?? "").trim().toLowerCase();
|
|
118
|
+
resolve(a === "" || a === "y" || a === "yes");
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Returns `true` if the launcher should NOT proceed to spawn pi (because we
|
|
124
|
+
// updated and exited / the user opted out and we should re-run). Returns
|
|
125
|
+
// `false` to let the launcher continue.
|
|
126
|
+
export async function checkForUpdate(currentVersion, opts = {}) {
|
|
127
|
+
const skip = opts.skip ?? shouldSkip();
|
|
128
|
+
if (skip === true) return false;
|
|
129
|
+
|
|
130
|
+
let latest = readCache()?.latest;
|
|
131
|
+
if (!latest) {
|
|
132
|
+
latest = await fetchLatest();
|
|
133
|
+
if (latest) writeCache(latest);
|
|
134
|
+
}
|
|
135
|
+
if (!latest) return false;
|
|
136
|
+
if (compareSemver(latest, currentVersion) <= 0) return false;
|
|
137
|
+
|
|
138
|
+
const headline =
|
|
139
|
+
`\n📦 little-coder v${latest} is available (you have v${currentVersion}).`;
|
|
140
|
+
|
|
141
|
+
if (skip === "notice-only") {
|
|
142
|
+
process.stderr.write(`${headline}\n Update with: npm install -g little-coder\n\n`);
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
process.stderr.write(`${headline}\n`);
|
|
147
|
+
const wantsUpdate = await promptYesNo(" Update now? [Y/n] ");
|
|
148
|
+
if (!wantsUpdate) {
|
|
149
|
+
process.stderr.write(" Skipping update for this run.\n\n");
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
process.stderr.write(`\n Running: npm install -g little-coder@${latest}\n\n`);
|
|
154
|
+
const result = spawnSync("npm", ["install", "-g", `little-coder@${latest}`], {
|
|
155
|
+
stdio: "inherit",
|
|
156
|
+
});
|
|
157
|
+
if (result.status === 0) {
|
|
158
|
+
process.stderr.write(
|
|
159
|
+
`\n ✓ Updated to v${latest}. Re-run \`little-coder\` to use the new version.\n\n`,
|
|
160
|
+
);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
process.stderr.write(
|
|
164
|
+
`\n ✗ Update failed (npm exit ${result.status}). Continuing with v${currentVersion}.\n\n`,
|
|
165
|
+
);
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mkdtempSync, rmSync, existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
cachePath,
|
|
7
|
+
readCache,
|
|
8
|
+
writeCache,
|
|
9
|
+
compareSemver,
|
|
10
|
+
shouldSkip,
|
|
11
|
+
} from "./update-check.mjs";
|
|
12
|
+
|
|
13
|
+
describe("compareSemver", () => {
|
|
14
|
+
it("orders major / minor / patch correctly", () => {
|
|
15
|
+
expect(compareSemver("1.0.0", "1.0.0")).toBe(0);
|
|
16
|
+
expect(compareSemver("1.0.1", "1.0.0")).toBe(1);
|
|
17
|
+
expect(compareSemver("1.0.0", "1.0.1")).toBe(-1);
|
|
18
|
+
expect(compareSemver("1.1.0", "1.0.99")).toBe(1);
|
|
19
|
+
expect(compareSemver("2.0.0", "1.99.99")).toBe(1);
|
|
20
|
+
expect(compareSemver("0.99.99", "1.0.0")).toBe(-1);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("treats releases as greater than pre-releases of same core", () => {
|
|
24
|
+
expect(compareSemver("1.0.0", "1.0.0-rc.1")).toBe(1);
|
|
25
|
+
expect(compareSemver("1.0.0-rc.1", "1.0.0")).toBe(-1);
|
|
26
|
+
expect(compareSemver("1.0.0-rc.2", "1.0.0-rc.1")).toBe(1);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("tolerates short version strings", () => {
|
|
30
|
+
expect(compareSemver("1.0", "1.0.0")).toBe(0);
|
|
31
|
+
expect(compareSemver("1", "1.0.0")).toBe(0);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("cachePath", () => {
|
|
36
|
+
it("uses XDG_CACHE_HOME when set", () => {
|
|
37
|
+
const orig = process.env.XDG_CACHE_HOME;
|
|
38
|
+
process.env.XDG_CACHE_HOME = "/tmp/xdg-test";
|
|
39
|
+
try {
|
|
40
|
+
expect(cachePath()).toBe("/tmp/xdg-test/little-coder/version-check.json");
|
|
41
|
+
} finally {
|
|
42
|
+
if (orig !== undefined) process.env.XDG_CACHE_HOME = orig;
|
|
43
|
+
else delete process.env.XDG_CACHE_HOME;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("falls back to ~/.cache when XDG is unset", () => {
|
|
48
|
+
const orig = process.env.XDG_CACHE_HOME;
|
|
49
|
+
delete process.env.XDG_CACHE_HOME;
|
|
50
|
+
try {
|
|
51
|
+
const p = cachePath();
|
|
52
|
+
expect(p).toMatch(/\.cache\/little-coder\/version-check\.json$/);
|
|
53
|
+
} finally {
|
|
54
|
+
if (orig !== undefined) process.env.XDG_CACHE_HOME = orig;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("read/writeCache", () => {
|
|
60
|
+
let tmp;
|
|
61
|
+
let origXdg;
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
tmp = mkdtempSync(join(tmpdir(), "lc-uc-test-"));
|
|
64
|
+
origXdg = process.env.XDG_CACHE_HOME;
|
|
65
|
+
process.env.XDG_CACHE_HOME = tmp;
|
|
66
|
+
});
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
if (origXdg !== undefined) process.env.XDG_CACHE_HOME = origXdg;
|
|
69
|
+
else delete process.env.XDG_CACHE_HOME;
|
|
70
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("returns null when no cache exists", () => {
|
|
74
|
+
expect(readCache()).toBeNull();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("round-trips a fresh entry", () => {
|
|
78
|
+
writeCache("1.0.5", 1000);
|
|
79
|
+
const cached = readCache(2000);
|
|
80
|
+
expect(cached?.latest).toBe("1.0.5");
|
|
81
|
+
expect(cached?.checkedAt).toBe(1000);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("returns null for stale entries past 12h TTL", () => {
|
|
85
|
+
writeCache("1.0.5", 0);
|
|
86
|
+
const stale = readCache(13 * 60 * 60 * 1000);
|
|
87
|
+
expect(stale).toBeNull();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("returns the entry if exactly at TTL boundary", () => {
|
|
91
|
+
writeCache("1.0.5", 0);
|
|
92
|
+
const at = readCache(12 * 60 * 60 * 1000);
|
|
93
|
+
expect(at?.latest).toBe("1.0.5");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("handles malformed cache files gracefully", () => {
|
|
97
|
+
writeCache("garbage", 1000);
|
|
98
|
+
const path = cachePath();
|
|
99
|
+
// Corrupt the file
|
|
100
|
+
const fs = readFileSync(path, "utf-8");
|
|
101
|
+
expect(fs).toContain("garbage");
|
|
102
|
+
// Now write actual garbage
|
|
103
|
+
require("node:fs").writeFileSync(path, "{not-json");
|
|
104
|
+
expect(readCache()).toBeNull();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("creates the cache directory if missing", () => {
|
|
108
|
+
rmSync(join(tmp, "little-coder"), { recursive: true, force: true });
|
|
109
|
+
writeCache("1.2.3", 5000);
|
|
110
|
+
expect(existsSync(cachePath())).toBe(true);
|
|
111
|
+
expect(readCache(5000)?.latest).toBe("1.2.3");
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("shouldSkip", () => {
|
|
116
|
+
function ttyStdout() { return { isTTY: true }; }
|
|
117
|
+
function pipeStdout() { return { isTTY: false }; }
|
|
118
|
+
const noEnv = {};
|
|
119
|
+
|
|
120
|
+
it("returns false in plain TTY interactive mode", () => {
|
|
121
|
+
expect(shouldSkip([], noEnv, ttyStdout())).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("skips when LITTLE_CODER_NO_UPDATE_CHECK=1", () => {
|
|
125
|
+
expect(shouldSkip([], { LITTLE_CODER_NO_UPDATE_CHECK: "1" }, ttyStdout())).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("skips on --no-update-check flag", () => {
|
|
129
|
+
expect(shouldSkip(["--no-update-check"], noEnv, ttyStdout())).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("skips on --help / -h", () => {
|
|
133
|
+
expect(shouldSkip(["--help"], noEnv, ttyStdout())).toBe(true);
|
|
134
|
+
expect(shouldSkip(["-h"], noEnv, ttyStdout())).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("skips on --version / -v", () => {
|
|
138
|
+
expect(shouldSkip(["--version"], noEnv, ttyStdout())).toBe(true);
|
|
139
|
+
expect(shouldSkip(["-v"], noEnv, ttyStdout())).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("skips on --list-models and --export", () => {
|
|
143
|
+
expect(shouldSkip(["--list-models"], noEnv, ttyStdout())).toBe(true);
|
|
144
|
+
expect(shouldSkip(["--export", "session.jsonl"], noEnv, ttyStdout())).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("skips for --mode rpc / --mode json", () => {
|
|
148
|
+
expect(shouldSkip(["--mode", "rpc"], noEnv, ttyStdout())).toBe(true);
|
|
149
|
+
expect(shouldSkip(["--mode", "json"], noEnv, ttyStdout())).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("does not skip for --mode text", () => {
|
|
153
|
+
expect(shouldSkip(["--mode", "text"], noEnv, ttyStdout())).toBe(false);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("skips in CI environments", () => {
|
|
157
|
+
expect(shouldSkip([], { CI: "true" }, ttyStdout())).toBe(true);
|
|
158
|
+
expect(shouldSkip([], { CI: "1" }, ttyStdout())).toBe(true);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("returns notice-only on non-TTY pipelines", () => {
|
|
162
|
+
expect(shouldSkip([], noEnv, pipeStdout())).toBe("notice-only");
|
|
163
|
+
});
|
|
164
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "little-coder",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.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": {
|