@simon_he/pi
Version:
Project-aware CLI that detects npm, pnpm, yarn, bun, Go, Rust and Python projects, then routes installs, scripts, builds and workspace commands automatically.
1,385 lines (1,374 loc) • 75.1 kB
JavaScript
import { createRequire } from "node:module";
import path from "node:path";
import process from "node:process";
import { isFile } from "lazy-js-utils";
import { getPkg, getPkgTool, jsShell, useNodeWorker } from "lazy-js-utils/node";
import color from "picocolors";
import readline from "node:readline";
import { spawnSync } from "node:child_process";
import { log } from "node:console";
import fs from "node:fs/promises";
import os from "node:os";
import fs$1 from "node:fs";
import { fileURLToPath } from "node:url";
//#region package.json
var version = "0.2.19";
//#endregion
//#region src/tty.ts
const isZh$6 = process.env.PI_Lang === "zh";
function isInteractive() {
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
}
function stripAnsi(input) {
let output = "";
for (let i = 0; i < input.length; i++) {
const ch = input[i];
if (ch === "\x1B" && input[i + 1] === "[") {
let j = i + 2;
while (j < input.length && /[0-9;]/.test(input[j])) j++;
if (input[j] === "m") {
i = j;
continue;
}
}
output += ch;
}
return output;
}
function charWidth(ch) {
return (ch.codePointAt(0) ?? 0) >= 4352 ? 2 : 1;
}
function stringWidth(input) {
const clean = stripAnsi(input);
let width = 0;
for (const ch of clean) width += charWidth(ch);
return width;
}
function rowCount(input, columns) {
const width = stringWidth(input);
return Math.max(1, Math.ceil(width / columns));
}
function fuzzyScore(option, query) {
const optionLower = option.toLowerCase();
const queryLower = query.toLowerCase();
let score = 0;
let lastIndex = -1;
for (const ch of queryLower) {
const idx = optionLower.indexOf(ch, lastIndex + 1);
if (idx === -1) return null;
score += idx === lastIndex + 1 ? 5 : 1;
score -= idx;
lastIndex = idx;
}
return score;
}
function getMatchIndices(option, query) {
if (!query) return [];
const optionLower = option.toLowerCase();
const queryLower = query.toLowerCase();
const indices = [];
let lastIndex = -1;
for (const ch of queryLower) {
const idx = optionLower.indexOf(ch, lastIndex + 1);
if (idx === -1) return [];
indices.push(idx);
lastIndex = idx;
}
return indices;
}
function highlightMatchTruncated(option, query, active, maxWidth = Number.POSITIVE_INFINITY) {
if (maxWidth <= 0) return "";
const matchSet = query ? new Set(getMatchIndices(option, query)) : null;
let output = "";
let width = 0;
let truncated = false;
const ellipsis = "…";
const ellipsisWidth = charWidth(ellipsis);
const limit = Number.isFinite(maxWidth) ? Math.max(0, maxWidth - ellipsisWidth) : maxWidth;
for (let i = 0; i < option.length; i++) {
const ch = option[i];
const w = charWidth(ch);
if (Number.isFinite(limit) && width + w > limit) {
truncated = true;
break;
}
if (matchSet && matchSet.has(i)) output += color.bold(color.yellow(ch));
else output += active ? color.cyan(ch) : ch;
width += w;
}
if (truncated) {
if (maxWidth <= ellipsisWidth) return color.dim(ellipsis);
output += color.dim(ellipsis);
}
return output;
}
function filterOptions(options, query) {
if (!query) return options;
const ranked = options.map((option, index) => {
const score = fuzzyScore(option, query);
return score === null ? null : {
option,
score,
index
};
}).filter(Boolean);
ranked.sort((a, b) => {
if (b.score !== a.score) return b.score - a.score;
return a.index - b.index;
});
return ranked.map((item) => item.option);
}
function getVisibleWindow(total, cursor, limit) {
if (total <= limit) return {
start: 0,
end: total
};
let start = cursor - Math.floor(limit / 2);
let end = start + limit;
if (start < 0) {
start = 0;
end = limit;
}
if (end > total) {
end = total;
start = Math.max(0, end - limit);
}
return {
start,
end
};
}
async function runSelect(options, config) {
if (!isInteractive()) return null;
if (options.length === 0) return null;
const stdin = process.stdin;
const stdout = process.stdout;
let columns = stdout.columns || 80;
let rows = stdout.rows || 24;
const updateDimensions = () => {
columns = stdout.columns || 80;
rows = stdout.rows || 24;
};
updateDimensions();
const requestCursorPosition = async () => {
return await new Promise((resolve) => {
let buffer = "";
let timer;
const tryParseCursorPosition = (input) => {
const start = input.lastIndexOf("\x1B[");
if (start === -1) return null;
let i = start + 2;
let rowStr = "";
while (i < input.length && input[i] >= "0" && input[i] <= "9") rowStr += input[i++];
if (!rowStr || input[i] !== ";") return null;
i++;
let colStr = "";
while (i < input.length && input[i] >= "0" && input[i] <= "9") colStr += input[i++];
if (!colStr || input[i] !== "R") return null;
return {
row: Number(rowStr),
col: Number(colStr)
};
};
function onData(chunk) {
buffer += chunk.toString("utf8");
const parsed = tryParseCursorPosition(buffer);
if (parsed) {
cleanup();
resolve(parsed);
}
}
function cleanup() {
if (timer) clearTimeout(timer);
stdin.off("data", onData);
}
timer = setTimeout(() => {
cleanup();
resolve(null);
}, 80);
stdin.on("data", onData);
stdout.write("\x1B[6n");
});
};
readline.emitKeypressEvents(stdin, { escapeCodeTimeout: 50 });
if (stdin.isTTY) stdin.setRawMode(true);
stdout.write("\x1B[?25l");
let anchor = await requestCursorPosition();
if (anchor) {
const prompt = "> ";
const header = `? ${config.placeholder}`;
const hintLine = `${config.mode === "multiple" ? isZh$6 ? "↑/↓ 选择,空格标记,Tab 补全,/ 搜索,回车确认,Esc 取消" : "Use ↑/↓ to move, Space to toggle, Tab to complete, / to search, Enter to confirm, Esc to cancel" : isZh$6 ? "↑/↓ 选择,Tab 补全,/ 搜索,回车确认,Esc 取消" : "Use ↑/↓ to move, Tab to complete, / to search, Enter to confirm, Esc to cancel"} (1/${options.length})`;
const headerRows = rowCount(header, columns);
const inputRows = rowCount(`${prompt}`, columns);
const hintRows = rowCount(hintLine, columns);
const minNeeded = headerRows + inputRows + hintRows + 10 + 2;
if (rows >= minNeeded) {
const maxAnchorRow = Math.max(1, rows - minNeeded + 1);
if (anchor.row > maxAnchorRow) anchor = {
...anchor,
row: maxAnchorRow,
col: 1
};
} else anchor = {
...anchor,
row: 1,
col: 1
};
}
let query = "";
let inputCursor = 0;
let filtered = options;
let cursor = 0;
const selected = /* @__PURE__ */ new Set();
let searchMode = false;
const updateFiltered = () => {
filtered = filterOptions(options, query);
if (filtered.length === 0) cursor = 0;
else if (cursor >= filtered.length) cursor = filtered.length - 1;
};
const render = () => {
updateFiltered();
if (query.length > 0) searchMode = true;
const prompt = searchMode ? "/ " : "> ";
const header = `? ${config.placeholder}`;
const inputLine = `${prompt}${query}`;
const hintLine = (() => {
const position = ` (${Math.min(cursor + 1, filtered.length)}/${filtered.length})`;
if (config.mode === "multiple") {
if (isZh$6) return color.dim("↑/↓ 选择,") + color.bold(color.cyan("空格")) + color.dim(" 标记,Tab 补全,/ 搜索,回车确认,Esc 取消") + color.dim(position);
return color.dim("Use ↑/↓ to move, ") + color.bold(color.cyan("Space")) + color.dim(" to toggle, Tab to complete, / to search, Enter to confirm, Esc to cancel") + color.dim(position);
}
const hint = isZh$6 ? "↑/↓ 选择,Tab 补全,/ 搜索,回车确认,Esc 取消" : "Use ↑/↓ to move, Tab to complete, / to search, Enter to confirm, Esc to cancel";
return color.dim(`${hint}${position}`);
})();
const headerRows = rowCount(header, columns);
const inputRows = rowCount(inputLine, columns);
const hintRows = rowCount(stripAnsi(hintLine), columns);
const availableBelow = anchor ? Math.max(1, rows - anchor.row + 1) : rows;
const optionAreaRows = Math.max(1, availableBelow - headerRows - inputRows - hintRows);
const needsScroll = filtered.length > optionAreaRows;
const minVisibleTarget = 10;
const ellipsisReserve = needsScroll ? 2 : 0;
const maxVisibleRaw = needsScroll ? Math.max(minVisibleTarget, optionAreaRows - ellipsisReserve) : filtered.length;
const maxVisible = Math.max(1, Math.min(maxVisibleRaw, optionAreaRows, filtered.length));
const lines = [header, inputLine];
if (filtered.length === 0) lines.push(color.dim(isZh$6 ? "没有匹配项" : "No matches"));
else {
const window = getVisibleWindow(filtered.length, cursor, maxVisible);
const visible = filtered.slice(window.start, window.end);
if (window.start > 0) lines.push(color.dim("…"));
visible.forEach((option, index) => {
const active = window.start + index === cursor;
const picked = selected.has(option);
const prefix = config.mode === "multiple" ? picked ? "[x] " : "[ ] " : "";
const indicator = active ? ">" : " ";
const prefixPlain = `${indicator} ${prefix}`;
const optionWidth = Math.max(0, columns - stringWidth(prefixPlain));
const renderedOption = highlightMatchTruncated(option, query, active, optionWidth);
const renderedPrefix = active ? color.cyan(prefix) : prefix;
const content = `${active ? color.cyan(indicator) : indicator} ${renderedPrefix}${renderedOption}`;
lines.push(content);
});
if (window.end < filtered.length) lines.push(color.dim("…"));
}
lines.push(hintLine);
if (anchor) stdout.write(`\x1B[${anchor.row};1H`);
else readline.cursorTo(stdout, 0);
readline.clearScreenDown(stdout);
stdout.write(lines.join("\n"));
const promptWidth = stringWidth(prompt);
const beforeWidth = stringWidth(query.slice(0, inputCursor));
const inputRowOffset = Math.floor((promptWidth + beforeWidth) / columns);
const inputCol = (promptWidth + beforeWidth) % columns;
const targetRowOffset = headerRows + inputRowOffset;
if (anchor) stdout.write(`\x1B[${anchor.row + targetRowOffset};${inputCol + 1}H`);
else readline.cursorTo(stdout, inputCol);
};
return new Promise((resolve) => {
let onKeypress;
let onResize;
const done = (value) => {
if (stdin.isTTY) stdin.setRawMode(false);
stdin.off("keypress", onKeypress);
process.off("SIGWINCH", onResize);
stdout.write("\x1B[?25h");
if (anchor) stdout.write(`\x1B[${anchor.row};1H`);
else readline.cursorTo(stdout, 0);
readline.clearScreenDown(stdout);
stdout.write("\n");
resolve(value);
};
const confirmSelection = () => {
if (filtered.length === 0) return done(null);
if (config.mode === "multiple") {
const picked = options.filter((option) => selected.has(option));
if (picked.length > 0) return done(picked);
return done([filtered[cursor]]);
}
return done(filtered[cursor]);
};
const cancelSelection = () => done(null);
onKeypress = (input, key) => {
if (key?.ctrl && key.name === "c") return cancelSelection();
if (key?.name === "escape") {
if (query.length > 0) {
query = "";
inputCursor = 0;
return render();
}
return cancelSelection();
}
if (key?.name === "return") return confirmSelection();
if (key?.name === "up") {
if (filtered.length > 0) cursor = (cursor - 1 + filtered.length) % filtered.length;
return render();
}
if (key?.name === "down") {
if (filtered.length > 0) cursor = (cursor + 1) % filtered.length;
return render();
}
if (config.mode === "multiple" && key?.name === "space") {
const option = filtered[cursor];
if (option) if (selected.has(option)) selected.delete(option);
else selected.add(option);
return render();
}
if (key?.name === "tab") {
if (filtered.length > 0) {
query = filtered[cursor] || filtered[0];
inputCursor = query.length;
searchMode = true;
return render();
}
return;
}
if (input === "/" && !key?.ctrl && !key?.meta) {
if (!searchMode) {
searchMode = true;
query = "";
inputCursor = 0;
return render();
}
if (searchMode && query.length === 0) {
searchMode = false;
return render();
}
}
if (key?.name === "left") {
if (inputCursor > 0) inputCursor -= 1;
return render();
}
if (key?.name === "right") {
if (inputCursor < query.length) inputCursor += 1;
return render();
}
if (key?.name === "home" || key?.ctrl && key?.name === "a") {
inputCursor = 0;
return render();
}
if (key?.name === "end" || key?.ctrl && key?.name === "e") {
inputCursor = query.length;
return render();
}
if (key?.name === "backspace") {
if (query) {
query = query.slice(0, Math.max(0, inputCursor - 1)) + query.slice(inputCursor);
inputCursor = Math.max(0, inputCursor - 1);
return render();
}
return;
}
if (key?.name === "delete") {
if (query && inputCursor < query.length) {
query = query.slice(0, inputCursor) + query.slice(inputCursor + 1);
return render();
}
return;
}
if (key?.ctrl && key?.name === "u") {
query = "";
inputCursor = 0;
return render();
}
if (input && !key?.ctrl && !key?.meta && input.length === 1) {
if (config.mode !== "multiple" || input !== " ") {
query = query.slice(0, inputCursor) + input + query.slice(inputCursor);
inputCursor += input.length;
searchMode = true;
}
return render();
}
};
onResize = () => {
updateDimensions();
render();
};
process.on("SIGWINCH", onResize);
stdin.on("keypress", onKeypress);
render();
});
}
async function ttySelect(options, placeholder) {
const result = await runSelect(options, {
placeholder,
mode: "single"
});
return typeof result === "string" ? result : null;
}
async function ttyMultiSelect(options, placeholder) {
const result = await runSelect(options, {
placeholder,
mode: "multiple"
});
return Array.isArray(result) ? result : null;
}
function renderBox(lines, options = {}) {
const width = options.width ?? Math.max(...lines.map((line) => line.length), 0);
const paddingX = options.paddingX ?? 2;
const paddingY = options.paddingY ?? 1;
const marginX = options.marginX ?? 0;
const marginY = options.marginY ?? 0;
const align = options.align ?? "left";
const innerWidth = width + paddingX * 2;
const margin = " ".repeat(marginX);
const top = `${margin}+${"-".repeat(innerWidth)}+`;
const bottom = top;
const emptyLine = `${margin}|${" ".repeat(innerWidth)}|`;
const alignedLines = lines.map((line) => {
const space = Math.max(0, width - line.length);
const leftPad = align === "center" ? Math.floor(space / 2) : 0;
const rightPad = space - leftPad;
return `${margin}|${" ".repeat(paddingX + leftPad)}${line}${" ".repeat(paddingX + rightPad)}|`;
});
const output = [];
for (let i = 0; i < marginY; i++) output.push("");
output.push(top);
for (let i = 0; i < paddingY; i++) output.push(emptyLine);
output.push(...alignedLines);
for (let i = 0; i < paddingY; i++) output.push(emptyLine);
output.push(bottom);
for (let i = 0; i < marginY; i++) output.push("");
return output.join("\n");
}
//#endregion
//#region src/help.ts
const isZh$5 = process.env.PI_Lang === "zh";
async function help(argv) {
const arg = argv[0];
if (arg === "-v" || arg === "--version") {
const message = isZh$5 ? [
`pi 版本: ${version}`,
"请为我的努力点一个行 🌟",
"谢谢 🤟"
] : [
`pi version: ${version}`,
"Please give me a 🌟 for my efforts",
"Thank you 🤟"
];
console.log(renderBox(message, {
align: "center",
width: 50,
marginX: 2,
marginY: 1,
paddingX: 4,
paddingY: 2
}));
process.exit(0);
} else if (arg === "-h" || arg === "--help") {
console.log(renderBox([
"PI Commands:",
"~ pi: install or update with the current project package manager",
"~ pi --choose-tool: choose package manager for this workspace",
"~ pi --choose-tool bun: choose the tool directly",
"~ pi --forget-tool: clear saved package manager choice",
"~ pi --show-tool: show current workspace package manager",
"~ pi --show-tool --json: show current tool as JSON",
"~ pi --list-tools: list detected package-manager candidates",
"~ pi --list-tools --json: list candidates as JSON",
"~ pix: npx package",
"~ pui: uninstall package",
"~ prun: run package script or language entry file",
"~ prun --doctor: show shell/history diagnostics",
"~ pinit: package init",
"~ pbuild: go build | cargo build",
"~ pfind: find and run workspace scripts",
"~ pa: deprecated alias; delegates to `na` when installed",
"~ pu: deprecated alias; delegates to `nu` when installed",
"~ pci: compatibility alias of `pi`; prefer `pi`",
"~ pci --choose-tool: re-pick tool before install",
"~ pci --choose-tool bun: choose the tool directly",
"~ pci --forget-tool: clear saved tool before install",
"~ pci --show-tool: show current tool before install",
"~ pci --show-tool --json: show current tool as JSON",
"~ pci --list-tools: list detected candidates",
"~ pci --list-tools --json: list candidates as JSON",
"~ pil: package latest install",
"~ pil --choose-tool: re-pick tool before latest install",
"~ pil --choose-tool bun: choose the tool directly",
"~ pil --forget-tool: clear saved tool before latest install",
"~ pil --show-tool: show current tool before latest install",
"~ pil --show-tool --json: show current tool as JSON",
"~ pil --list-tools: list detected candidates",
"~ pil --list-tools --json: list candidates as JSON"
], {
align: "left",
width: 76,
marginX: 2,
marginY: 1,
paddingX: 1,
paddingY: 1
}));
process.exit(0);
}
}
//#endregion
//#region src/pa.ts
function hasCommand$1(command) {
return (process.platform === "win32" ? spawnSync("where", [command], { stdio: "ignore" }) : spawnSync("sh", ["-c", `command -v ${command}`], { stdio: "ignore" })).status === 0;
}
function pa(params = "") {
console.warn(color.yellow("[pi] `pa` is deprecated and only kept as a compatibility bridge to `na`. Install/use `na` directly if you still need that workflow."));
if (!hasCommand$1("na")) {
console.error(color.red("[pi] `pa` delegates to `na`, but `na` is not installed. Install the package that provides `na`, or stop using the deprecated `pa` alias."));
process.exitCode = 1;
return;
}
return jsShell(`na${params ? ` ${params}` : ""}`, "inherit");
}
//#endregion
//#region src/detectNode.ts
async function detectNode() {
try {
await getPkg();
} catch {
const cwd = process.cwd();
console.log(color.red(`当前目录: ${cwd} 没有package.json文件`));
process.exit(1);
}
}
//#endregion
//#region src/pkgManager.ts
const resolvedToolCache = /* @__PURE__ */ new Map();
const toolIndicatorMap = {
pnpm: ["pnpm-workspace.yaml", "pnpm-lock.yaml"],
yarn: ["yarn.lock", ".yarnrc.yml"],
bun: ["bun.lock", "bun.lockb"],
npm: ["package-lock.json", "npm-shrinkwrap.json"]
};
const isZh$4 = process.env.PI_Lang === "zh";
const supportedPkgTools = Object.keys(toolIndicatorMap);
function isEnabled(value) {
if (!value) return false;
const normalized = value.toLowerCase();
return normalized === "1" || normalized === "true" || normalized === "yes";
}
function normalizeDir$1(dir) {
return path.resolve(dir);
}
function isSameOrInsideDir(base, target) {
const relative = path.relative(base, target);
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
}
function findUpSync$1(startDir, predicate) {
let current = normalizeDir$1(startDir);
while (true) {
if (predicate(current)) return current;
const parent = path.dirname(current);
if (parent === current) return null;
current = parent;
}
}
function findToolCandidate(cwd, tool) {
const indicators = toolIndicatorMap[tool];
if (!indicators?.length) return null;
const root = findUpSync$1(cwd, (dir) => indicators.some((indicator) => isFile(path.join(dir, indicator))));
if (!root) return null;
const foundIndicators = indicators.filter((indicator) => isFile(path.join(root, indicator)));
return {
tool,
root,
indicators: foundIndicators.length ? foundIndicators : indicators.slice(0, 1)
};
}
function getToolCandidates(cwd) {
return Object.keys(toolIndicatorMap).map((tool) => findToolCandidate(cwd, tool)).filter(Boolean);
}
function getPreferenceWorkspaceKey(cwd, candidates) {
const roots = [...new Set(candidates.map((candidate) => normalizeDir$1(candidate.root)))];
if (roots.length === 1) return roots[0];
return normalizeDir$1(cwd);
}
function findStoredWorkspaceKey(cwd, preferences) {
return Object.keys(preferences.workspaces).filter((workspaceKey) => isSameOrInsideDir(workspaceKey, cwd)).sort((a, b) => b.length - a.length)[0] || null;
}
function resolveWorkspaceKey(cwd, candidates, preferences) {
if (candidates.length > 0) return getPreferenceWorkspaceKey(cwd, candidates);
return findStoredWorkspaceKey(cwd, preferences) || normalizeDir$1(cwd);
}
function getConfigHome() {
const home = process.env.HOME || os.homedir();
if (process.platform === "win32") return process.env.APPDATA || path.join(home, "AppData", "Roaming");
return process.env.XDG_CONFIG_HOME || path.join(home, ".config");
}
function getWorkspaceToolPreferencePath() {
return path.join(getConfigHome(), "pi", "workspace-tools.json");
}
async function readWorkspaceToolPreferences() {
const configPath = getWorkspaceToolPreferencePath();
try {
const raw = await fs.readFile(configPath, "utf8");
const parsed = JSON.parse(raw);
return {
version: 1,
workspaces: parsed.workspaces && typeof parsed.workspaces === "object" ? parsed.workspaces : {}
};
} catch {
return {
version: 1,
workspaces: {}
};
}
}
async function writeWorkspaceToolPreference(workspaceKey, tool) {
const configPath = getWorkspaceToolPreferencePath();
const data = await readWorkspaceToolPreferences();
data.workspaces[workspaceKey] = tool;
await fs.mkdir(path.dirname(configPath), { recursive: true });
await fs.writeFile(configPath, JSON.stringify(data, null, 2), "utf8");
resolvedToolCache.clear();
}
async function deleteWorkspaceToolPreference(workspaceKey) {
const configPath = getWorkspaceToolPreferencePath();
const data = await readWorkspaceToolPreferences();
if (!(workspaceKey in data.workspaces)) return;
delete data.workspaces[workspaceKey];
await fs.mkdir(path.dirname(configPath), { recursive: true });
await fs.writeFile(configPath, JSON.stringify(data, null, 2), "utf8");
resolvedToolCache.clear();
}
async function forgetPkgToolPreference() {
const workspaceKey = findStoredWorkspaceKey(normalizeDir$1(process.cwd()), await readWorkspaceToolPreferences());
if (!workspaceKey) return false;
await deleteWorkspaceToolPreference(workspaceKey);
return true;
}
function getSupportedPkgToolNames() {
return supportedPkgTools.slice();
}
function getPreferredToolFromEnv(candidates) {
const preferred = process.env.PI_DEFAULT;
if (!preferred) return null;
return candidates.some((candidate) => candidate.tool === preferred) ? preferred : null;
}
function getExplicitPreferredTool(value) {
if (!value) return null;
return supportedPkgTools.includes(value) ? value : null;
}
function validateExplicitPreferredTool(preferredTool, candidates) {
if (candidates.length === 0) return true;
return candidates.some((candidate) => candidate.tool === preferredTool);
}
function logInvalidPreferredTool(preferredTool, candidates) {
const names = candidates.map((candidate) => candidate.tool).join(", ");
console.log(color.red(isZh$4 ? `当前 workspace 可选的包管理器是: ${names},不能直接指定 ${preferredTool}。` : `This workspace supports: ${names}. ${preferredTool} cannot be selected directly here.`));
}
function getDetectedToolFallback(detected, candidates) {
if (candidates.some((candidate) => candidate.tool === detected)) return detected;
return candidates[0]?.tool || detected;
}
function formatCandidateLabel(candidate, cwd) {
const indicators = candidate.indicators.join(", ");
const relativeRoot = path.relative(cwd, candidate.root) || ".";
if (relativeRoot === ".") return `${candidate.tool}: ${indicators}`;
return `${candidate.tool}: ${indicators} (${relativeRoot})`;
}
function toCandidateInfo(candidates) {
return candidates.map((candidate) => ({
tool: candidate.tool,
indicators: candidate.indicators,
root: candidate.root
}));
}
async function selectToolCandidate(candidates, cwd) {
if (!isInteractive()) return { status: "unavailable" };
const options = candidates.map((candidate) => formatCandidateLabel(candidate, cwd));
const labelToTool = new Map(candidates.map((candidate) => [formatCandidateLabel(candidate, cwd), candidate.tool]));
const choice = await ttySelect(options, isZh$4 ? "🤔检测到多个包管理环境,请选择当前 workspace 使用的安装方式" : "Multiple package managers were detected, choose one for this workspace.");
if (!choice) return { status: "cancelled" };
const tool = labelToTool.get(choice);
if (!tool) return { status: "cancelled" };
return {
status: "selected",
tool
};
}
function logAmbiguousToolFallback(candidates, tool) {
const names = candidates.map((candidate) => candidate.tool).join(", ");
console.log(color.yellow(isZh$4 ? `检测到多个包管理环境(${names}),当前不是交互式终端,临时使用 ${tool}。可在交互式终端运行一次 pi 以保存当前 workspace 的选择。` : `Detected multiple package managers (${names}). Using ${tool} for now because no interactive TTY is available. Run pi once in an interactive shell to save this workspace preference.`));
}
function getSourceLabel(source) {
switch (source) {
case "saved-preference": return isZh$4 ? "已保存的 workspace 选择" : "saved workspace choice";
case "fresh-selection": return isZh$4 ? "本次重新选择并保存" : "picked now and saved";
case "single-candidate": return isZh$4 ? "当前只检测到一种包管理器标记" : "single detected package-manager indicator";
case "env-default": return isZh$4 ? "PI_DEFAULT 兜底" : "PI_DEFAULT fallback";
case "detected-tool": return isZh$4 ? "环境自动检测结果" : "environment auto-detection";
case "non-interactive-fallback": return isZh$4 ? "非交互终端下的临时兜底" : "non-interactive fallback";
}
}
function logWorkspaceToolSelected(tool, forceChoose) {
console.log(color.green(forceChoose ? isZh$4 ? `当前 workspace 已切换为使用 ${tool},并保存了这个选择。` : `This workspace now uses ${tool}, and the choice has been saved.` : isZh$4 ? `已为当前 workspace 记住 ${tool} 作为包管理器。` : `Saved ${tool} as the package manager for this workspace.`));
}
function logWorkspaceToolResolved(tool) {
console.log(color.green(isZh$4 ? `当前 workspace 使用 ${tool} 作为包管理器。` : `This workspace uses ${tool} as the package manager.`));
}
function logStaleWorkspaceToolRemoved(tool) {
console.log(color.yellow(isZh$4 ? `检测到之前保存的 ${tool} 已不再适用于当前 workspace,已自动清除旧记录。` : `The saved ${tool} choice no longer matches this workspace and was removed automatically.`));
}
async function preparePkgToolContext(forgetPreference = false) {
const cwd = normalizeDir$1(process.cwd());
const originalDetected = await getPkgTool() || "npm";
const candidates = getToolCandidates(cwd);
const preferences = await readWorkspaceToolPreferences();
const workspaceKey = resolveWorkspaceKey(cwd, candidates, preferences);
let detected = originalDetected;
if (forgetPreference) await deleteWorkspaceToolPreference(workspaceKey);
if (forgetPreference) delete preferences.workspaces[workspaceKey];
let rememberedTool = preferences.workspaces[workspaceKey];
if (rememberedTool && !candidates.some((candidate) => candidate.tool === rememberedTool)) {
await deleteWorkspaceToolPreference(workspaceKey);
delete preferences.workspaces[workspaceKey];
logStaleWorkspaceToolRemoved(rememberedTool);
rememberedTool = void 0;
}
if (detected === "npm" && candidates.length === 1) detected = candidates[0].tool;
return {
detected,
candidates,
rememberedTool
};
}
async function getPkgToolStatus() {
const { detected, candidates, rememberedTool } = await preparePkgToolContext();
const candidateInfo = toCandidateInfo(candidates);
if (rememberedTool) return {
status: "resolved",
detected,
tool: rememberedTool,
source: "saved-preference",
candidates: candidateInfo
};
if (candidates.length <= 1) {
const fallback = process.env.PI_DEFAULT;
return {
status: "resolved",
detected,
tool: detected === "npm" && fallback ? fallback : detected,
source: detected === "npm" && fallback ? "env-default" : candidates.length === 1 ? "single-candidate" : "detected-tool",
candidates: candidateInfo
};
}
const envPreferredTool = getPreferredToolFromEnv(candidates);
if (envPreferredTool) return {
status: "resolved",
detected,
tool: envPreferredTool,
source: "env-default",
candidates: candidateInfo
};
if (!isInteractive()) return {
status: "resolved",
detected,
tool: getDetectedToolFallback(detected, candidates),
source: "non-interactive-fallback",
candidates: candidateInfo
};
return {
status: "needs-selection",
detected,
candidates: candidateInfo
};
}
function printPkgToolStatus(status, options = {}) {
if (options.json) {
console.log(JSON.stringify({
...status,
sourceLabel: status.status === "resolved" ? getSourceLabel(status.source) : void 0
}, null, 2));
return;
}
if (status.status === "resolved") {
const candidateNames = status.candidates.map((candidate) => candidate.tool).join(", ");
console.log(color.green(isZh$4 ? `当前 workspace 使用 ${status.tool} 作为包管理器。` : `This workspace uses ${status.tool} as the package manager.`));
console.log(color.cyan(`${isZh$4 ? "来源" : "Source"}: ${getSourceLabel(status.source)}`));
if (candidateNames) console.log(color.dim(`${isZh$4 ? "候选项" : "Candidates"}: ${candidateNames}`));
return;
}
const candidateNames = status.candidates.map((candidate) => candidate.tool).join(", ");
console.log(color.yellow(isZh$4 ? "当前 workspace 还没有固定包管理器选择。" : "This workspace does not have a locked package-manager choice yet."));
console.log(color.cyan(`${isZh$4 ? "原因" : "Reason"}: ${isZh$4 ? "检测到了多个包管理器标记,且当前没有保存的选择。" : "Multiple package-manager indicators were found and no saved choice exists yet."}`));
console.log(color.dim(`${isZh$4 ? "候选项" : "Candidates"}: ${candidateNames}`));
console.log(color.dim(isZh$4 ? "可执行 `pi --choose-tool` 来保存当前 workspace 的选择。" : "Run `pi --choose-tool` to save a choice for this workspace."));
}
function printPkgToolCandidates(status, options = {}) {
if (options.json) {
console.log(JSON.stringify({
...status,
sourceLabel: status.status === "resolved" ? getSourceLabel(status.source) : void 0
}, null, 2));
return;
}
if (status.status === "resolved") {
console.log(color.green(isZh$4 ? `当前 workspace 使用 ${status.tool} 作为包管理器。` : `This workspace uses ${status.tool} as the package manager.`));
console.log(color.cyan(`${isZh$4 ? "来源" : "Source"}: ${getSourceLabel(status.source)}`));
} else console.log(color.yellow(isZh$4 ? "当前 workspace 还没有固定包管理器选择。" : "This workspace does not have a locked package-manager choice yet."));
if (status.candidates.length === 0) {
console.log(color.dim(isZh$4 ? "当前 workspace 没有检测到明确的 lockfile / workspace 候选。" : "No explicit lockfile or workspace candidates were detected in this workspace."));
return;
}
console.log(color.bold(isZh$4 ? "候选工具:" : "Candidate tools:"));
for (const candidate of status.candidates) {
const indicators = candidate.indicators.join(", ");
console.log(`- ${candidate.tool}`);
console.log(color.dim(` ${isZh$4 ? "root" : "root"}: ${candidate.root}`));
console.log(color.dim(` ${isZh$4 ? "indicators" : "indicators"}: ${indicators}`));
}
}
async function resolvePkgTool(options = {}) {
const cwd = normalizeDir$1(process.cwd());
const forceChoose = options.forceChoose || isEnabled(process.env.PI_FORCE_PICK_TOOL);
const forgetPreference = options.forgetPreference || isEnabled(process.env.PI_FORGET_PICK_TOOL);
const preferredTool = getExplicitPreferredTool(options.preferredTool || process.env.PI_PREFERRED_TOOL);
const shouldBypassCache = forceChoose || forgetPreference || Boolean(preferredTool);
const cached = resolvedToolCache.get(cwd);
if (!shouldBypassCache && cached) return cached;
const pending = (async () => {
const { detected, candidates, rememberedTool } = await preparePkgToolContext(forgetPreference);
if (preferredTool) {
if (!validateExplicitPreferredTool(preferredTool, candidates)) {
logInvalidPreferredTool(preferredTool, candidates);
process.exit(1);
}
await writeWorkspaceToolPreference(resolveWorkspaceKey(normalizeDir$1(process.cwd()), candidates, await readWorkspaceToolPreferences()), preferredTool);
logWorkspaceToolSelected(preferredTool, true);
return {
detected,
tool: preferredTool,
source: "fresh-selection"
};
}
if (!forceChoose && rememberedTool) return {
detected,
tool: rememberedTool,
source: "saved-preference"
};
if (candidates.length <= 1) {
const fallback = process.env.PI_DEFAULT;
const tool = detected === "npm" && fallback ? fallback : detected;
if (forceChoose) logWorkspaceToolResolved(tool);
return {
detected,
tool,
source: detected === "npm" && fallback ? "env-default" : candidates.length === 1 ? "single-candidate" : "detected-tool"
};
}
const envPreferredTool = getPreferredToolFromEnv(candidates);
if (!forceChoose && envPreferredTool) return {
detected,
tool: envPreferredTool,
source: "env-default"
};
const cwdForSelection = normalizeDir$1(process.cwd());
const selection = await selectToolCandidate(candidates, cwd);
if (selection.status === "selected") {
await writeWorkspaceToolPreference(resolveWorkspaceKey(cwdForSelection, candidates, await readWorkspaceToolPreferences()), selection.tool);
logWorkspaceToolSelected(selection.tool, forceChoose);
return {
detected,
tool: selection.tool,
source: "fresh-selection"
};
}
if (selection.status === "cancelled") {
console.log(color.dim(isZh$4 ? "已取消" : "Cancelled"));
process.exit(0);
}
const tool = getDetectedToolFallback(detected, candidates);
logAmbiguousToolFallback(candidates, tool);
return {
detected,
tool,
source: "non-interactive-fallback"
};
})();
resolvedToolCache.set(cwd, pending);
return pending;
}
function getInstallCommand(tool, hasParams) {
const action = hasParams ? "add" : "install";
switch (tool) {
case "pnpm": return `pnpm ${action}`;
case "yarn": return `yarn ${action}`;
case "bun": return `bun ${action}`;
case "npm": return "npm install";
default: return `${tool} ${action}`;
}
}
function getRemoveCommand(tool) {
switch (tool) {
case "pnpm": return "pnpm remove";
case "yarn": return "yarn remove";
case "bun": return "bun remove";
case "npm": return "npm uninstall";
default: return `${tool} remove`;
}
}
//#endregion
//#region src/utils.ts
const DW = /\s-DW/g;
const W = /\s-W/g;
const Dw = /\s-Dw/g;
const w = /\s-w/g;
const D = /\s-D(?!w)/g;
const d = /\s-d(?!w)/g;
const isZh$3 = process.env.PI_Lang === "zh";
const log$1 = console.log;
function normalizeDir(dir) {
return path.resolve(dir);
}
function isSameDir(a, b) {
return normalizeDir(a) === normalizeDir(b);
}
function findUpSync(startDir, predicate) {
let current = normalizeDir(startDir);
while (true) {
if (predicate(current)) return current;
const parent = path.dirname(current);
if (parent === current) return null;
current = parent;
}
}
async function findUpAsync(startDir, predicate) {
let current = normalizeDir(startDir);
while (true) {
if (await predicate(current)) return current;
const parent = path.dirname(current);
if (parent === current) return null;
current = parent;
}
}
async function getParams(params) {
const cwd = process.cwd();
try {
const { tool } = await resolvePkgTool();
switch (tool) {
case "pnpm": {
const pnpmWorkspaceRoot = findUpSync(cwd, (dir) => isFile(path.join(dir, "pnpm-workspace.yaml")));
const inPnpmWorkspace = Boolean(pnpmWorkspaceRoot);
const isPnpmWorkspaceRoot = pnpmWorkspaceRoot ? isSameDir(pnpmWorkspaceRoot, cwd) : false;
if (!inPnpmWorkspace) {
if (DW.test(params)) return params.replace(DW, " -D");
if (Dw.test(params)) return params.replace(Dw, " -D");
if (W.test(params)) return params.replace(W, "");
if (w.test(params)) return params.replace(w, "");
if (d.test(params)) return params.replace(d, " -D");
return params;
}
let out = params;
if (DW.test(out)) out = out.replace(DW, " -Dw");
if (W.test(out)) out = out.replace(W, " -w");
if (isPnpmWorkspaceRoot) {
if (D.test(out)) out = out.replace(D, " -Dw");
if (d.test(out)) out = out.replace(d, " -Dw");
if (!out || Dw.test(out) || w.test(out)) return out;
return `${out} -w`;
}
if (d.test(out)) out = out.replace(d, " -D");
return out;
}
case "yarn": {
const yarnWorkspaceRoot = await findUpAsync(cwd, async (dir) => {
try {
return Boolean((await getPkg(path.join(dir, "package.json")))?.workspaces);
} catch {
return false;
}
});
const inYarnWorkspace = Boolean(yarnWorkspaceRoot);
const isYarnWorkspaceRoot = yarnWorkspaceRoot ? isSameDir(yarnWorkspaceRoot, cwd) : false;
if (!inYarnWorkspace) {
if (Dw.test(params)) return params.replace(Dw, " -D");
if (DW.test(params)) return params.replace(DW, " -D");
if (W.test(params)) return params.replace(W, "");
if (w.test(params)) return params.replace(w, "");
if (d.test(params)) return params.replace(d, " -D");
return params;
}
let out = params;
if (w.test(out)) out = out.replace(w, " -W");
if (Dw.test(out)) out = out.replace(Dw, " -DW");
if (W.test(out)) out = out.replace(W, " -W");
if (isYarnWorkspaceRoot) {
if (D.test(out)) out = out.replace(D, " -DW");
if (d.test(out)) out = out.replace(d, " -DW");
if (!out || W.test(out) || DW.test(out)) return out;
return `${out} -W`;
}
if (d.test(out)) out = out.replace(d, " -D");
return out;
}
default: return d.test(params) ? params.replace(d, " -D") : params;
}
} catch {
console.log(color.red(`${isZh$3 ? "package.json并不存在,在以下目录中:" : "package.json has not been found in"} ${process.cwd()}`));
process.exit(1);
}
}
async function loading(text, isSilent = false) {
const { color, spinner } = await getStyle();
const ora = (await import("ora")).default;
return ora({
text,
spinner,
color,
isSilent,
discardStdin: true
}).start();
}
async function getStyle() {
const { PI_COLOR: color = "yellow", PI_SPINNER: spinner = "star" } = process.env;
return {
color,
spinner
};
}
async function getLatestVersion(pkg, isZh = true) {
const data = [];
for (const p of pkg.replace(/\s+/, " ").split(" ")) {
const [pName, v] = p.split("$");
let { status, result } = await jsShell(`npm view ${pName}`, [
"inherit",
"pipe",
"inherit"
]);
if (status === 0) {
if (result.startsWith("@")) result = result.slice(1);
const item = isZh ? `${pName} ${color.gray(v)} -> ${result.match(/@(\S+)/)[1]}` : `Installed ${pName} ${color.dim(v)} -> latest version:${result.match(/@(\S+)/)[1]}`;
data.push(item);
} else throw new Error(result);
}
return `${data.join(" ")}${isZh ? " 安装成功! 😊" : " successfully! 😊"}`;
}
async function pushHistory(command) {
log$1(color.bold(color.blue(`${isZh$3 ? "快捷指令" : "shortcut command"}: ${command}`)));
const historyHint = process.env.CCOMMAND_HISTORY_HINT || path.join(process.env.XDG_CACHE_HOME || path.join(process.env.HOME || os.homedir(), ".cache"), "ccommand", "last-history");
try {
fs$1.mkdirSync(path.dirname(historyHint), { recursive: true });
fs$1.writeFileSync(historyHint, `${Date.now()}\t${command}\n`, "utf8");
} catch {}
const shellName = (process.env.SHELL || "/bin/bash").split("/").pop() || "bash";
let historyFile = "";
let historyFormat = "bash";
const home = process.env.HOME || os.homedir();
switch (shellName) {
case "zsh":
historyFile = path.join(home, ".zsh_history");
historyFormat = "zsh";
break;
case "bash":
historyFile = process.env.HISTFILE || path.join(home, ".bash_history");
historyFormat = "bash";
break;
case "fish":
historyFile = path.join(home, ".local", "share", "fish", "fish_history");
historyFormat = "fish";
break;
default:
historyFile = process.env.HISTFILE || path.join(home, ".bash_history");
historyFormat = "bash";
}
try {
if (!fs$1.existsSync(historyFile)) {
log$1(color.yellow(`${isZh$3 ? `未找到 ${shellName} 历史文件` : `${shellName} history file not found`}`));
return;
}
const raw = fs$1.readFileSync(historyFile, "utf8");
const timestamp = Math.floor(Date.now() / 1e3);
let newEntry = "";
if (historyFormat === "zsh") newEntry = `: ${timestamp}:0;${command}`;
else if (historyFormat === "fish") newEntry = `- cmd: ${command}\n when: ${timestamp}`;
else if (process.env.HISTTIMEFORMAT) newEntry = `#${timestamp}\n${command}`;
else newEntry = command;
function parseEntries(content) {
if (historyFormat === "fish") {
const lines = content.split(/\r?\n/);
const blocks = [];
let buffer = [];
for (const line of lines) if (line.startsWith("- cmd: ")) {
if (buffer.length) {
blocks.push(buffer.join("\n"));
buffer = [];
}
buffer.push(line);
} else if (buffer.length) buffer.push(line);
else if (line.trim() !== "") blocks.push(line);
if (buffer.length) blocks.push(buffer.join("\n"));
return blocks.filter(Boolean);
} else if (historyFormat === "zsh") return content.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
else {
const lines = content.split(/\r?\n/);
const entries = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.startsWith("#")) {
const next = lines[i + 1] ?? "";
entries.push(`${line}\n${next}`);
i++;
} else if (line.trim() !== "") entries.push(line);
}
return entries;
}
}
const entries = parseEntries(raw);
function extractCommand(entry) {
if (historyFormat === "fish") {
const m = entry.split("\n")[0].match(/^- cmd: (.*)$/);
return m ? m[1] : entry;
} else if (historyFormat === "zsh") {
const m = entry.match(/^[^;]*;(.+)$/);
return m ? m[1] : entry;
} else {
if (entry.startsWith("#")) {
const parts = entry.split(/\r?\n/);
return parts[1] ?? parts[0];
}
return entry;
}
}
const newEntries = [];
const newCmd = extractCommand(newEntry);
let existingFishBlock = null;
for (const e of entries) {
if (extractCommand(e) === newCmd) {
if (historyFormat === "fish") {
existingFishBlock = e;
continue;
}
continue;
}
newEntries.push(e);
}
if (historyFormat === "fish" && existingFishBlock) {
const lines = existingFishBlock.split("\n");
let hasWhen = false;
const updated = lines.map((line) => {
if (line.trim().startsWith("when:") || line.startsWith(" when:")) {
hasWhen = true;
return ` when: ${timestamp}`;
}
return line;
});
if (!hasWhen) updated.splice(1, 0, ` when: ${timestamp}`);
newEntries.push(updated.join("\n"));
} else newEntries.push(newEntry);
let finalContent = "";
if (historyFormat === "fish") finalContent = `${newEntries.map((e) => e.trimEnd()).join("\n")}\n`;
else finalContent = `${newEntries.join("\n")}\n`;
const tmpPath = `${historyFile}.ccommand.tmp`;
fs$1.writeFileSync(tmpPath, finalContent, "utf8");
fs$1.renameSync(tmpPath, historyFile);
} catch (err) {
log$1(color.red(`${isZh$3 ? `❌ 添加到 ${shellName} 历史记录失败` : `❌ Failed to add to ${shellName} history`}${err ? `: ${String(err)}` : ""}`));
}
}
//#endregion
//#region src/pi.ts
const isZh$2 = process.env.PI_Lang === "zh";
async function pi(params, pkg, executor = "pi") {
await detectNode();
const text = pkg ? `Installing ${params} ...` : "Updating dependency ...";
const isLatest = executor === "pil";
const start = Date.now();
let successMsg = "";
if (isLatest) successMsg = await getLatestVersion(pkg, isZh$2);
else successMsg = pkg ? isZh$2 ? `${pkg} 安装成功! 😊` : `Installed ${pkg} successfully! 😊` : isZh$2 ? "依赖更新成功! 😊" : "Updated dependency successfully! 😊";
const failMsg = pkg ? isZh$2 ? `${params} 安装失败 😭` : `Failed to install ${params} 😭` : isZh$2 ? "依赖更新失败 😭" : "Failed to update dependency 😭";
const isSilent = process.env.PI_SILENT === "true";
let stdio = isSilent ? "inherit" : [
"inherit",
"pipe",
"inherit"
];
let loading_status;
const { PI_DEFAULT, PI_MaxSockets: sockets } = process.env;
const { tool } = await resolvePkgTool();
const maxSockets = sockets || 4;
if (tool === "npm" && !PI_DEFAULT) stdio = "inherit";
else loading_status = await loading(text, isSilent);
executor = getInstallCommand(tool, Boolean(params));
const newParams = isLatest ? "" : await getParams(params);
const runSockets = tool === "npm" ? ` --max-sockets=${maxSockets}` : "";
const latestParams = Array.isArray(params) ? params : params ? [params] : [];
const cmdList = isLatest ? latestParams.map((p) => `${executor} ${p}`) : [`${executor}${newParams ? ` ${newParams}` : runSockets}`];
const runCmd = isLatest ? cmdList.join(" & ") : cmdList[0];
const runCommands = async (commands) => {
const results = await Promise.all(commands.map((command) => useNodeWorker({
params: command,
stdio,
errorExit: false
})));
const failed = results.find((r) => r.status !== 0);
const merged = results.map((r) => r.result).filter(Boolean).join("\n");
return {
status: failed ? failed.status : 0,
result: failed?.result || merged
};
};
let { status, result } = await runCommands(cmdList);
if (result && result.includes("pnpm versions with respective Node.js version support")) {
log(result);
log(color.yellow(isZh$2 ? "正在尝试使用 npm 再次执行..." : "Trying to use npm to run again..."));
const fallbackCommands = isLatest ? latestParams.map((p) => `npm install ${p}`) : [`npm install${newParams ? ` ${newParams}` : runSockets}`];
const fallbackResults = await Promise.all(fallbackCommands.map((command) => jsShell(command, { stdio })));
const fallbackFailed = fallbackResults.find((r) => r.status !== 0);
const fallbackMerged = fallbackResults.map((r) => r.result).filter(Boolean).join("\n");
status = fallbackFailed ? fallbackFailed.status : 0;
result = fallbackFailed?.result || fallbackMerged;
}
if (stdio === "inherit") loading_status = await loading("");
const costTime = (Date.now() - start) / 1e3;
successMsg += color.blue(` ---- ⏰:${costTime}s`);
if (status === 0) {
loading_status.succeed(color.green(successMsg));
pushHistory(runCmd);
} else if (result && result.includes("Not Found - 404")) {
const _pkg = result.match(/\/[^/:]+:/)?.[0].slice(1, -1);
const _result = isZh$2 ? `${_pkg} 包名可能有误或者版本号不存在,并不能在npm中搜索到,请检查` : `${_pkg} the package name may be wrong, and cannot be found in npm, please check`;
loading_status.fail(color.red(result ? `${failMsg}\n${_result}` : failMsg));
} else loading_status.fail(color.red(result ? `${failMsg}\n${result}` : failMsg));
if (result) {
const match = result.match(/ERR_PNPM_NO_MATCHING_VERSION_INSIDE_WORKSPACE\u2009 In : No matching version found for\s+([^@]+)/);
if (match) {
const dep = match[1];
jsShell(`pi ${dep}@latest`);
}
}
process.exit();
}
//#endregion
//#region src/pci.ts
function pci(params, pkg) {
return pi(params, pkg);
}
//#endregion
//#region src/require.ts
const base = fileURLToPath(import.meta.url);
const localRequire = createRequire(base);
function getCcommand() {
return localRequire("ccommand");
}
//#endregion
//#region src/prun.ts
async function prun(params) {
ensurePrunAutoInit();
const prevNoHistory = process.env.CCOMMAND_NO_HISTORY;
if (!shouldSuppressHistory$1()) delete process.env.CCOMMAND_NO_HISTORY;
else process.env.CCOMMAND_NO_HISTORY = "1";
const { ccommand } = getCcommand();
try {
await ccommand(params);
} finally {
if (prevNoHistory == null) delete process.env.CCOMMAND_NO_HISTORY;
else process.env.CCOMMAND_NO_HISTORY = prevNoHistory;
}
}
const isZh$1 = process.env.PI_Lang === "zh";
const safeShellValue = /^[\w./:@%+=,-]+$/;
function isNoHistory$1(value) {
if (!value) return false;
const normalized = value.toLowerCase();
return normalized === "1" || normalized === "true" || normalized === "yes";
}
function shouldSuppressHistory$1() {
return isNoHistory$1(process.env.CCOMMAND_NO_HISTORY) || isNoHistory$1(process.env.NO_HISTORY);
}
function hasTruthyEnv(...values) {
return values.some(isNoHistory$1);
}
function shellQuote(value) {
if (value === "") return "''";
if (safeShellValue.test(value)) return value;
return `'${value.replace(/'/g, `'\\''`)}'`;
}
function powerShellQuote(value) {
if (value === "") return "''";
return `'${value.replace(/'/g, "''")}'`;
}
function splitCommand(value) {
const parts = [];
let current = "";
let quote = null;
let hasValue = false;
const pushCurrent = () => {
if (!hasValue) return;
parts.push(current);
current = "";
hasValue = false;
};
for (let i = 0; i < value.length; i++) {
const char = value[i];
if (quote) {
if (char === quote) {
quote = null;
hasValue = true;
continue;
}
if (quote === "\"" && char === "\\") {
const next = value[i + 1];
if (next) {
current += next;
hasValue = true;
i++;
continue;
}
}
current += char;
hasValue = true;
continue;
}
if (char === "\"" || char === "'") {
quote = char;
hasValue = true;
continue;
}
if (/\s/.test(char)) {
pushCurrent();
while (i + 1 < value.length && /\s/.test(value[i + 1])) i++;
continue;
}
if (char === "\\") {
const next = value[i + 1];
if (next) {
current += next;
hasValue = true;
i++;
continue;
}
}
current += char;
hasValue = true;
}
pushCurrent();
return parts;
}
function normalizeShellName(value) {
const shell = (value || "").toLowerCase().replace(/\.exe$/, "");
if (shell === "powershell") return "powershell";
if (shell === "pwsh") return "pwsh";
if (shell === "fish" || shell === "zsh" || shell === "bash") return shell;
return shell;
}
function detectShell() {
const envShell = normalizeShellName(path.basename(process.env.SHELL || ""));
if (process.env.FISH_VERSION) return "fish";
if (process.env.ZSH_VERSION) return "zsh