@tommy-ca/lazypi 0.10.1 → 0.10.3
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 +101 -35
- 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
|
@@ -96,6 +96,25 @@ function renderLogo() {
|
|
|
96
96
|
// ---------------------------------------------------------------------------
|
|
97
97
|
const KNOWN_COMMANDS = new Set(["install", "status", "update", "doctor", "remove"]);
|
|
98
98
|
|
|
99
|
+
function assignSelector(flags, key, raw) {
|
|
100
|
+
const list = parseList(raw);
|
|
101
|
+
if (list.length === 0) {
|
|
102
|
+
flags.usageError ??= `--${key} requires a non-empty list`;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
flags[key] = list;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function consumeSelector(args, i, flags, key) {
|
|
109
|
+
const value = args[i + 1];
|
|
110
|
+
if (value == null || value.startsWith("-")) {
|
|
111
|
+
flags.usageError ??= `--${key} requires a non-empty list`;
|
|
112
|
+
return i;
|
|
113
|
+
}
|
|
114
|
+
assignSelector(flags, key, value);
|
|
115
|
+
return i + 1;
|
|
116
|
+
}
|
|
117
|
+
|
|
99
118
|
function parseArgs(args) {
|
|
100
119
|
const flags = {
|
|
101
120
|
command: "install",
|
|
@@ -107,6 +126,7 @@ function parseArgs(args) {
|
|
|
107
126
|
only: null,
|
|
108
127
|
except: null,
|
|
109
128
|
targets: [],
|
|
129
|
+
usageError: null,
|
|
110
130
|
};
|
|
111
131
|
|
|
112
132
|
let i = 0;
|
|
@@ -121,10 +141,10 @@ function parseArgs(args) {
|
|
|
121
141
|
else if (arg === "-y" || arg === "--yes") flags.yes = true;
|
|
122
142
|
else if (arg === "-V" || arg === "--version") flags.version = true;
|
|
123
143
|
else if (arg === "-h" || arg === "--help") flags.help = true;
|
|
124
|
-
else if (arg === "--only")
|
|
125
|
-
else if (arg.startsWith("--only=")) flags
|
|
126
|
-
else if (arg === "--except")
|
|
127
|
-
else if (arg.startsWith("--except=")) flags
|
|
144
|
+
else if (arg === "--only") i = consumeSelector(args, i, flags, "only");
|
|
145
|
+
else if (arg.startsWith("--only=")) assignSelector(flags, "only", arg.slice("--only=".length));
|
|
146
|
+
else if (arg === "--except") i = consumeSelector(args, i, flags, "except");
|
|
147
|
+
else if (arg.startsWith("--except=")) assignSelector(flags, "except", arg.slice("--except=".length));
|
|
128
148
|
else if (flags.command === "remove" && !arg.startsWith("-")) flags.targets.push(arg);
|
|
129
149
|
else {
|
|
130
150
|
console.error(red(`Unknown argument: ${arg}`));
|
|
@@ -134,6 +154,10 @@ function parseArgs(args) {
|
|
|
134
154
|
}
|
|
135
155
|
}
|
|
136
156
|
|
|
157
|
+
if (flags.only && flags.except) {
|
|
158
|
+
flags.usageError ??= "Use either --only or --except, not both";
|
|
159
|
+
}
|
|
160
|
+
|
|
137
161
|
return flags;
|
|
138
162
|
}
|
|
139
163
|
|
|
@@ -151,9 +175,10 @@ function validateSelectors(list, label) {
|
|
|
151
175
|
if (bad.length > 0) {
|
|
152
176
|
console.error(red(`Unknown ${label}: ${bad.join(", ")}`));
|
|
153
177
|
console.error(`Valid categories: ${CATEGORIES.join(", ")}`);
|
|
154
|
-
console.error(`Valid package ids:
|
|
155
|
-
|
|
178
|
+
console.error(`Valid package ids: ${[...ids].join(", ")}`);
|
|
179
|
+
return false;
|
|
156
180
|
}
|
|
181
|
+
return true;
|
|
157
182
|
}
|
|
158
183
|
|
|
159
184
|
function matchesSelector(pkg, selectors) {
|
|
@@ -162,11 +187,11 @@ function matchesSelector(pkg, selectors) {
|
|
|
162
187
|
|
|
163
188
|
function resolveSelection(flags) {
|
|
164
189
|
if (flags.only) {
|
|
165
|
-
validateSelectors(flags.only, "--only");
|
|
190
|
+
if (!validateSelectors(flags.only, "--only")) return null;
|
|
166
191
|
return new Set(PACKAGES.filter((p) => matchesSelector(p, flags.only)).map((p) => p.id));
|
|
167
192
|
}
|
|
168
193
|
if (flags.except) {
|
|
169
|
-
validateSelectors(flags.except, "--except");
|
|
194
|
+
if (!validateSelectors(flags.except, "--except")) return null;
|
|
170
195
|
return new Set(PACKAGES.filter((p) => !matchesSelector(p, flags.except)).map((p) => p.id));
|
|
171
196
|
}
|
|
172
197
|
return new Set(PACKAGES.filter((p) => p.category === "core").map((p) => p.id));
|
|
@@ -206,8 +231,8 @@ ${bold("Default behaviour:")}
|
|
|
206
231
|
${bold("Categories:")}
|
|
207
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
|
|
208
233
|
optional only when selected (--only optional, picker, or the everything flow): lsp diagnostics, interactive-shell overlays, autoresearch loops, todo tracking, markdown memory
|
|
209
|
-
|
|
210
|
-
|
|
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.)
|
|
211
236
|
|
|
212
237
|
${bold("Examples:")}
|
|
213
238
|
npx @tommy-ca/lazypi # core catalog (interactive picker on a TTY)
|
|
@@ -224,18 +249,13 @@ ${bold("Examples:")}
|
|
|
224
249
|
// Pi / settings plumbing
|
|
225
250
|
// ---------------------------------------------------------------------------
|
|
226
251
|
const VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// shell, so we route spawned commands through the platform shell there while
|
|
230
|
-
// keeping direct execution on Unix.
|
|
231
|
-
export function buildSpawnOptions(options = {}, platformName = platform()) {
|
|
232
|
-
const resolved = { ...options };
|
|
233
|
-
if (platformName === "win32" && resolved.shell == null) resolved.shell = true;
|
|
234
|
-
return resolved;
|
|
252
|
+
export function buildSpawnOptions(options = {}, _platformName = platform()) {
|
|
253
|
+
return { ...options };
|
|
235
254
|
}
|
|
236
255
|
|
|
237
256
|
function spawnCommand(command, args = [], options = {}) {
|
|
238
|
-
|
|
257
|
+
const resolved = command === "pi" || command === "npm" ? commandPath(command) ?? command : command;
|
|
258
|
+
return spawnSync(resolved, args, buildSpawnOptions(options));
|
|
239
259
|
}
|
|
240
260
|
|
|
241
261
|
function hasCmd(name) {
|
|
@@ -296,11 +316,19 @@ function writeSettings(local, mutate) {
|
|
|
296
316
|
return { ok: true, path: current.path, backup, changed: true };
|
|
297
317
|
}
|
|
298
318
|
|
|
299
|
-
function writeSubagentOverrides(local) {
|
|
319
|
+
export function writeSubagentOverrides(local) {
|
|
300
320
|
return writeSettings(local, (settings) => {
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
321
|
+
const existing = settings.subagents?.agentOverrides ?? {};
|
|
322
|
+
const overrides = { ...existing };
|
|
323
|
+
let changed = false;
|
|
324
|
+
for (const name of SUBAGENT_BUILTIN_MODELS) {
|
|
325
|
+
const current = overrides[name];
|
|
326
|
+
if (current && typeof current === "object" && !Array.isArray(current) && current.model === "") continue;
|
|
327
|
+
overrides[name] = { ...(current && typeof current === "object" && !Array.isArray(current) ? current : {}), model: "" };
|
|
328
|
+
changed = true;
|
|
329
|
+
}
|
|
330
|
+
if (!changed) return false;
|
|
331
|
+
settings.subagents = { ...(settings.subagents ?? {}), agentOverrides: overrides };
|
|
304
332
|
return true;
|
|
305
333
|
});
|
|
306
334
|
}
|
|
@@ -325,12 +353,16 @@ function readInstalledSources(local) {
|
|
|
325
353
|
|
|
326
354
|
function commandPath(name) {
|
|
327
355
|
const command = platform() === "win32" ? "where" : "which";
|
|
328
|
-
const probe =
|
|
356
|
+
const probe = spawnSync(command, [name], buildSpawnOptions({ encoding: "utf8" }));
|
|
329
357
|
if (probe.status !== 0) return null;
|
|
330
358
|
const first = String(probe.stdout ?? "").split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
331
359
|
return first || null;
|
|
332
360
|
}
|
|
333
361
|
|
|
362
|
+
function printSettingsParseError(current) {
|
|
363
|
+
console.error(red(`Could not parse ${current.path} — ${current.error}`));
|
|
364
|
+
}
|
|
365
|
+
|
|
334
366
|
function legacySourcesForPackage(pkg) {
|
|
335
367
|
return Array.isArray(pkg.legacySources) ? pkg.legacySources : [];
|
|
336
368
|
}
|
|
@@ -499,6 +531,11 @@ async function ensurePi(flags) {
|
|
|
499
531
|
if (hasCmd("pi")) return true;
|
|
500
532
|
|
|
501
533
|
log.warn("Could not find the `pi` command on PATH.");
|
|
534
|
+
if (!flags.yes && !isInteractive()) {
|
|
535
|
+
log.error("Install Pi first (`npm install -g @earendil-works/pi-coding-agent`), then re-run `npx @tommy-ca/lazypi`.");
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
|
|
502
539
|
const ok = flags.yes || (await confirm("Install Pi now with `npm install -g @earendil-works/pi-coding-agent`?", true));
|
|
503
540
|
if (!ok) {
|
|
504
541
|
log.error("Install Pi first, then re-run `npx @tommy-ca/lazypi`.");
|
|
@@ -524,6 +561,7 @@ async function ensurePi(flags) {
|
|
|
524
561
|
// ---------------------------------------------------------------------------
|
|
525
562
|
async function cmdInstall(flags) {
|
|
526
563
|
let selectedIds = resolveSelection(flags);
|
|
564
|
+
if (!selectedIds) return 2;
|
|
527
565
|
|
|
528
566
|
const usedSelectionFlag = Boolean(flags.only || flags.except);
|
|
529
567
|
const interactive = !flags.yes && !usedSelectionFlag && isInteractive();
|
|
@@ -532,6 +570,18 @@ async function cmdInstall(flags) {
|
|
|
532
570
|
console.log(renderLogo());
|
|
533
571
|
intro(bold("LazyPi"));
|
|
534
572
|
}
|
|
573
|
+
|
|
574
|
+
const settings = readSettings(flags.local);
|
|
575
|
+
if (settings.error) {
|
|
576
|
+
if (interactive) {
|
|
577
|
+
log.error(`Could not parse ${settings.path} — ${settings.error}`);
|
|
578
|
+
outro(red("Aborted."));
|
|
579
|
+
} else {
|
|
580
|
+
printSettingsParseError(settings);
|
|
581
|
+
}
|
|
582
|
+
return 2;
|
|
583
|
+
}
|
|
584
|
+
|
|
535
585
|
if (!(await ensurePi(flags))) return 127;
|
|
536
586
|
|
|
537
587
|
if (interactive) {
|
|
@@ -550,8 +600,7 @@ async function cmdInstall(flags) {
|
|
|
550
600
|
return 0;
|
|
551
601
|
}
|
|
552
602
|
|
|
553
|
-
const { sources: installedSources
|
|
554
|
-
if (settingsError) log.warn(`Could not parse ${settingsPath(flags.local)} — ${settingsError}`);
|
|
603
|
+
const { sources: installedSources } = readInstalledSources(flags.local);
|
|
555
604
|
|
|
556
605
|
const toInstall = selected.filter((pkg) => {
|
|
557
606
|
return !isPackageInstalled(pkg, installedSources);
|
|
@@ -706,7 +755,7 @@ function cmdStatus(flags) {
|
|
|
706
755
|
console.log(yellow(" (not found — Pi has not written settings yet)"));
|
|
707
756
|
} else if (error) {
|
|
708
757
|
console.error(red(` could not parse: ${error}`));
|
|
709
|
-
return
|
|
758
|
+
return 2;
|
|
710
759
|
}
|
|
711
760
|
|
|
712
761
|
const installed = PACKAGES.filter((pkg) => packageInstallStatus(pkg, sources).installed);
|
|
@@ -719,7 +768,8 @@ function cmdStatus(flags) {
|
|
|
719
768
|
printHeader(`Installed from LazyPi catalog (${installed.length}/${PACKAGES.length}):`);
|
|
720
769
|
if (installed.length === 0) console.log(dim(" none"));
|
|
721
770
|
for (const pkg of installed) {
|
|
722
|
-
|
|
771
|
+
const installedSource = [...sources].find((src) => sourcesMatch(pkg.source, src)) ?? pkg.source;
|
|
772
|
+
console.log(` ${green("✓")} [${pkg.category}] ${pkg.id.padEnd(20)} ${dim(installedSource)}`);
|
|
723
773
|
}
|
|
724
774
|
|
|
725
775
|
printHeader(`Installed with legacy catalog sources (${legacy.length}):`);
|
|
@@ -746,14 +796,14 @@ function cmdStatus(flags) {
|
|
|
746
796
|
// update
|
|
747
797
|
// ---------------------------------------------------------------------------
|
|
748
798
|
async function cmdUpdate(flags) {
|
|
749
|
-
if (!(await ensurePi(flags))) return 127;
|
|
750
|
-
|
|
751
799
|
const settings = readSettings(flags.local);
|
|
752
800
|
if (settings.error) {
|
|
753
|
-
|
|
754
|
-
return
|
|
801
|
+
printSettingsParseError(settings);
|
|
802
|
+
return 2;
|
|
755
803
|
}
|
|
756
804
|
|
|
805
|
+
if (!(await ensurePi(flags))) return 127;
|
|
806
|
+
|
|
757
807
|
console.log(bold("pi update"));
|
|
758
808
|
|
|
759
809
|
return spawnCommand("pi", flags.local ? ["update", "--extensions"] : ["update"], { stdio: "inherit" }).status ?? 1;
|
|
@@ -763,6 +813,12 @@ async function cmdUpdate(flags) {
|
|
|
763
813
|
// doctor
|
|
764
814
|
// ---------------------------------------------------------------------------
|
|
765
815
|
function cmdDoctor(flags) {
|
|
816
|
+
const settings = readSettings(flags.local);
|
|
817
|
+
if (settings.error) {
|
|
818
|
+
printSettingsParseError(settings);
|
|
819
|
+
return 2;
|
|
820
|
+
}
|
|
821
|
+
|
|
766
822
|
let problems = 0;
|
|
767
823
|
let warnings = 0;
|
|
768
824
|
const pass = (msg) => console.log(` ${green("✓")} ${msg}`);
|
|
@@ -831,12 +887,18 @@ function cmdDoctor(flags) {
|
|
|
831
887
|
// remove
|
|
832
888
|
// ---------------------------------------------------------------------------
|
|
833
889
|
async function cmdRemove(flags, targets) {
|
|
890
|
+
const installed = readInstalledSources(flags.local);
|
|
891
|
+
if (installed.error) {
|
|
892
|
+
printSettingsParseError(installed);
|
|
893
|
+
return 2;
|
|
894
|
+
}
|
|
895
|
+
|
|
834
896
|
if (targets.length === 0) {
|
|
835
897
|
if (!isInteractive()) {
|
|
836
898
|
console.error(red("Usage: npx @tommy-ca/lazypi remove <id|source> [...]"));
|
|
837
899
|
return 2;
|
|
838
900
|
}
|
|
839
|
-
const { sources } =
|
|
901
|
+
const { sources } = installed;
|
|
840
902
|
const installedPkgs = PACKAGES.filter((p) => isPackagePresent(p, sources));
|
|
841
903
|
if (installedPkgs.length === 0) {
|
|
842
904
|
console.log(yellow("No catalog packages are installed."));
|
|
@@ -860,10 +922,9 @@ async function cmdRemove(flags, targets) {
|
|
|
860
922
|
targets = picked;
|
|
861
923
|
}
|
|
862
924
|
|
|
863
|
-
const { sources: installedSources } =
|
|
925
|
+
const { sources: installedSources } = installed;
|
|
864
926
|
let exitCode = 0;
|
|
865
927
|
for (const target of targets) {
|
|
866
|
-
// Resolve a catalog id to its source string, or pass through raw sources
|
|
867
928
|
const pkg = PACKAGES.find((p) => p.id === target);
|
|
868
929
|
const source = pkg ? pkg.source : target;
|
|
869
930
|
|
|
@@ -896,6 +957,11 @@ async function main() {
|
|
|
896
957
|
console.log(`@tommy-ca/lazypi ${VERSION}`);
|
|
897
958
|
return 0;
|
|
898
959
|
}
|
|
960
|
+
if (flags.usageError) {
|
|
961
|
+
console.error(red(flags.usageError));
|
|
962
|
+
printHelp();
|
|
963
|
+
return 2;
|
|
964
|
+
}
|
|
899
965
|
if (flags.help) {
|
|
900
966
|
printHelp();
|
|
901
967
|
return flags.badArg ? 2 : 0;
|