@tommy-ca/lazypi 0.10.2 → 0.10.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/README.md +30 -4
- package/bin/lazypi.mjs +61 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,17 +13,43 @@ LazyPi will:
|
|
|
13
13
|
1. Install `pi` for you if it isn't installed yet.
|
|
14
14
|
2. Ask if you want to install all the packages or choose which to install.
|
|
15
15
|
|
|
16
|
-
That setup is the harness core
|
|
16
|
+
That setup is the 12-package harness core. `--yes` installs those 12. On a TTY, Install everything installs all 17. Packages LazyPi dropped stay installable with `pi install`. LazyPi does not manage them. See the Catalog table and `docs/docs/philosophy.html`.
|
|
17
17
|
|
|
18
18
|
That's it. Once done - run `pi` and experience a feature rich coding agent experience.
|
|
19
19
|
|
|
20
20
|
Install is **idempotent** — LazyPi reads your Pi settings and skips any package that is already installed, so re-running is safe.
|
|
21
21
|
|
|
22
|
+
## Catalog
|
|
23
|
+
|
|
24
|
+
`--yes` installs 12 core packages. TTY Install everything installs all 17.
|
|
25
|
+
|
|
26
|
+
| id | category | why |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| subagents | core | Isolated specialists instead of one bloated agent |
|
|
29
|
+
| pi-ask-user | core | Stop and ask instead of guessing |
|
|
30
|
+
| pi-skillful | core | Keep 4 to 6 skills visible. Hide the rest |
|
|
31
|
+
| mention-skill | core | `$` mention is the one way to reach hidden skills |
|
|
32
|
+
| goal | core | Long-objective gate. Not a checkbox list |
|
|
33
|
+
| btw | core | Side chat that does not pollute history |
|
|
34
|
+
| context-usage | core | See what is burning the window |
|
|
35
|
+
| simplify | core | Review diffs for clarity after implementation |
|
|
36
|
+
| web-access | core | Search and fetch instead of stale training data |
|
|
37
|
+
| fff | core | Paged fuzzy search so grep does not flood the transcript |
|
|
38
|
+
| dynamic-workflows | core | Fan work out across subagents |
|
|
39
|
+
| ponytail | core | Stdlib-first discipline. Lean catalog as a habit |
|
|
40
|
+
| lsp | optional | Targeted diagnostics. Cataloged, not default |
|
|
41
|
+
| interactive-shell | optional | TUI overlays. Chrome, not control plane |
|
|
42
|
+
| autoresearch | optional | Experiment loops. Convenience, not core |
|
|
43
|
+
| todos | optional | Structured tracker. Optional because core forbids checkbox todos as default |
|
|
44
|
+
| memory | optional | Git-backed memory. Infrastructure, not control plane |
|
|
45
|
+
|
|
46
|
+
Dropped packages are outside this table. `pi install` still works. LazyPi does not manage them.
|
|
47
|
+
|
|
22
48
|
## Commands
|
|
23
49
|
|
|
24
50
|
| Command | What it does |
|
|
25
51
|
| --- | --- |
|
|
26
|
-
| `npx @tommy-ca/lazypi` | Install
|
|
52
|
+
| `npx @tommy-ca/lazypi` | TTY Install everything is 17. `--yes` is the 12 core packages. Picker otherwise. |
|
|
27
53
|
| `npx @tommy-ca/lazypi remove <id>` | Remove a catalog package by id (or pass a raw pi source) |
|
|
28
54
|
| `npx @tommy-ca/lazypi status` | Show which catalog packages are installed, missing, or extra |
|
|
29
55
|
| `npx @tommy-ca/lazypi update` | Run `pi update` for installed Pi packages |
|
|
@@ -71,7 +97,7 @@ specs behave the same way, so `cd` out of the checkout first.
|
|
|
71
97
|
|
|
72
98
|
## Site / docs
|
|
73
99
|
|
|
74
|
-
The
|
|
100
|
+
The Jekyll source lives in `docs/`. This fork does not enable GitHub Pages, and [lazypi.org](https://lazypi.org) still serves the upstream installer.
|
|
75
101
|
|
|
76
102
|
To preview locally (requires Ruby + Bundler):
|
|
77
103
|
|
|
@@ -106,4 +132,4 @@ before treating releases as automated.
|
|
|
106
132
|
|
|
107
133
|
---
|
|
108
134
|
|
|
109
|
-
|
|
135
|
+
The catalog table above is the npm list. `docs/docs/philosophy.html` explains membership. Run `npx @tommy-ca/lazypi` to install.
|
package/bin/lazypi.mjs
CHANGED
|
@@ -175,7 +175,7 @@ function validateSelectors(list, label) {
|
|
|
175
175
|
if (bad.length > 0) {
|
|
176
176
|
console.error(red(`Unknown ${label}: ${bad.join(", ")}`));
|
|
177
177
|
console.error(`Valid categories: ${CATEGORIES.join(", ")}`);
|
|
178
|
-
console.error(`Valid package ids:
|
|
178
|
+
console.error(`Valid package ids: ${[...ids].join(", ")}`);
|
|
179
179
|
return false;
|
|
180
180
|
}
|
|
181
181
|
return true;
|
|
@@ -185,7 +185,7 @@ function matchesSelector(pkg, selectors) {
|
|
|
185
185
|
return selectors.some((name) => name === pkg.category || name === pkg.id);
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
function resolveSelection(flags) {
|
|
188
|
+
export function resolveSelection(flags) {
|
|
189
189
|
if (flags.only) {
|
|
190
190
|
if (!validateSelectors(flags.only, "--only")) return null;
|
|
191
191
|
return new Set(PACKAGES.filter((p) => matchesSelector(p, flags.only)).map((p) => p.id));
|
|
@@ -231,8 +231,8 @@ ${bold("Default behaviour:")}
|
|
|
231
231
|
${bold("Categories:")}
|
|
232
232
|
core the harness control plane: sub-agents, workflows, ask gate, skill visibility, $ mention, goal, side chat, context budget, simplify, web research, fff search, ponytail discipline
|
|
233
233
|
optional only when selected (--only optional, picker, or the everything flow): lsp diagnostics, interactive-shell overlays, autoresearch loops, todo tracking, markdown memory
|
|
234
|
-
|
|
235
|
-
|
|
234
|
+
(Dropped packages such as skill-args, mcp, ralph-wiggum, curated-themes are
|
|
235
|
+
outside the catalog — pi install npm:<source> still works for them.)
|
|
236
236
|
|
|
237
237
|
${bold("Examples:")}
|
|
238
238
|
npx @tommy-ca/lazypi # core catalog (interactive picker on a TTY)
|
|
@@ -253,9 +253,47 @@ export function buildSpawnOptions(options = {}, _platformName = platform()) {
|
|
|
253
253
|
return { ...options };
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
+
function isWindowsCmdOrBat(resolved, platformName = platform()) {
|
|
257
|
+
return platformName === "win32" && /\.(cmd|bat)$/i.test(String(resolved));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const CMD_QUOTE_RE = /[\s&|<>^%!"()]/;
|
|
261
|
+
|
|
262
|
+
export function quoteCmdArg(value, { always = false } = {}) {
|
|
263
|
+
const text = String(value);
|
|
264
|
+
if (!always && text !== "" && !CMD_QUOTE_RE.test(text)) return text;
|
|
265
|
+
return `"${text.replaceAll("%", "%%").replaceAll('"', '""')}"`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function windowsComSpec(env = process.env) {
|
|
269
|
+
const comspec = env.ComSpec;
|
|
270
|
+
if (comspec && /(^|[\\/])cmd\.exe$/i.test(comspec)) return comspec;
|
|
271
|
+
const root = env.SystemRoot || "C:\\Windows";
|
|
272
|
+
return `${root}\\System32\\cmd.exe`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function pickWindowsWhereHit(stdout) {
|
|
276
|
+
const lines = String(stdout ?? "").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
277
|
+
return lines.find((line) => /\.(cmd|bat|exe|com)$/i.test(line)) ?? lines[0] ?? null;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function windowsSpawnArgv(resolved, args = [], platformName = platform()) {
|
|
281
|
+
if (isWindowsCmdOrBat(resolved, platformName)) {
|
|
282
|
+
return {
|
|
283
|
+
command: windowsComSpec(),
|
|
284
|
+
args: ["/d", "/s", "/c", "call", quoteCmdArg(resolved, { always: true }), ...args.map((arg) => quoteCmdArg(arg))],
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
return { command: resolved, args: [...args] };
|
|
288
|
+
}
|
|
289
|
+
|
|
256
290
|
function spawnCommand(command, args = [], options = {}) {
|
|
257
291
|
const resolved = command === "pi" || command === "npm" ? commandPath(command) ?? command : command;
|
|
258
|
-
|
|
292
|
+
const spawned = windowsSpawnArgv(resolved, args);
|
|
293
|
+
const spawnOptions = isWindowsCmdOrBat(resolved)
|
|
294
|
+
? { ...buildSpawnOptions(options), shell: false, windowsVerbatimArguments: true }
|
|
295
|
+
: buildSpawnOptions(options);
|
|
296
|
+
return spawnSync(spawned.command, spawned.args, spawnOptions);
|
|
259
297
|
}
|
|
260
298
|
|
|
261
299
|
function hasCmd(name) {
|
|
@@ -355,6 +393,7 @@ function commandPath(name) {
|
|
|
355
393
|
const command = platform() === "win32" ? "where" : "which";
|
|
356
394
|
const probe = spawnSync(command, [name], buildSpawnOptions({ encoding: "utf8" }));
|
|
357
395
|
if (probe.status !== 0) return null;
|
|
396
|
+
if (platform() === "win32") return pickWindowsWhereHit(probe.stdout);
|
|
358
397
|
const first = String(probe.stdout ?? "").split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
359
398
|
return first || null;
|
|
360
399
|
}
|
|
@@ -531,6 +570,11 @@ async function ensurePi(flags) {
|
|
|
531
570
|
if (hasCmd("pi")) return true;
|
|
532
571
|
|
|
533
572
|
log.warn("Could not find the `pi` command on PATH.");
|
|
573
|
+
if (!flags.yes && !isInteractive()) {
|
|
574
|
+
log.error("Install Pi first (`npm install -g @earendil-works/pi-coding-agent`), then re-run `npx @tommy-ca/lazypi`.");
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
|
|
534
578
|
const ok = flags.yes || (await confirm("Install Pi now with `npm install -g @earendil-works/pi-coding-agent`?", true));
|
|
535
579
|
if (!ok) {
|
|
536
580
|
log.error("Install Pi first, then re-run `npx @tommy-ca/lazypi`.");
|
|
@@ -761,9 +805,15 @@ function cmdStatus(flags) {
|
|
|
761
805
|
);
|
|
762
806
|
|
|
763
807
|
printHeader(`Installed from LazyPi catalog (${installed.length}/${PACKAGES.length}):`);
|
|
808
|
+
const coreTotal = PACKAGES.filter((pkg) => pkg.category === "core").length;
|
|
809
|
+
const optionalTotal = PACKAGES.filter((pkg) => pkg.category === "optional").length;
|
|
810
|
+
const coreInstalled = installed.filter((pkg) => pkg.category === "core").length;
|
|
811
|
+
const optionalInstalled = installed.filter((pkg) => pkg.category === "optional").length;
|
|
812
|
+
console.log(dim(` core ${coreInstalled}/${coreTotal}, optional ${optionalInstalled}/${optionalTotal}`));
|
|
764
813
|
if (installed.length === 0) console.log(dim(" none"));
|
|
765
814
|
for (const pkg of installed) {
|
|
766
|
-
|
|
815
|
+
const installedSource = [...sources].find((src) => sourcesMatch(pkg.source, src)) ?? pkg.source;
|
|
816
|
+
console.log(` ${green("✓")} [${pkg.category}] ${pkg.id.padEnd(20)} ${dim(installedSource)}`);
|
|
767
817
|
}
|
|
768
818
|
|
|
769
819
|
printHeader(`Installed with legacy catalog sources (${legacy.length}):`);
|
|
@@ -816,7 +866,7 @@ function cmdDoctor(flags) {
|
|
|
816
866
|
let problems = 0;
|
|
817
867
|
let warnings = 0;
|
|
818
868
|
const pass = (msg) => console.log(` ${green("✓")} ${msg}`);
|
|
819
|
-
const warn = (msg, { fatal =
|
|
869
|
+
const warn = (msg, { fatal = false } = {}) => {
|
|
820
870
|
console.log(` ${yellow("!")} ${msg}`);
|
|
821
871
|
if (fatal) problems++;
|
|
822
872
|
else warnings++;
|
|
@@ -854,7 +904,10 @@ function cmdDoctor(flags) {
|
|
|
854
904
|
else if (error) fail(`${path} is not valid JSON — ${error}`);
|
|
855
905
|
else {
|
|
856
906
|
pass(`${path} is readable`);
|
|
857
|
-
const unpinnedGit = [...sources].filter((src) =>
|
|
907
|
+
const unpinnedGit = [...sources].filter((src) => {
|
|
908
|
+
if (!/^git:github\.com\/[^/@]+\/[^@\s]+$/.test(src)) return false;
|
|
909
|
+
return !PACKAGES.some((pkg) => sourcesMatch(pkg.source, src) || isLegacySourceForPackage(pkg, src));
|
|
910
|
+
});
|
|
858
911
|
for (const src of unpinnedGit) warn(`${src} is an unpinned git head — pin it to a commit for reproducible installs`, { fatal: false });
|
|
859
912
|
}
|
|
860
913
|
|