@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 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 — isolated sub-agents, a structured ask gate, skill visibility, $ skill mention, a long-objective gate, side chat, context budgeting, code simplification review, web research, FFF search, a workflow engine for sub-agent fan-out, and ponytail discipline review. A five-package optional tier (targeted LSP diagnostics, interactive shell overlays, autonomous research loops, structured todo tracking, markdown-backed memory) installs on request with `--only optional`. Other extras (skill arguments, MCP, ralph-wiggum, themes) install on demand with `pi install`.
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 all or selected catalog (interactive picker by default) |
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 site at [lazypi.org](https://lazypi.org) lives in `docs/` and is a Jekyll site compiled by GitHub Pages automatically on push to `master`.
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
- For the full list of included packages and optional extras, see [lazypi.org](https://lazypi.org).
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: ${[...ids].join(", ")}`);
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
- Non-catalog extras (pi install npm:<source>): skill-args, mcp,
235
- ralph-wiggum, curated-themes (65 dark themes)
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
- return spawnSync(resolved, args, buildSpawnOptions(options));
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
- console.log(` ${green("✓")} [${pkg.category}] ${pkg.id.padEnd(20)} ${dim(pkg.source)}`);
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 = true } = {}) => {
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) => /^git:github\.com\/[^/@]+\/[^@\s]+$/.test(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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tommy-ca/lazypi",
3
- "version": "0.10.2",
3
+ "version": "0.10.4",
4
4
  "description": "Opinionated one-shot installer for a full-featured Pi coding agent setup.",
5
5
  "license": "MIT",
6
6
  "repository": {