@pify/cli 0.1.2 → 0.2.0
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 +11 -1
- package/catalog.json +23 -23
- package/dist/cli.js +439 -124
- package/dist/index.d.ts +56 -4
- package/dist/index.js +343 -25
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
import { parseArgs } from "util";
|
|
5
5
|
|
|
6
6
|
// src/pi.ts
|
|
7
|
-
import { readFileSync, realpathSync } from "fs";
|
|
7
|
+
import { accessSync as accessSync2, constants as constants2, readFileSync, realpathSync, statSync as statSync2 } from "fs";
|
|
8
8
|
import { homedir } from "os";
|
|
9
|
-
import { join as join2 } from "path";
|
|
9
|
+
import { dirname, join as join2 } from "path";
|
|
10
10
|
|
|
11
11
|
// src/exec.ts
|
|
12
12
|
import { spawn, spawnSync } from "child_process";
|
|
@@ -97,6 +97,17 @@ function runInherit(command, args) {
|
|
|
97
97
|
child.on("close", (code) => resolve2(code ?? 1));
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
|
+
function runOfficialInstaller(kind) {
|
|
101
|
+
const [cmd, argv] = kind === "windows" ? [
|
|
102
|
+
"powershell",
|
|
103
|
+
["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", "irm https://pi.dev/install.ps1 | iex"]
|
|
104
|
+
] : ["sh", ["-c", "curl -fsSL https://pi.dev/install.sh | sh"]];
|
|
105
|
+
return new Promise((resolve2, reject) => {
|
|
106
|
+
const child = spawn(cmd, argv, { stdio: "inherit", windowsHide: false });
|
|
107
|
+
child.on("error", reject);
|
|
108
|
+
child.on("close", (code) => resolve2(code ?? 1));
|
|
109
|
+
});
|
|
110
|
+
}
|
|
100
111
|
function which(command) {
|
|
101
112
|
const dirs = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
|
|
102
113
|
if (isWindows) {
|
|
@@ -258,38 +269,107 @@ function guardNpmManaged(binPath) {
|
|
|
258
269
|
);
|
|
259
270
|
}
|
|
260
271
|
}
|
|
272
|
+
function officialInstallerCommand() {
|
|
273
|
+
return isWindows ? 'powershell -c "irm https://pi.dev/install.ps1 | iex"' : "curl -fsSL https://pi.dev/install.sh | sh";
|
|
274
|
+
}
|
|
275
|
+
function writableOrCreatable(path) {
|
|
276
|
+
let current = path;
|
|
277
|
+
for (; ; ) {
|
|
278
|
+
try {
|
|
279
|
+
statSync2(current);
|
|
280
|
+
break;
|
|
281
|
+
} catch {
|
|
282
|
+
const parent = dirname(current);
|
|
283
|
+
if (parent === current) return false;
|
|
284
|
+
current = parent;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
if (!statSync2(current).isDirectory()) return false;
|
|
289
|
+
accessSync2(current, constants2.W_OK);
|
|
290
|
+
return true;
|
|
291
|
+
} catch {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function npmInstallPiArgs(spec, npmMajor, prefix) {
|
|
296
|
+
const args = ["install", "-g", "--ignore-scripts"];
|
|
297
|
+
if (npmMajor >= 11) args.push("--min-release-age=0");
|
|
298
|
+
args.push("--no-fund", "--no-audit");
|
|
299
|
+
if (prefix) args.push("--prefix", prefix);
|
|
300
|
+
args.push(spec);
|
|
301
|
+
return args;
|
|
302
|
+
}
|
|
303
|
+
function npmMajorVersion() {
|
|
304
|
+
const res = runCapture("npm", ["--version"]);
|
|
305
|
+
const major = Number.parseInt(res.stdout.trim().split(".")[0] ?? "", 10);
|
|
306
|
+
return Number.isNaN(major) ? 0 : major;
|
|
307
|
+
}
|
|
261
308
|
async function installPi(version) {
|
|
262
309
|
assertNodeVersion();
|
|
263
|
-
if (!which("npm")) {
|
|
264
|
-
throw environmentError(
|
|
265
|
-
"npm was not found on PATH.",
|
|
266
|
-
"Install Node.js (which bundles npm) from https://nodejs.org and retry."
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
310
|
if (process.env.PI_MANAGED_INSTALL_ROOT && !piStatus().installed) {
|
|
270
311
|
throw environmentError(
|
|
271
312
|
"PI_MANAGED_INSTALL_ROOT is set but pi is not installed.",
|
|
272
|
-
|
|
313
|
+
`Use the official installer: ${officialInstallerCommand()}`
|
|
273
314
|
);
|
|
274
315
|
}
|
|
275
316
|
const spec = version ? `${PI_PACKAGE}@${version}` : PI_PACKAGE;
|
|
276
|
-
|
|
277
|
-
const
|
|
317
|
+
const npmPath = which("npm");
|
|
318
|
+
const bunPath = which("bun");
|
|
319
|
+
if (!npmPath && !bunPath) {
|
|
320
|
+
throw environmentError(
|
|
321
|
+
"Neither npm nor bun was found on PATH.",
|
|
322
|
+
`Run the official installer (it can install Node too): ${officialInstallerCommand()}`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
let command;
|
|
326
|
+
let args;
|
|
327
|
+
let localPrefix = null;
|
|
328
|
+
if (npmPath) {
|
|
329
|
+
command = "npm";
|
|
330
|
+
if (!isWindows) {
|
|
331
|
+
const prefixRes = runCapture("npm", ["prefix", "-g"]);
|
|
332
|
+
const prefix = prefixRes.status === 0 ? prefixRes.stdout.trim() : "";
|
|
333
|
+
const prefixWritable = prefix !== "" && writableOrCreatable(join2(prefix, "lib", "node_modules")) && writableOrCreatable(join2(prefix, "bin"));
|
|
334
|
+
if (prefix && !prefixWritable) {
|
|
335
|
+
let staleGlobalPi = false;
|
|
336
|
+
try {
|
|
337
|
+
statSync2(join2(prefix, "bin", "pi"));
|
|
338
|
+
staleGlobalPi = true;
|
|
339
|
+
} catch {
|
|
340
|
+
}
|
|
341
|
+
if (staleGlobalPi) {
|
|
342
|
+
throw environmentError(
|
|
343
|
+
`npm's global directory is not writable (${prefix}) and pi is already installed there.`,
|
|
344
|
+
`Update or remove it first: sudo npm install -g --ignore-scripts ${PI_PACKAGE}`
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
localPrefix = join2(homedir(), ".local");
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
args = npmInstallPiArgs(spec, npmMajorVersion(), localPrefix ?? void 0);
|
|
351
|
+
} else {
|
|
352
|
+
command = "bun";
|
|
353
|
+
args = ["add", "-g", "--ignore-scripts", spec];
|
|
354
|
+
}
|
|
355
|
+
step(`${command} ${args.join(" ")}`);
|
|
356
|
+
const code = await runInherit(command, args);
|
|
278
357
|
if (code !== 0) {
|
|
279
358
|
throw new PifyError(
|
|
280
|
-
|
|
359
|
+
`${command} ${args[0]} exited with code ${code}.`,
|
|
281
360
|
ExitCode.SUBPROCESS,
|
|
282
|
-
isWindows ? "If this is a permissions or file-lock error, close running pi sessions and retry from an elevated terminal." :
|
|
361
|
+
isWindows ? "If this is a permissions or file-lock error, close running pi sessions and retry from an elevated terminal." : `If the problem persists, try the official installer: ${officialInstallerCommand()}`
|
|
283
362
|
);
|
|
284
363
|
}
|
|
285
364
|
const st = piStatus();
|
|
286
365
|
if (!st.installed) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
"Add npm's global prefix to PATH (npm prefix -g) and open a new terminal."
|
|
290
|
-
);
|
|
366
|
+
const pathHint = localPrefix ? `Add ${join2(localPrefix, "bin")} to PATH and open a new terminal.` : "Add the global bin directory to PATH (npm prefix -g) and open a new terminal.";
|
|
367
|
+
throw environmentError("pi was installed but is not resolvable on PATH.", pathHint);
|
|
291
368
|
}
|
|
292
369
|
success(`pi ${st.version ?? ""} installed.`.replace(" ", " "));
|
|
370
|
+
if (localPrefix) {
|
|
371
|
+
hint(`Installed under ${localPrefix} because npm's global prefix is not writable.`);
|
|
372
|
+
}
|
|
293
373
|
}
|
|
294
374
|
function requirePi() {
|
|
295
375
|
if (!piStatus().installed) {
|
|
@@ -349,12 +429,19 @@ function installedVersionOnDisk(name, scope) {
|
|
|
349
429
|
|
|
350
430
|
// src/commands/setup.ts
|
|
351
431
|
async function setup(opts) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
432
|
+
if (opts.installer) {
|
|
433
|
+
if (!process.stdin.isTTY || process.env.CI) {
|
|
434
|
+
throw environmentError(
|
|
435
|
+
"The official installer is interactive and needs a terminal.",
|
|
436
|
+
"In CI, use plain `pify setup` (non-interactive npm path) instead."
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
step(officialInstallerCommand());
|
|
440
|
+
const code = await runOfficialInstaller(isWindows ? "windows" : "posix");
|
|
441
|
+
if (code !== 0) {
|
|
442
|
+
throw new PifyError(`The official installer exited with code ${code}.`, ExitCode.SUBPROCESS);
|
|
443
|
+
}
|
|
444
|
+
return 0;
|
|
358
445
|
}
|
|
359
446
|
const st = piStatus();
|
|
360
447
|
if (st.installed && !opts.force && !opts.piVersion) {
|
|
@@ -381,10 +468,10 @@ async function setup(opts) {
|
|
|
381
468
|
// src/catalog.ts
|
|
382
469
|
import { readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
|
|
383
470
|
import { homedir as homedir2 } from "os";
|
|
384
|
-
import { dirname, join as join3 } from "path";
|
|
471
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
385
472
|
import { fileURLToPath } from "url";
|
|
386
473
|
function bundledCatalogPath() {
|
|
387
|
-
return join3(
|
|
474
|
+
return join3(dirname2(fileURLToPath(import.meta.url)), "..", "catalog.json");
|
|
388
475
|
}
|
|
389
476
|
function cachePath() {
|
|
390
477
|
return join3(homedir2(), ".pify", "catalog.json");
|
|
@@ -459,7 +546,7 @@ function readCache() {
|
|
|
459
546
|
}
|
|
460
547
|
function writeCache(catalog) {
|
|
461
548
|
try {
|
|
462
|
-
mkdirSync(
|
|
549
|
+
mkdirSync(dirname2(cachePath()), { recursive: true });
|
|
463
550
|
writeFileSync(cachePath(), JSON.stringify({ fetchedAt: Date.now(), catalog }, null, 2));
|
|
464
551
|
} catch {
|
|
465
552
|
}
|
|
@@ -675,12 +762,12 @@ async function update(targets, opts) {
|
|
|
675
762
|
const updateEverything = targets.length === 0;
|
|
676
763
|
const piOnly = targets.length === 1 && (targets[0] === "pi" || targets[0] === "self");
|
|
677
764
|
const installed = installedPifyPackages();
|
|
678
|
-
let
|
|
765
|
+
let packageNames2 = [];
|
|
679
766
|
if (updateEverything) {
|
|
680
|
-
|
|
767
|
+
packageNames2 = [...installed.keys()].sort();
|
|
681
768
|
} else if (!piOnly) {
|
|
682
|
-
|
|
683
|
-
for (const name of
|
|
769
|
+
packageNames2 = targets.map(resolvePifyName);
|
|
770
|
+
for (const name of packageNames2) {
|
|
684
771
|
if (!installed.has(name)) {
|
|
685
772
|
throw notFoundError(`${name} is not installed.`, `pify install ${name}`);
|
|
686
773
|
}
|
|
@@ -688,7 +775,7 @@ async function update(targets, opts) {
|
|
|
688
775
|
}
|
|
689
776
|
if (opts.dryRun) {
|
|
690
777
|
if (updateEverything || piOnly) out("pi update");
|
|
691
|
-
for (const name of
|
|
778
|
+
for (const name of packageNames2) out(`pi update npm:@pify/${name}`);
|
|
692
779
|
if (updateEverything) out("refresh catalog");
|
|
693
780
|
return 0;
|
|
694
781
|
}
|
|
@@ -702,20 +789,20 @@ async function update(targets, opts) {
|
|
|
702
789
|
if (code !== 0) failed.push("pi");
|
|
703
790
|
}
|
|
704
791
|
}
|
|
705
|
-
if (
|
|
792
|
+
if (packageNames2.length > 0 && !piStatus().installed) {
|
|
706
793
|
throw new PifyError(
|
|
707
794
|
"pi is required to update packages.",
|
|
708
795
|
ExitCode.ENVIRONMENT,
|
|
709
796
|
"Run pify setup first."
|
|
710
797
|
);
|
|
711
798
|
}
|
|
712
|
-
for (const name of
|
|
799
|
+
for (const name of packageNames2) {
|
|
713
800
|
const args = ["update", `npm:@pify/${name}`];
|
|
714
801
|
step(`pi ${args.join(" ")}`);
|
|
715
802
|
const code = await delegate(args);
|
|
716
803
|
if (code !== 0) failed.push(name);
|
|
717
804
|
}
|
|
718
|
-
if (updateEverything &&
|
|
805
|
+
if (updateEverything && packageNames2.length === 0) {
|
|
719
806
|
hint("No @pify packages installed; nothing more to update.");
|
|
720
807
|
}
|
|
721
808
|
if (updateEverything) {
|
|
@@ -822,6 +909,14 @@ async function doctor(opts) {
|
|
|
822
909
|
gitPath ? "ok" : "warn",
|
|
823
910
|
gitPath ?? "git not found - pi's git: package sources will not work"
|
|
824
911
|
);
|
|
912
|
+
if (isWindows) {
|
|
913
|
+
const bashPath = which("bash");
|
|
914
|
+
push(
|
|
915
|
+
"bash",
|
|
916
|
+
bashPath ? "ok" : "warn",
|
|
917
|
+
bashPath ?? "bash not found - pi needs Git Bash on Windows; run pify setup --installer"
|
|
918
|
+
);
|
|
919
|
+
}
|
|
825
920
|
const pi = piStatus();
|
|
826
921
|
if (!pi.installed) {
|
|
827
922
|
push("pi", "fail", "not installed - run: pify setup");
|
|
@@ -1100,6 +1195,244 @@ async function init(dirArg, opts) {
|
|
|
1100
1195
|
return 0;
|
|
1101
1196
|
}
|
|
1102
1197
|
|
|
1198
|
+
// src/registry.ts
|
|
1199
|
+
var GLOBAL_FLAGS = [
|
|
1200
|
+
{ name: "help", short: "h", description: "Show help" },
|
|
1201
|
+
{ name: "version", short: "v", description: "Print the pify version" },
|
|
1202
|
+
{ name: "no-color", description: "Disable colored output" }
|
|
1203
|
+
];
|
|
1204
|
+
var COMMAND_SPECS = [
|
|
1205
|
+
{
|
|
1206
|
+
name: "setup",
|
|
1207
|
+
aliases: [],
|
|
1208
|
+
description: "Install the pi coding agent (safe to re-run)",
|
|
1209
|
+
positional: "none",
|
|
1210
|
+
flags: [
|
|
1211
|
+
{ name: "force", description: "Reinstall pi even if present" },
|
|
1212
|
+
{ name: "pi-version", takesValue: true, description: "Install an exact pi version" },
|
|
1213
|
+
{ name: "installer", description: "Run pi's official interactive installer" }
|
|
1214
|
+
]
|
|
1215
|
+
},
|
|
1216
|
+
{
|
|
1217
|
+
name: "install",
|
|
1218
|
+
aliases: ["i", "add"],
|
|
1219
|
+
description: "Install @pify packages by short name",
|
|
1220
|
+
positional: "packages",
|
|
1221
|
+
flags: [
|
|
1222
|
+
{ name: "local", short: "l", description: "Project scope" },
|
|
1223
|
+
{ name: "approve", short: "a", description: "Forward --approve to pi" },
|
|
1224
|
+
{ name: "dry-run", description: "Print the pi commands without running them" }
|
|
1225
|
+
]
|
|
1226
|
+
},
|
|
1227
|
+
{
|
|
1228
|
+
name: "remove",
|
|
1229
|
+
aliases: ["rm", "uninstall"],
|
|
1230
|
+
description: "Remove installed @pify packages",
|
|
1231
|
+
positional: "packages",
|
|
1232
|
+
flags: [
|
|
1233
|
+
{ name: "local", short: "l", description: "Project scope" },
|
|
1234
|
+
{ name: "approve", short: "a", description: "Forward --approve to pi" },
|
|
1235
|
+
{ name: "dry-run", description: "Print the pi commands without running them" }
|
|
1236
|
+
]
|
|
1237
|
+
},
|
|
1238
|
+
{
|
|
1239
|
+
name: "update",
|
|
1240
|
+
aliases: ["up"],
|
|
1241
|
+
description: "Update pi and installed @pify packages",
|
|
1242
|
+
positional: "updateTargets",
|
|
1243
|
+
flags: [
|
|
1244
|
+
{ name: "catalog", description: "Refresh the catalog cache only" },
|
|
1245
|
+
{ name: "dry-run", description: "Print the update plan without executing" }
|
|
1246
|
+
]
|
|
1247
|
+
},
|
|
1248
|
+
{
|
|
1249
|
+
name: "list",
|
|
1250
|
+
aliases: ["ls"],
|
|
1251
|
+
description: "Show the @pify catalog with install state",
|
|
1252
|
+
positional: "none",
|
|
1253
|
+
flags: [{ name: "json", description: "Machine-readable output" }]
|
|
1254
|
+
},
|
|
1255
|
+
{
|
|
1256
|
+
name: "doctor",
|
|
1257
|
+
aliases: [],
|
|
1258
|
+
description: "Diagnose the local pi/pify environment",
|
|
1259
|
+
positional: "none",
|
|
1260
|
+
flags: [{ name: "json", description: "Machine-readable output" }]
|
|
1261
|
+
},
|
|
1262
|
+
{
|
|
1263
|
+
name: "init",
|
|
1264
|
+
aliases: [],
|
|
1265
|
+
description: "Scaffold a new Pi Package",
|
|
1266
|
+
positional: "directory",
|
|
1267
|
+
flags: [
|
|
1268
|
+
{ name: "name", takesValue: true, description: "npm package name" },
|
|
1269
|
+
{ name: "description", takesValue: true, description: "Package description" }
|
|
1270
|
+
]
|
|
1271
|
+
},
|
|
1272
|
+
{
|
|
1273
|
+
name: "completions",
|
|
1274
|
+
aliases: [],
|
|
1275
|
+
description: "Print a shell completion script",
|
|
1276
|
+
positional: "shell",
|
|
1277
|
+
flags: []
|
|
1278
|
+
}
|
|
1279
|
+
];
|
|
1280
|
+
var COMPLETION_SHELLS = ["bash", "zsh", "fish", "powershell"];
|
|
1281
|
+
function parseArgsOptionsFor(spec) {
|
|
1282
|
+
const options = {};
|
|
1283
|
+
for (const flag of spec.flags) {
|
|
1284
|
+
options[flag.name] = flag.takesValue ? { type: "string", ...flag.short ? { short: flag.short } : {} } : { type: "boolean", default: false, ...flag.short ? { short: flag.short } : {} };
|
|
1285
|
+
}
|
|
1286
|
+
return options;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// src/completions.ts
|
|
1290
|
+
function packageNames() {
|
|
1291
|
+
return loadBundledCatalog().packages.map((p) => p.name);
|
|
1292
|
+
}
|
|
1293
|
+
function allNames(spec) {
|
|
1294
|
+
return [spec.name, ...spec.aliases];
|
|
1295
|
+
}
|
|
1296
|
+
function flagTokens(spec) {
|
|
1297
|
+
return spec.flags.flatMap((f) => [`--${f.name}`, ...f.short ? [`-${f.short}`] : []]);
|
|
1298
|
+
}
|
|
1299
|
+
function positionalWords(spec) {
|
|
1300
|
+
switch (spec.positional) {
|
|
1301
|
+
case "packages":
|
|
1302
|
+
return packageNames();
|
|
1303
|
+
case "updateTargets":
|
|
1304
|
+
return ["pi", ...packageNames()];
|
|
1305
|
+
case "shell":
|
|
1306
|
+
return [...COMPLETION_SHELLS];
|
|
1307
|
+
default:
|
|
1308
|
+
return [];
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
function bashScript() {
|
|
1312
|
+
const commands = COMMAND_SPECS.flatMap(allNames).join(" ");
|
|
1313
|
+
const cases = COMMAND_SPECS.map((spec) => {
|
|
1314
|
+
const words = [...positionalWords(spec), ...flagTokens(spec)].join(" ");
|
|
1315
|
+
return ` ${allNames(spec).join("|")}) words="${words}" ;;`;
|
|
1316
|
+
}).join("\n");
|
|
1317
|
+
return `# bash completion for pify \u2014 generated by \`pify completions bash\`
|
|
1318
|
+
# Install: eval "$(pify completions bash)" (or save to a bash-completion dir)
|
|
1319
|
+
_pify_completions() {
|
|
1320
|
+
local cur words
|
|
1321
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
1322
|
+
if [ "$COMP_CWORD" -eq 1 ]; then
|
|
1323
|
+
COMPREPLY=($(compgen -W "${commands} ${GLOBAL_FLAGS.map((f) => `--${f.name}`).join(" ")}" -- "$cur"))
|
|
1324
|
+
return
|
|
1325
|
+
fi
|
|
1326
|
+
case "\${COMP_WORDS[1]}" in
|
|
1327
|
+
${cases}
|
|
1328
|
+
*) words="" ;;
|
|
1329
|
+
esac
|
|
1330
|
+
COMPREPLY=($(compgen -W "$words" -- "$cur"))
|
|
1331
|
+
}
|
|
1332
|
+
complete -F _pify_completions pify
|
|
1333
|
+
`;
|
|
1334
|
+
}
|
|
1335
|
+
function zshScript() {
|
|
1336
|
+
const commandDescs = COMMAND_SPECS.flatMap(
|
|
1337
|
+
(spec) => allNames(spec).map((n) => ` '${n}:${spec.description.replace(/'/g, "")}'`)
|
|
1338
|
+
).join("\n");
|
|
1339
|
+
const cases = COMMAND_SPECS.map((spec) => {
|
|
1340
|
+
const words = [...positionalWords(spec), ...flagTokens(spec)].map((w) => `'${w}'`).join(" ");
|
|
1341
|
+
return ` ${allNames(spec).join("|")}) _values 'value' ${words || "''"} ;;`;
|
|
1342
|
+
}).join("\n");
|
|
1343
|
+
return `#compdef pify
|
|
1344
|
+
# zsh completion for pify \u2014 generated by \`pify completions zsh\`
|
|
1345
|
+
# Install: eval "$(pify completions zsh)" (or save into a directory on $fpath)
|
|
1346
|
+
_pify() {
|
|
1347
|
+
local -a commands
|
|
1348
|
+
commands=(
|
|
1349
|
+
${commandDescs}
|
|
1350
|
+
)
|
|
1351
|
+
if (( CURRENT == 2 )); then
|
|
1352
|
+
_describe 'command' commands
|
|
1353
|
+
return
|
|
1354
|
+
fi
|
|
1355
|
+
case "$words[2]" in
|
|
1356
|
+
${cases}
|
|
1357
|
+
esac
|
|
1358
|
+
}
|
|
1359
|
+
_pify "$@"
|
|
1360
|
+
`;
|
|
1361
|
+
}
|
|
1362
|
+
function fishScript() {
|
|
1363
|
+
const lines = [
|
|
1364
|
+
`# fish completion for pify \u2014 generated by \`pify completions fish\``,
|
|
1365
|
+
`# Install: pify completions fish > ~/.config/fish/completions/pify.fish`,
|
|
1366
|
+
`complete -c pify -f`
|
|
1367
|
+
];
|
|
1368
|
+
for (const spec of COMMAND_SPECS) {
|
|
1369
|
+
for (const name of allNames(spec)) {
|
|
1370
|
+
lines.push(
|
|
1371
|
+
`complete -c pify -n __fish_use_subcommand -a ${name} -d '${spec.description.replace(/'/g, "")}'`
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
const condition = `__fish_seen_subcommand_from ${allNames(spec).join(" ")}`;
|
|
1375
|
+
const words = positionalWords(spec);
|
|
1376
|
+
if (words.length > 0) {
|
|
1377
|
+
lines.push(`complete -c pify -n '${condition}' -a '${words.join(" ")}'`);
|
|
1378
|
+
}
|
|
1379
|
+
for (const flag of spec.flags) {
|
|
1380
|
+
const short = flag.short ? ` -s ${flag.short}` : "";
|
|
1381
|
+
lines.push(
|
|
1382
|
+
`complete -c pify -n '${condition}' -l ${flag.name}${short} -d '${flag.description.replace(/'/g, "")}'`
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
for (const flag of GLOBAL_FLAGS) {
|
|
1387
|
+
const short = flag.short ? ` -s ${flag.short}` : "";
|
|
1388
|
+
lines.push(`complete -c pify -l ${flag.name}${short} -d '${flag.description.replace(/'/g, "")}'`);
|
|
1389
|
+
}
|
|
1390
|
+
return `${lines.join("\n")}
|
|
1391
|
+
`;
|
|
1392
|
+
}
|
|
1393
|
+
function powershellScript() {
|
|
1394
|
+
const commands = COMMAND_SPECS.flatMap(allNames);
|
|
1395
|
+
const caseBlocks = COMMAND_SPECS.map((spec) => {
|
|
1396
|
+
const words = [...positionalWords(spec), ...flagTokens(spec)].map((w) => `'${w}'`).join(", ");
|
|
1397
|
+
return ` ${allNames(spec).map((n) => `'${n}'`).join(" ")} { @(${words}) }`;
|
|
1398
|
+
}).join("\n");
|
|
1399
|
+
return `# PowerShell completion for pify \u2014 generated by \`pify completions powershell\`
|
|
1400
|
+
# Install: pify completions powershell | Out-String | Invoke-Expression (add to $PROFILE)
|
|
1401
|
+
Register-ArgumentCompleter -Native -CommandName pify -ScriptBlock {
|
|
1402
|
+
param($wordToComplete, $commandAst, $cursorPosition)
|
|
1403
|
+
$tokens = $commandAst.CommandElements | ForEach-Object { $_.ToString() }
|
|
1404
|
+
$candidates = if ($tokens.Count -le 1 -or ($tokens.Count -eq 2 -and $wordToComplete)) {
|
|
1405
|
+
@(${commands.map((c) => `'${c}'`).join(", ")})
|
|
1406
|
+
} else {
|
|
1407
|
+
switch ($tokens[1]) {
|
|
1408
|
+
${caseBlocks}
|
|
1409
|
+
default { @() }
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
$candidates | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
|
1413
|
+
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
`;
|
|
1417
|
+
}
|
|
1418
|
+
function generateCompletion(shell) {
|
|
1419
|
+
switch (shell) {
|
|
1420
|
+
case "bash":
|
|
1421
|
+
return bashScript();
|
|
1422
|
+
case "zsh":
|
|
1423
|
+
return zshScript();
|
|
1424
|
+
case "fish":
|
|
1425
|
+
return fishScript();
|
|
1426
|
+
case "powershell":
|
|
1427
|
+
return powershellScript();
|
|
1428
|
+
default:
|
|
1429
|
+
throw usageError(
|
|
1430
|
+
`Unknown shell ${JSON.stringify(shell)}.`,
|
|
1431
|
+
`Usage: pify completions <${COMPLETION_SHELLS.join("|")}>`
|
|
1432
|
+
);
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1103
1436
|
// src/help.ts
|
|
1104
1437
|
function render(text) {
|
|
1105
1438
|
return text.split("\n").map((line) => {
|
|
@@ -1124,6 +1457,7 @@ Commands:
|
|
|
1124
1457
|
list Show the @pify catalog with install state
|
|
1125
1458
|
doctor Diagnose the local pi/pify environment
|
|
1126
1459
|
init [dir] Scaffold a new Pi Package
|
|
1460
|
+
completions <shell> Print a completion script (bash|zsh|fish|powershell)
|
|
1127
1461
|
|
|
1128
1462
|
Short forms:
|
|
1129
1463
|
i = install, rm = remove, up = update, ls = list
|
|
@@ -1159,15 +1493,21 @@ settings. Anything beyond these commands is pi's job: run pi --help.
|
|
|
1159
1493
|
}
|
|
1160
1494
|
var COMMAND_HELP = {
|
|
1161
1495
|
setup: `Usage:
|
|
1162
|
-
pify setup [--force] [--pi-version <version>]
|
|
1496
|
+
pify setup [--force] [--pi-version <version>] [--installer]
|
|
1163
1497
|
|
|
1164
|
-
Install the pi coding agent
|
|
1165
|
-
|
|
1166
|
-
|
|
1498
|
+
Install the pi coding agent. Safe to re-run; reports status when pi is
|
|
1499
|
+
already installed. Uses the same npm invocation as pi's official installers
|
|
1500
|
+
(--ignore-scripts --min-release-age=0), falls back to bun when npm is
|
|
1501
|
+
absent, and to --prefix ~/.local on POSIX when npm's global prefix is not
|
|
1502
|
+
writable (never sudo). Updates of an existing pi are pi's own job: run
|
|
1503
|
+
pify update pi.
|
|
1167
1504
|
|
|
1168
1505
|
Options:
|
|
1169
1506
|
--force Reinstall pi even if it is already installed
|
|
1170
1507
|
--pi-version <version> Install an exact pi version (may downgrade)
|
|
1508
|
+
--installer Run pi's official interactive installer for
|
|
1509
|
+
this OS (can bootstrap Node and Git Bash;
|
|
1510
|
+
needs a terminal, refuses in CI)
|
|
1171
1511
|
|
|
1172
1512
|
Examples:
|
|
1173
1513
|
# Bootstrap a bare machine
|
|
@@ -1175,6 +1515,9 @@ Examples:
|
|
|
1175
1515
|
|
|
1176
1516
|
# Pin the version your team has tested
|
|
1177
1517
|
pify setup --pi-version 0.84.4
|
|
1518
|
+
|
|
1519
|
+
# Full native experience (Windows: install.ps1, Unix: install.sh)
|
|
1520
|
+
pify setup --installer
|
|
1178
1521
|
`,
|
|
1179
1522
|
install: `Usage:
|
|
1180
1523
|
pify install <name...> [-l] [-a] [--dry-run]
|
|
@@ -1251,6 +1594,23 @@ Options:
|
|
|
1251
1594
|
|
|
1252
1595
|
Examples:
|
|
1253
1596
|
pify doctor
|
|
1597
|
+
`,
|
|
1598
|
+
completions: `Usage:
|
|
1599
|
+
pify completions <bash|zsh|fish|powershell>
|
|
1600
|
+
|
|
1601
|
+
Print a shell completion script generated from the live command registry \u2014
|
|
1602
|
+
command names, aliases, flags, and package names all tab-complete.
|
|
1603
|
+
|
|
1604
|
+
Examples:
|
|
1605
|
+
# bash / zsh \u2014 evaluate at shell startup (add to your rc file)
|
|
1606
|
+
eval "$(pify completions bash)"
|
|
1607
|
+
eval "$(pify completions zsh)"
|
|
1608
|
+
|
|
1609
|
+
# fish
|
|
1610
|
+
pify completions fish > ~/.config/fish/completions/pify.fish
|
|
1611
|
+
|
|
1612
|
+
# PowerShell \u2014 add to $PROFILE
|
|
1613
|
+
pify completions powershell | Out-String | Invoke-Expression
|
|
1254
1614
|
`,
|
|
1255
1615
|
init: `Usage:
|
|
1256
1616
|
pify init [dir] [--name <package-name>] [--description <text>]
|
|
@@ -1277,85 +1637,40 @@ function commandHelp(command) {
|
|
|
1277
1637
|
}
|
|
1278
1638
|
|
|
1279
1639
|
// src/cli.ts
|
|
1280
|
-
var
|
|
1281
|
-
{
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
},
|
|
1291
|
-
{
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
options: {
|
|
1310
|
-
local: { type: "boolean", short: "l", default: false },
|
|
1311
|
-
approve: { type: "boolean", short: "a", default: false },
|
|
1312
|
-
"dry-run": { type: "boolean", default: false }
|
|
1313
|
-
},
|
|
1314
|
-
allowPositionals: true,
|
|
1315
|
-
run: (p, v) => remove(p, {
|
|
1316
|
-
local: Boolean(v.local),
|
|
1317
|
-
approve: Boolean(v.approve),
|
|
1318
|
-
dryRun: Boolean(v["dry-run"])
|
|
1319
|
-
})
|
|
1320
|
-
},
|
|
1321
|
-
{
|
|
1322
|
-
name: "update",
|
|
1323
|
-
aliases: ["up"],
|
|
1324
|
-
options: {
|
|
1325
|
-
catalog: { type: "boolean", default: false },
|
|
1326
|
-
"dry-run": { type: "boolean", default: false }
|
|
1327
|
-
},
|
|
1328
|
-
allowPositionals: true,
|
|
1329
|
-
run: (p, v) => update(p, { catalogOnly: Boolean(v.catalog), dryRun: Boolean(v["dry-run"]) })
|
|
1330
|
-
},
|
|
1331
|
-
{
|
|
1332
|
-
name: "list",
|
|
1333
|
-
aliases: ["ls"],
|
|
1334
|
-
options: { json: { type: "boolean", default: false } },
|
|
1335
|
-
allowPositionals: false,
|
|
1336
|
-
run: (_p, v) => list({ json: Boolean(v.json) })
|
|
1337
|
-
},
|
|
1338
|
-
{
|
|
1339
|
-
name: "doctor",
|
|
1340
|
-
aliases: [],
|
|
1341
|
-
options: { json: { type: "boolean", default: false } },
|
|
1342
|
-
allowPositionals: false,
|
|
1343
|
-
run: (_p, v) => doctor({ json: Boolean(v.json) })
|
|
1344
|
-
},
|
|
1345
|
-
{
|
|
1346
|
-
name: "init",
|
|
1347
|
-
aliases: [],
|
|
1348
|
-
options: {
|
|
1349
|
-
name: { type: "string" },
|
|
1350
|
-
description: { type: "string" }
|
|
1351
|
-
},
|
|
1352
|
-
allowPositionals: true,
|
|
1353
|
-
run: (p, v) => init(p[0], {
|
|
1354
|
-
name: v.name,
|
|
1355
|
-
description: v.description
|
|
1356
|
-
})
|
|
1640
|
+
var RUNNERS = {
|
|
1641
|
+
setup: (_p, v) => setup({
|
|
1642
|
+
force: Boolean(v.force),
|
|
1643
|
+
piVersion: v["pi-version"],
|
|
1644
|
+
installer: Boolean(v.installer)
|
|
1645
|
+
}),
|
|
1646
|
+
install: (p, v) => install(p, {
|
|
1647
|
+
local: Boolean(v.local),
|
|
1648
|
+
approve: Boolean(v.approve),
|
|
1649
|
+
dryRun: Boolean(v["dry-run"])
|
|
1650
|
+
}),
|
|
1651
|
+
remove: (p, v) => remove(p, {
|
|
1652
|
+
local: Boolean(v.local),
|
|
1653
|
+
approve: Boolean(v.approve),
|
|
1654
|
+
dryRun: Boolean(v["dry-run"])
|
|
1655
|
+
}),
|
|
1656
|
+
update: (p, v) => update(p, { catalogOnly: Boolean(v.catalog), dryRun: Boolean(v["dry-run"]) }),
|
|
1657
|
+
list: (_p, v) => list({ json: Boolean(v.json) }),
|
|
1658
|
+
doctor: (_p, v) => doctor({ json: Boolean(v.json) }),
|
|
1659
|
+
init: (p, v) => init(p[0], {
|
|
1660
|
+
name: v.name,
|
|
1661
|
+
description: v.description
|
|
1662
|
+
}),
|
|
1663
|
+
completions: async (p) => {
|
|
1664
|
+
if (!p[0]) {
|
|
1665
|
+
throw usageError("Missing shell.", "Usage: pify completions <bash|zsh|fish|powershell>");
|
|
1666
|
+
}
|
|
1667
|
+
out(generateCompletion(p[0]));
|
|
1668
|
+
return 0;
|
|
1357
1669
|
}
|
|
1358
|
-
|
|
1670
|
+
};
|
|
1671
|
+
function findSpec(name) {
|
|
1672
|
+
return COMMAND_SPECS.find((c) => c.name === name || c.aliases.includes(name));
|
|
1673
|
+
}
|
|
1359
1674
|
async function main(argv) {
|
|
1360
1675
|
const args = argv.filter((a) => a !== "--no-color");
|
|
1361
1676
|
if (args.length !== argv.length) setColor(false);
|
|
@@ -1375,8 +1690,8 @@ async function main(argv) {
|
|
|
1375
1690
|
if (first.startsWith("-")) {
|
|
1376
1691
|
throw usageError(`Unknown option ${first}.`, 'Use "pify --help".');
|
|
1377
1692
|
}
|
|
1378
|
-
const
|
|
1379
|
-
if (!
|
|
1693
|
+
const spec = findSpec(first);
|
|
1694
|
+
if (!spec) {
|
|
1380
1695
|
throw usageError(
|
|
1381
1696
|
`Unknown command: ${JSON.stringify(first)}`,
|
|
1382
1697
|
'Run "pify --help" to see available commands.'
|
|
@@ -1384,26 +1699,26 @@ async function main(argv) {
|
|
|
1384
1699
|
}
|
|
1385
1700
|
const rest = args.slice(1);
|
|
1386
1701
|
if (rest.includes("-h") || rest.includes("--help")) {
|
|
1387
|
-
out(commandHelp(
|
|
1702
|
+
out(commandHelp(spec.name));
|
|
1388
1703
|
return 0;
|
|
1389
1704
|
}
|
|
1390
1705
|
let parsed;
|
|
1391
1706
|
try {
|
|
1392
1707
|
parsed = parseArgs({
|
|
1393
1708
|
args: rest,
|
|
1394
|
-
options:
|
|
1395
|
-
allowPositionals:
|
|
1709
|
+
options: parseArgsOptionsFor(spec),
|
|
1710
|
+
allowPositionals: spec.positional !== "none",
|
|
1396
1711
|
strict: true
|
|
1397
1712
|
});
|
|
1398
1713
|
} catch (err) {
|
|
1399
1714
|
const message = err instanceof Error ? err.message : String(err);
|
|
1400
1715
|
const unknown = /Unknown option '(--?[^']+)'/.exec(message)?.[1];
|
|
1401
1716
|
throw usageError(
|
|
1402
|
-
unknown ? `Unknown option ${unknown} for "${
|
|
1403
|
-
`Use "pify --help" or "pify ${
|
|
1717
|
+
unknown ? `Unknown option ${unknown} for "${spec.name}".` : message,
|
|
1718
|
+
`Use "pify --help" or "pify ${spec.name} --help".`
|
|
1404
1719
|
);
|
|
1405
1720
|
}
|
|
1406
|
-
return
|
|
1721
|
+
return RUNNERS[spec.name](parsed.positionals, parsed.values);
|
|
1407
1722
|
}
|
|
1408
1723
|
main(process.argv.slice(2)).then((code) => {
|
|
1409
1724
|
process.exitCode = code;
|