@simon_he/pi 0.1.24 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -114,6 +114,42 @@ export PI_Lang=en
114
114
 
115
115
  ```
116
116
 
117
+ ## Shell Integration (prun)
118
+
119
+ ```
120
+ # zsh
121
+ eval "$(prun --init zsh)"
122
+
123
+ # bash
124
+ eval "$(prun --init bash)"
125
+
126
+ # fish
127
+ eval (prun --init fish)
128
+ ```
129
+
130
+ > Note: This lets the command selected by prun be available immediately in your shell history (press ↑ to recall).
131
+
132
+ Auto integration (built-in):
133
+
134
+ - In interactive shells, the first `prun` run will append the right line to your shell rc (zsh: `~/.zshrc`, bash: `~/.bashrc`, fish: `~/.config/fish/config.fish`).
135
+ - Disable with `PI_NO_AUTO_INIT=1` (or set `PI_AUTO_INIT=0`).
136
+ - Open a new terminal (or source the rc file) after the first run.
137
+
138
+ Make it persistent:
139
+
140
+ ```
141
+ # zsh
142
+ echo 'eval "$(prun --init zsh)"' >> ~/.zshrc
143
+
144
+ # bash
145
+ echo 'eval "$(prun --init bash)"' >> ~/.bashrc
146
+
147
+ # fish
148
+ echo 'prun --init fish | source' >> ~/.config/fish/config.fish
149
+ ```
150
+
151
+ Reload your shell config (or open a new terminal) after adding the line.
152
+
117
153
  ## Power
118
154
 
119
155
  The current environment is npm | yarn | pnpm, and it supports passing some args --silent
package/dist/index.cjs CHANGED
@@ -44,39 +44,39 @@ let node_module = require("node:module");
44
44
  let node_url = require("node:url");
45
45
 
46
46
  //#region package.json
47
- var version = "0.1.24";
47
+ var version = "0.1.26";
48
48
 
49
49
  //#endregion
50
50
  //#region src/installDeps.ts
51
- const isZh$6 = node_process.default.env.PI_Lang === "zh";
51
+ const isZh$7 = node_process.default.env.PI_Lang === "zh";
52
52
  const gumDocUrl = "https://github.com/charmbracelet/gum";
53
53
  const niDocUrl = "https://github.com/antfu/ni";
54
54
  async function installDeps(options = {}) {
55
55
  const { gum = true, ni = true, strict = true } = options;
56
56
  const platform = node_process.default.platform;
57
57
  if (gum && !await (0, lazy_js_utils_node.isInstallPkg)("gum")) if (platform === "darwin") {
58
- console.log(picocolors.default.cyan(isZh$6 ? "正在为您安装必要的依赖gum..." : "Installing gum..."));
58
+ console.log(picocolors.default.cyan(isZh$7 ? "正在为您安装必要的依赖gum..." : "Installing gum..."));
59
59
  const { status } = await (0, lazy_js_utils_node.jsShell)("brew install gum", [
60
60
  "inherit",
61
61
  "pipe",
62
62
  "inherit"
63
63
  ]);
64
- if (status === 0) console.log(picocolors.default.cyan(isZh$6 ? "gum 安装成功!" : "gum installed successfully!"));
64
+ if (status === 0) console.log(picocolors.default.cyan(isZh$7 ? "gum 安装成功!" : "gum installed successfully!"));
65
65
  else {
66
- console.log(picocolors.default.red(isZh$6 ? `gum 安装失败,请尝试从官网解决安装问题! ${gumDocUrl}` : `gum installation failed, please try manual install: ${gumDocUrl}`));
66
+ console.log(picocolors.default.red(isZh$7 ? `gum 安装失败,请尝试从官网解决安装问题! ${gumDocUrl}` : `gum installation failed, please try manual install: ${gumDocUrl}`));
67
67
  if (strict) node_process.default.exit(1);
68
68
  }
69
- } else console.log(picocolors.default.yellow(isZh$6 ? `未检测到 gum,请根据系统手动安装: ${gumDocUrl}` : `gum not found, please install it manually: ${gumDocUrl}`));
69
+ } else console.log(picocolors.default.yellow(isZh$7 ? `未检测到 gum,请根据系统手动安装: ${gumDocUrl}` : `gum not found, please install it manually: ${gumDocUrl}`));
70
70
  if (ni && !await (0, lazy_js_utils_node.isInstallPkg)("ni")) {
71
- console.log(picocolors.default.cyan(isZh$6 ? "正在为您安装必要的依赖ni..." : "Installing ni..."));
71
+ console.log(picocolors.default.cyan(isZh$7 ? "正在为您安装必要的依赖ni..." : "Installing ni..."));
72
72
  const { status } = await (0, lazy_js_utils_node.jsShell)("npm i -g @antfu/ni", [
73
73
  "inherit",
74
74
  "pipe",
75
75
  "inherit"
76
76
  ]);
77
- if (status === 0) console.log(picocolors.default.cyan(isZh$6 ? "ni 安装成功!" : "ni installed successfully!"));
77
+ if (status === 0) console.log(picocolors.default.cyan(isZh$7 ? "ni 安装成功!" : "ni installed successfully!"));
78
78
  else {
79
- console.log(picocolors.default.red(isZh$6 ? `ni 安装失败,请尝试从官网解决安装问题! ${niDocUrl}` : `ni installation failed, please try manual install: ${niDocUrl}`));
79
+ console.log(picocolors.default.red(isZh$7 ? `ni 安装失败,请尝试从官网解决安装问题! ${niDocUrl}` : `ni installation failed, please try manual install: ${niDocUrl}`));
80
80
  if (strict) node_process.default.exit(1);
81
81
  }
82
82
  }
@@ -84,7 +84,7 @@ async function installDeps(options = {}) {
84
84
 
85
85
  //#endregion
86
86
  //#region src/help.ts
87
- const isZh$5 = node_process.default.env.PI_Lang === "zh";
87
+ const isZh$6 = node_process.default.env.PI_Lang === "zh";
88
88
  async function ensureGum() {
89
89
  if (await (0, lazy_js_utils_node.isInstallPkg)("gum")) return true;
90
90
  await installDeps({
@@ -95,9 +95,9 @@ async function ensureGum() {
95
95
  return await (0, lazy_js_utils_node.isInstallPkg)("gum");
96
96
  }
97
97
  function printPlainVersion() {
98
- console.log(isZh$5 ? `pi 版本: ${version}` : `pi version: ${version}`);
99
- console.log(isZh$5 ? "请为我的努力点一个行 🌟" : "Please give me a 🌟 for my efforts");
100
- console.log(isZh$5 ? "谢谢 🤟" : "Thank you 🤟");
98
+ console.log(isZh$6 ? `pi 版本: ${version}` : `pi version: ${version}`);
99
+ console.log(isZh$6 ? "请为我的努力点一个行 🌟" : "Please give me a 🌟 for my efforts");
100
+ console.log(isZh$6 ? "谢谢 🤟" : "Thank you 🤟");
101
101
  }
102
102
  function printPlainHelp() {
103
103
  console.log([
@@ -118,7 +118,7 @@ function printPlainHelp() {
118
118
  async function help(argv) {
119
119
  const arg = argv[0];
120
120
  if (arg === "-v" || arg === "--version") {
121
- if (await ensureGum()) await (0, lazy_js_utils_node.jsShell)(isZh$5 ? `gum style \
121
+ if (await ensureGum()) await (0, lazy_js_utils_node.jsShell)(isZh$6 ? `gum style \
122
122
  --foreground 212 --border-foreground 212 --border double \
123
123
  --align center --width 50 --margin "1 2" --padding "2 4" \
124
124
  "pi 版本: ${version}" "请为我的努力点一个行 🌟" "谢谢 🤟"` : `gum style \
@@ -146,7 +146,7 @@ function pa() {
146
146
 
147
147
  //#endregion
148
148
  //#region src/detectNode.ts
149
- const isZh$4 = node_process.default.env.PI_Lang === "zh";
149
+ const isZh$5 = node_process.default.env.PI_Lang === "zh";
150
150
  async function detectNode() {
151
151
  let pkg;
152
152
  try {
@@ -165,7 +165,7 @@ async function detectNode() {
165
165
  const hasFnm = await (0, lazy_js_utils_node.isInstallPkg)("fnm");
166
166
  if (!hasGum || !hasFnm) {
167
167
  const missing = [!hasGum ? "gum" : "", !hasFnm ? "fnm" : ""].filter(Boolean).join(", ");
168
- console.log(picocolors.default.yellow(isZh$4 ? `当前 node 版本不满足 ${pkg.engines.node},未检测到 ${missing},请手动切换版本。` : `Current Node version does not satisfy ${pkg.engines.node}. Missing ${missing}. Please switch manually.`));
168
+ console.log(picocolors.default.yellow(isZh$5 ? `当前 node 版本不满足 ${pkg.engines.node},未检测到 ${missing},请手动切换版本。` : `Current Node version does not satisfy ${pkg.engines.node}. Missing ${missing}. Please switch manually.`));
169
169
  return;
170
170
  }
171
171
  const { result, status } = await (0, lazy_js_utils_node.jsShell)(`echo "yes\nno" | gum filter --placeholder=" 当前node版本不满足 ${pkg.engines.node},是否切换node版本"`, [
@@ -197,7 +197,7 @@ const Dw = /\s-Dw/g;
197
197
  const w = /\s-w/g;
198
198
  const D = /\s-D(?!w)/g;
199
199
  const d = /\s-d(?!w)/g;
200
- const isZh$3 = node_process.default.env.PI_Lang === "zh";
200
+ const isZh$4 = node_process.default.env.PI_Lang === "zh";
201
201
  const log$1 = console.log;
202
202
  async function getParams(params) {
203
203
  const root = node_process.default.cwd();
@@ -240,7 +240,7 @@ async function getParams(params) {
240
240
  default: return d.test(params) ? params.replace(d, " -D") : params;
241
241
  }
242
242
  } catch {
243
- console.log(picocolors.default.red(`${isZh$3 ? "package.json并不存在,在以下目录中:" : "package.json has not been found in"} ${node_process.default.cwd()}`));
243
+ console.log(picocolors.default.red(`${isZh$4 ? "package.json并不存在,在以下目录中:" : "package.json has not been found in"} ${node_process.default.cwd()}`));
244
244
  node_process.default.exit(1);
245
245
  }
246
246
  }
@@ -280,7 +280,7 @@ async function getLatestVersion(pkg, isZh = true) {
280
280
  return `${data.join(" ")}${isZh ? " 安装成功! 😊" : " successfully! 😊"}`;
281
281
  }
282
282
  async function pushHistory(command) {
283
- log$1(picocolors.default.bold(picocolors.default.blue(`${isZh$3 ? "快捷指令" : "shortcut command"}: ${command}`)));
283
+ log$1(picocolors.default.bold(picocolors.default.blue(`${isZh$4 ? "快捷指令" : "shortcut command"}: ${command}`)));
284
284
  const shellName = (node_process.default.env.SHELL || "/bin/bash").split("/").pop() || "bash";
285
285
  let historyFile = "";
286
286
  let historyFormat = "bash";
@@ -304,7 +304,7 @@ async function pushHistory(command) {
304
304
  }
305
305
  try {
306
306
  if (!node_fs.default.existsSync(historyFile)) {
307
- log$1(picocolors.default.yellow(`${isZh$3 ? `未找到 ${shellName} 历史文件` : `${shellName} history file not found`}`));
307
+ log$1(picocolors.default.yellow(`${isZh$4 ? `未找到 ${shellName} 历史文件` : `${shellName} history file not found`}`));
308
308
  return;
309
309
  }
310
310
  const raw = node_fs.default.readFileSync(historyFile, "utf8");
@@ -393,22 +393,22 @@ async function pushHistory(command) {
393
393
  node_fs.default.writeFileSync(tmpPath, finalContent, "utf8");
394
394
  node_fs.default.renameSync(tmpPath, historyFile);
395
395
  } catch (err) {
396
- log$1(picocolors.default.red(`${isZh$3 ? `❌ 添加到 ${shellName} 历史记录失败` : `❌ Failed to add to ${shellName} history`}${err ? `: ${String(err)}` : ""}`));
396
+ log$1(picocolors.default.red(`${isZh$4 ? `❌ 添加到 ${shellName} 历史记录失败` : `❌ Failed to add to ${shellName} history`}${err ? `: ${String(err)}` : ""}`));
397
397
  }
398
398
  }
399
399
 
400
400
  //#endregion
401
401
  //#region src/pi.ts
402
- const isZh$2 = node_process.default.env.PI_Lang === "zh";
402
+ const isZh$3 = node_process.default.env.PI_Lang === "zh";
403
403
  async function pi(params, pkg, executor = "ni") {
404
404
  await detectNode();
405
405
  const text = pkg ? `Installing ${params} ...` : "Updating dependency ...";
406
406
  const isLatest = executor === "pil";
407
407
  const start = Date.now();
408
408
  let successMsg = "";
409
- if (isLatest) successMsg = await getLatestVersion(pkg, isZh$2);
410
- else successMsg = pkg ? isZh$2 ? `${pkg} 安装成功! 😊` : `Installed ${pkg} successfully! 😊` : isZh$2 ? "依赖更新成功! 😊" : "Updated dependency successfully! 😊";
411
- const failMsg = pkg ? isZh$2 ? `${params} 安装失败 😭` : `Failed to install ${params} 😭` : isZh$2 ? "依赖更新失败 😭" : "Failed to update dependency 😭";
409
+ if (isLatest) successMsg = await getLatestVersion(pkg, isZh$3);
410
+ else successMsg = pkg ? isZh$3 ? `${pkg} 安装成功! 😊` : `Installed ${pkg} successfully! 😊` : isZh$3 ? "依赖更新成功! 😊" : "Updated dependency successfully! 😊";
411
+ const failMsg = pkg ? isZh$3 ? `${params} 安装失败 😭` : `Failed to install ${params} 😭` : isZh$3 ? "依赖更新失败 😭" : "Failed to update dependency 😭";
412
412
  const isSilent = node_process.default.env.PI_SILENT === "true";
413
413
  let stdio = isSilent ? "inherit" : [
414
414
  "inherit",
@@ -452,7 +452,7 @@ async function pi(params, pkg, executor = "ni") {
452
452
  let { status, result } = await runCommands(cmdList);
453
453
  if (result && result.includes("pnpm versions with respective Node.js version support")) {
454
454
  (0, node_console.log)(result);
455
- (0, node_console.log)(picocolors.default.yellow(isZh$2 ? "正在尝试使用 npm 再次执行..." : "Trying to use npm to run again..."));
455
+ (0, node_console.log)(picocolors.default.yellow(isZh$3 ? "正在尝试使用 npm 再次执行..." : "Trying to use npm to run again..."));
456
456
  const fallbackCommands = isLatest ? latestParams.map((p) => `npm install ${p}`) : [`npm install${newParams ? ` ${newParams}` : runSockets}`];
457
457
  const fallbackResults = await Promise.all(fallbackCommands.map((command) => (0, lazy_js_utils_node.jsShell)(command, { stdio })));
458
458
  const fallbackFailed = fallbackResults.find((r) => r.status !== 0);
@@ -468,7 +468,7 @@ async function pi(params, pkg, executor = "ni") {
468
468
  pushHistory(runCmd);
469
469
  } else if (result && result.includes("Not Found - 404")) {
470
470
  const _pkg = result.match(/\/[^/:]+:/)?.[0].slice(1, -1);
471
- const _result = isZh$2 ? `${_pkg} 包名可能有误或者版本号不存在,并不能在npm中搜索到,请检查` : `${_pkg} the package name may be wrong, and cannot be found in npm, please check`;
471
+ const _result = isZh$3 ? `${_pkg} 包名可能有误或者版本号不存在,并不能在npm中搜索到,请检查` : `${_pkg} the package name may be wrong, and cannot be found in npm, please check`;
472
472
  loading_status.fail(picocolors.default.red(result ? `${failMsg}\n${_result}` : failMsg));
473
473
  } else loading_status.fail(picocolors.default.red(result ? `${failMsg}\n${result}` : failMsg));
474
474
  if (result) {
@@ -498,9 +498,24 @@ function getCcommand() {
498
498
 
499
499
  //#endregion
500
500
  //#region src/pfind.ts
501
- function pfind(params) {
501
+ function isNoHistory$1(value) {
502
+ if (!value) return false;
503
+ const normalized = value.toLowerCase();
504
+ return normalized === "1" || normalized === "true" || normalized === "yes";
505
+ }
506
+ async function pfind(params) {
507
+ const hadNoHistoryEnv = node_process.default.env.CCOMMAND_NO_HISTORY != null || node_process.default.env.NO_HISTORY != null;
508
+ const initialNoHistory = node_process.default.env.CCOMMAND_NO_HISTORY ?? node_process.default.env.NO_HISTORY;
509
+ const shouldWriteHistory = !(hadNoHistoryEnv && isNoHistory$1(initialNoHistory));
502
510
  const { ccommand } = getCcommand();
503
- return ccommand(`find ${params}`);
511
+ const prevNoHistory = node_process.default.env.CCOMMAND_NO_HISTORY;
512
+ if (shouldWriteHistory) delete node_process.default.env.CCOMMAND_NO_HISTORY;
513
+ try {
514
+ await ccommand(`find ${params}`);
515
+ } finally {
516
+ if (prevNoHistory == null) delete node_process.default.env.CCOMMAND_NO_HISTORY;
517
+ else node_process.default.env.CCOMMAND_NO_HISTORY = prevNoHistory;
518
+ }
504
519
  }
505
520
 
506
521
  //#endregion
@@ -615,9 +630,234 @@ async function pix(params) {
615
630
 
616
631
  //#endregion
617
632
  //#region src/prun.ts
618
- function prun(params) {
633
+ async function prun(params) {
634
+ ensurePrunAutoInit();
635
+ const hadNoHistoryEnv = node_process.default.env.CCOMMAND_NO_HISTORY != null || node_process.default.env.NO_HISTORY != null;
636
+ const initialNoHistory = node_process.default.env.CCOMMAND_NO_HISTORY ?? node_process.default.env.NO_HISTORY;
619
637
  const { ccommand } = getCcommand();
620
- return ccommand(params);
638
+ const prevNoHistory = node_process.default.env.CCOMMAND_NO_HISTORY;
639
+ const shouldWriteHistory = !(hadNoHistoryEnv && isNoHistory(initialNoHistory));
640
+ const { lines, restore } = captureOutput();
641
+ node_process.default.env.CCOMMAND_NO_HISTORY = "1";
642
+ try {
643
+ await ccommand(params);
644
+ } finally {
645
+ restore();
646
+ if (prevNoHistory == null) delete node_process.default.env.CCOMMAND_NO_HISTORY;
647
+ else node_process.default.env.CCOMMAND_NO_HISTORY = prevNoHistory;
648
+ }
649
+ const shortcut = extractShortcutCommand(lines());
650
+ if (shortcut && shouldWriteHistory) writeShellHistory(shortcut);
651
+ }
652
+ const isZh$2 = node_process.default.env.PI_Lang === "zh";
653
+ const safeShellValue = /^[\w./:@%+=,-]+$/;
654
+ const ansiEscape = String.fromCharCode(27);
655
+ const ansiRegex = new RegExp(`${ansiEscape}\\[[0-9;]*m`, "g");
656
+ function stripAnsi(value) {
657
+ return value.replace(ansiRegex, "");
658
+ }
659
+ function captureOutput() {
660
+ const stdoutWrite = node_process.default.stdout.write.bind(node_process.default.stdout);
661
+ const stderrWrite = node_process.default.stderr.write.bind(node_process.default.stderr);
662
+ let buffer = "";
663
+ const output = [];
664
+ const pushBufferLines = () => {
665
+ let idx = buffer.indexOf("\n");
666
+ while (idx !== -1) {
667
+ output.push(buffer.slice(0, idx));
668
+ buffer = buffer.slice(idx + 1);
669
+ idx = buffer.indexOf("\n");
670
+ }
671
+ };
672
+ node_process.default.stdout.write = ((chunk, encoding, cb) => {
673
+ const text = typeof chunk === "string" ? chunk : chunk?.toString(encoding || "utf8");
674
+ if (text) {
675
+ buffer += text;
676
+ pushBufferLines();
677
+ }
678
+ return stdoutWrite(chunk, encoding, cb);
679
+ });
680
+ node_process.default.stderr.write = ((chunk, encoding, cb) => {
681
+ const text = typeof chunk === "string" ? chunk : chunk?.toString(encoding || "utf8");
682
+ if (text) {
683
+ buffer += text;
684
+ pushBufferLines();
685
+ }
686
+ return stderrWrite(chunk, encoding, cb);
687
+ });
688
+ const restore = () => {
689
+ if (buffer.trim()) output.push(buffer);
690
+ buffer = "";
691
+ node_process.default.stdout.write = stdoutWrite;
692
+ node_process.default.stderr.write = stderrWrite;
693
+ };
694
+ return {
695
+ lines: () => output.slice(),
696
+ restore
697
+ };
698
+ }
699
+ function isNoHistory(value) {
700
+ if (!value) return false;
701
+ const normalized = value.toLowerCase();
702
+ return normalized === "1" || normalized === "true" || normalized === "yes";
703
+ }
704
+ function stripTrailingNonAscii(value) {
705
+ let end = value.length;
706
+ while (end > 0) {
707
+ if (value.charCodeAt(end - 1) <= 127) break;
708
+ end -= 1;
709
+ }
710
+ return end === value.length ? value : value.slice(0, end);
711
+ }
712
+ function extractShortcutCommand(logs) {
713
+ for (let i = logs.length - 1; i >= 0; i--) {
714
+ const line = stripAnsi(logs[i]).trim();
715
+ if (!line) continue;
716
+ const shortcutMatch = line.match(/(?:shortcut command|快捷指令)\s*:\s*(prun\b.*)$/i);
717
+ if (shortcutMatch) return shortcutMatch[1].trim();
718
+ const runningMatch = line.match(/(?:is running for you\.\.\.|正在为您执行\.{3})\s+(\S.*)$/i);
719
+ if (runningMatch) {
720
+ const command = runningMatch[1].trim();
721
+ if (command) return command.startsWith("prun ") ? command : `prun ${command}`;
722
+ }
723
+ const unquoted = stripTrailingNonAscii(line).trim().replace(/^['"]|['"]$/g, "");
724
+ if (unquoted.startsWith("prun ")) return unquoted;
725
+ if (unquoted === "prun") return unquoted;
726
+ const idx = unquoted.lastIndexOf("prun ");
727
+ if (idx !== -1) return unquoted.slice(idx).trim();
728
+ }
729
+ return "";
730
+ }
731
+ function resolveHistoryTarget() {
732
+ const shellEnv = node_process.default.env.SHELL || "/bin/bash";
733
+ const shellName = node_process.default.env.FISH_VERSION && "fish" || node_process.default.env.ZSH_VERSION && "zsh" || node_process.default.env.BASH_VERSION && "bash" || shellEnv.split("/").pop() || "bash";
734
+ const home = node_process.default.env.HOME || node_os.default.homedir();
735
+ const histFileEnv = node_process.default.env.HISTFILE;
736
+ const xdgDataHome = node_process.default.env.XDG_DATA_HOME;
737
+ let historyFile = "";
738
+ let historyKind = "bash";
739
+ if (shellName === "zsh") {
740
+ historyKind = "zsh";
741
+ historyFile = histFileEnv || node_path.default.join(home, ".zsh_history");
742
+ } else if (shellName === "fish") {
743
+ historyKind = "fish";
744
+ const base = xdgDataHome || node_path.default.join(home, ".local", "share");
745
+ historyFile = node_path.default.join(base, "fish", "fish_history");
746
+ } else {
747
+ historyKind = "bash";
748
+ historyFile = histFileEnv || node_path.default.join(home, ".bash_history");
749
+ }
750
+ return {
751
+ historyFile,
752
+ historyKind
753
+ };
754
+ }
755
+ function buildHistoryEntry(command, kind) {
756
+ const timestamp = Math.floor(Date.now() / 1e3);
757
+ if (kind === "zsh") return `: ${timestamp}:0;${command}\n`;
758
+ if (kind === "fish") return `- cmd: ${command}\n when: ${timestamp}\n`;
759
+ if (node_process.default.env.HISTTIMEFORMAT) return `#${timestamp}\n${command}\n`;
760
+ return `${command}\n`;
761
+ }
762
+ function writeShellHistory(command) {
763
+ try {
764
+ const { historyFile, historyKind } = resolveHistoryTarget();
765
+ if (!historyFile) return;
766
+ if (!node_fs.default.existsSync(historyFile)) {
767
+ console.log(picocolors.default.yellow(isZh$2 ? `未找到 history 文件: ${historyFile}` : `History file not found: ${historyFile}`));
768
+ return;
769
+ }
770
+ const entry = buildHistoryEntry(command, historyKind);
771
+ node_fs.default.appendFileSync(historyFile, entry, "utf8");
772
+ } catch (error) {
773
+ console.log(picocolors.default.red(`${isZh$2 ? "写入 history 失败" : "Failed to write history"}${error ? `: ${String(error)}` : ""}`));
774
+ }
775
+ }
776
+ function shellQuote(value) {
777
+ if (value === "") return "''";
778
+ if (safeShellValue.test(value)) return value;
779
+ return `'${value.replace(/'/g, `'\\''`)}'`;
780
+ }
781
+ function detectShell() {
782
+ const envShell = node_process.default.env.SHELL || "";
783
+ if (node_process.default.env.FISH_VERSION) return "fish";
784
+ if (node_process.default.env.ZSH_VERSION) return "zsh";
785
+ if (node_process.default.env.BASH_VERSION) return "bash";
786
+ return envShell.split("/").pop() || "zsh";
787
+ }
788
+ function ensurePrunAutoInit() {
789
+ if (!shouldAutoInit()) return;
790
+ const shell = detectShell();
791
+ const home = node_process.default.env.HOME || node_os.default.homedir();
792
+ let rcFile = "";
793
+ let initLine = "";
794
+ if (shell === "zsh") {
795
+ const zdotdir = node_process.default.env.ZDOTDIR || home;
796
+ rcFile = node_path.default.join(zdotdir, ".zshrc");
797
+ initLine = "eval \"$(prun --init zsh)\"";
798
+ } else if (shell === "bash") {
799
+ rcFile = node_path.default.join(home, ".bashrc");
800
+ initLine = "eval \"$(prun --init bash)\"";
801
+ } else if (shell === "fish") {
802
+ const configHome = node_process.default.env.XDG_CONFIG_HOME || node_path.default.join(home, ".config");
803
+ rcFile = node_path.default.join(configHome, "fish", "config.fish");
804
+ initLine = "prun --init fish | source";
805
+ } else return;
806
+ try {
807
+ const dir = node_path.default.dirname(rcFile);
808
+ if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
809
+ const content = node_fs.default.existsSync(rcFile) ? node_fs.default.readFileSync(rcFile, "utf8") : "";
810
+ if (!/prun\s+--init/.test(content)) {
811
+ const prefix = content.length && !content.endsWith("\n") ? "\n" : "";
812
+ node_fs.default.appendFileSync(rcFile, `${prefix}${initLine}\n`, "utf8");
813
+ }
814
+ } catch {}
815
+ }
816
+ function shouldAutoInit() {
817
+ const auto = node_process.default.env.PI_AUTO_INIT || node_process.default.env.PRUN_AUTO_INIT;
818
+ if (auto != null) return isNoHistory(auto);
819
+ if (isNoHistory(node_process.default.env.PI_NO_AUTO_INIT || node_process.default.env.PRUN_NO_AUTO_INIT)) return false;
820
+ if (node_process.default.env.CI) return false;
821
+ if (!node_process.default.stdout.isTTY || !node_process.default.stdin.isTTY) return false;
822
+ return true;
823
+ }
824
+ function printPrunInit(args = []) {
825
+ const shellArg = args[0];
826
+ const binArg = args[1];
827
+ const bin = shellQuote(binArg || node_process.default.env.PRUN_BIN || "prun");
828
+ const shell = shellArg || detectShell() || "zsh";
829
+ let script = "";
830
+ if (shell === "zsh") script = [
831
+ "prun() {",
832
+ ` local bin=${bin}`,
833
+ " local -a cmd",
834
+ " cmd=(${=bin})",
835
+ " command \"${cmd[@]}\" \"$@\"",
836
+ " fc -R",
837
+ "}"
838
+ ].join("\n");
839
+ else if (shell === "bash") script = [
840
+ "prun() {",
841
+ ` local bin=${bin}`,
842
+ " local -a cmd",
843
+ " read -r -a cmd <<< \"$bin\"",
844
+ " command \"${cmd[@]}\" \"$@\"",
845
+ " history -n",
846
+ "}"
847
+ ].join("\n");
848
+ else if (shell === "fish") script = [
849
+ "function prun",
850
+ ` set -l bin ${bin}`,
851
+ " set -l cmd (string split -- \" \" $bin)",
852
+ " command $cmd $argv",
853
+ " history --merge",
854
+ "end"
855
+ ].join("\n");
856
+ else {
857
+ console.log(picocolors.default.red(isZh$2 ? `不支持的 shell: ${shell}` : `Unsupported shell: ${shell}`));
858
+ return;
859
+ }
860
+ console.log(script);
621
861
  }
622
862
 
623
863
  //#endregion
@@ -700,6 +940,10 @@ async function setup() {
700
940
  }
701
941
  const argv = node_process.default.argv.slice(2);
702
942
  await help(argv);
943
+ if ((exec === "prun" || exec === "prun.mjs") && argv[0] === "--init") {
944
+ printPrunInit(argv.slice(1));
945
+ return;
946
+ }
703
947
  let params = (0, lazy_js_utils.spaceFormat)(argv.join(" ")).trim();
704
948
  if (!await (0, lazy_js_utils_node.hasPkg)(rootPath)) {
705
949
  if (await (0, lazy_js_utils_node.isGo)(rootPath)) {
package/dist/index.mjs CHANGED
@@ -11,39 +11,39 @@ import os from "node:os";
11
11
  import { fileURLToPath } from "node:url";
12
12
 
13
13
  //#region package.json
14
- var version = "0.1.24";
14
+ var version = "0.1.26";
15
15
 
16
16
  //#endregion
17
17
  //#region src/installDeps.ts
18
- const isZh$6 = process.env.PI_Lang === "zh";
18
+ const isZh$7 = process.env.PI_Lang === "zh";
19
19
  const gumDocUrl = "https://github.com/charmbracelet/gum";
20
20
  const niDocUrl = "https://github.com/antfu/ni";
21
21
  async function installDeps(options = {}) {
22
22
  const { gum = true, ni = true, strict = true } = options;
23
23
  const platform = process.platform;
24
24
  if (gum && !await isInstallPkg("gum")) if (platform === "darwin") {
25
- console.log(color.cyan(isZh$6 ? "正在为您安装必要的依赖gum..." : "Installing gum..."));
25
+ console.log(color.cyan(isZh$7 ? "正在为您安装必要的依赖gum..." : "Installing gum..."));
26
26
  const { status } = await jsShell("brew install gum", [
27
27
  "inherit",
28
28
  "pipe",
29
29
  "inherit"
30
30
  ]);
31
- if (status === 0) console.log(color.cyan(isZh$6 ? "gum 安装成功!" : "gum installed successfully!"));
31
+ if (status === 0) console.log(color.cyan(isZh$7 ? "gum 安装成功!" : "gum installed successfully!"));
32
32
  else {
33
- console.log(color.red(isZh$6 ? `gum 安装失败,请尝试从官网解决安装问题! ${gumDocUrl}` : `gum installation failed, please try manual install: ${gumDocUrl}`));
33
+ console.log(color.red(isZh$7 ? `gum 安装失败,请尝试从官网解决安装问题! ${gumDocUrl}` : `gum installation failed, please try manual install: ${gumDocUrl}`));
34
34
  if (strict) process.exit(1);
35
35
  }
36
- } else console.log(color.yellow(isZh$6 ? `未检测到 gum,请根据系统手动安装: ${gumDocUrl}` : `gum not found, please install it manually: ${gumDocUrl}`));
36
+ } else console.log(color.yellow(isZh$7 ? `未检测到 gum,请根据系统手动安装: ${gumDocUrl}` : `gum not found, please install it manually: ${gumDocUrl}`));
37
37
  if (ni && !await isInstallPkg("ni")) {
38
- console.log(color.cyan(isZh$6 ? "正在为您安装必要的依赖ni..." : "Installing ni..."));
38
+ console.log(color.cyan(isZh$7 ? "正在为您安装必要的依赖ni..." : "Installing ni..."));
39
39
  const { status } = await jsShell("npm i -g @antfu/ni", [
40
40
  "inherit",
41
41
  "pipe",
42
42
  "inherit"
43
43
  ]);
44
- if (status === 0) console.log(color.cyan(isZh$6 ? "ni 安装成功!" : "ni installed successfully!"));
44
+ if (status === 0) console.log(color.cyan(isZh$7 ? "ni 安装成功!" : "ni installed successfully!"));
45
45
  else {
46
- console.log(color.red(isZh$6 ? `ni 安装失败,请尝试从官网解决安装问题! ${niDocUrl}` : `ni installation failed, please try manual install: ${niDocUrl}`));
46
+ console.log(color.red(isZh$7 ? `ni 安装失败,请尝试从官网解决安装问题! ${niDocUrl}` : `ni installation failed, please try manual install: ${niDocUrl}`));
47
47
  if (strict) process.exit(1);
48
48
  }
49
49
  }
@@ -51,7 +51,7 @@ async function installDeps(options = {}) {
51
51
 
52
52
  //#endregion
53
53
  //#region src/help.ts
54
- const isZh$5 = process.env.PI_Lang === "zh";
54
+ const isZh$6 = process.env.PI_Lang === "zh";
55
55
  async function ensureGum() {
56
56
  if (await isInstallPkg("gum")) return true;
57
57
  await installDeps({
@@ -62,9 +62,9 @@ async function ensureGum() {
62
62
  return await isInstallPkg("gum");
63
63
  }
64
64
  function printPlainVersion() {
65
- console.log(isZh$5 ? `pi 版本: ${version}` : `pi version: ${version}`);
66
- console.log(isZh$5 ? "请为我的努力点一个行 🌟" : "Please give me a 🌟 for my efforts");
67
- console.log(isZh$5 ? "谢谢 🤟" : "Thank you 🤟");
65
+ console.log(isZh$6 ? `pi 版本: ${version}` : `pi version: ${version}`);
66
+ console.log(isZh$6 ? "请为我的努力点一个行 🌟" : "Please give me a 🌟 for my efforts");
67
+ console.log(isZh$6 ? "谢谢 🤟" : "Thank you 🤟");
68
68
  }
69
69
  function printPlainHelp() {
70
70
  console.log([
@@ -85,7 +85,7 @@ function printPlainHelp() {
85
85
  async function help(argv) {
86
86
  const arg = argv[0];
87
87
  if (arg === "-v" || arg === "--version") {
88
- if (await ensureGum()) await jsShell(isZh$5 ? `gum style \
88
+ if (await ensureGum()) await jsShell(isZh$6 ? `gum style \
89
89
  --foreground 212 --border-foreground 212 --border double \
90
90
  --align center --width 50 --margin "1 2" --padding "2 4" \
91
91
  "pi 版本: ${version}" "请为我的努力点一个行 🌟" "谢谢 🤟"` : `gum style \
@@ -113,7 +113,7 @@ function pa() {
113
113
 
114
114
  //#endregion
115
115
  //#region src/detectNode.ts
116
- const isZh$4 = process.env.PI_Lang === "zh";
116
+ const isZh$5 = process.env.PI_Lang === "zh";
117
117
  async function detectNode() {
118
118
  let pkg;
119
119
  try {
@@ -132,7 +132,7 @@ async function detectNode() {
132
132
  const hasFnm = await isInstallPkg("fnm");
133
133
  if (!hasGum || !hasFnm) {
134
134
  const missing = [!hasGum ? "gum" : "", !hasFnm ? "fnm" : ""].filter(Boolean).join(", ");
135
- console.log(color.yellow(isZh$4 ? `当前 node 版本不满足 ${pkg.engines.node},未检测到 ${missing},请手动切换版本。` : `Current Node version does not satisfy ${pkg.engines.node}. Missing ${missing}. Please switch manually.`));
135
+ console.log(color.yellow(isZh$5 ? `当前 node 版本不满足 ${pkg.engines.node},未检测到 ${missing},请手动切换版本。` : `Current Node version does not satisfy ${pkg.engines.node}. Missing ${missing}. Please switch manually.`));
136
136
  return;
137
137
  }
138
138
  const { result, status } = await jsShell(`echo "yes\nno" | gum filter --placeholder=" 当前node版本不满足 ${pkg.engines.node},是否切换node版本"`, [
@@ -164,7 +164,7 @@ const Dw = /\s-Dw/g;
164
164
  const w = /\s-w/g;
165
165
  const D = /\s-D(?!w)/g;
166
166
  const d = /\s-d(?!w)/g;
167
- const isZh$3 = process.env.PI_Lang === "zh";
167
+ const isZh$4 = process.env.PI_Lang === "zh";
168
168
  const log$1 = console.log;
169
169
  async function getParams(params) {
170
170
  const root = process.cwd();
@@ -207,7 +207,7 @@ async function getParams(params) {
207
207
  default: return d.test(params) ? params.replace(d, " -D") : params;
208
208
  }
209
209
  } catch {
210
- console.log(color.red(`${isZh$3 ? "package.json并不存在,在以下目录中:" : "package.json has not been found in"} ${process.cwd()}`));
210
+ console.log(color.red(`${isZh$4 ? "package.json并不存在,在以下目录中:" : "package.json has not been found in"} ${process.cwd()}`));
211
211
  process.exit(1);
212
212
  }
213
213
  }
@@ -247,7 +247,7 @@ async function getLatestVersion(pkg, isZh = true) {
247
247
  return `${data.join(" ")}${isZh ? " 安装成功! 😊" : " successfully! 😊"}`;
248
248
  }
249
249
  async function pushHistory(command) {
250
- log$1(color.bold(color.blue(`${isZh$3 ? "快捷指令" : "shortcut command"}: ${command}`)));
250
+ log$1(color.bold(color.blue(`${isZh$4 ? "快捷指令" : "shortcut command"}: ${command}`)));
251
251
  const shellName = (process.env.SHELL || "/bin/bash").split("/").pop() || "bash";
252
252
  let historyFile = "";
253
253
  let historyFormat = "bash";
@@ -271,7 +271,7 @@ async function pushHistory(command) {
271
271
  }
272
272
  try {
273
273
  if (!fs.existsSync(historyFile)) {
274
- log$1(color.yellow(`${isZh$3 ? `未找到 ${shellName} 历史文件` : `${shellName} history file not found`}`));
274
+ log$1(color.yellow(`${isZh$4 ? `未找到 ${shellName} 历史文件` : `${shellName} history file not found`}`));
275
275
  return;
276
276
  }
277
277
  const raw = fs.readFileSync(historyFile, "utf8");
@@ -360,22 +360,22 @@ async function pushHistory(command) {
360
360
  fs.writeFileSync(tmpPath, finalContent, "utf8");
361
361
  fs.renameSync(tmpPath, historyFile);
362
362
  } catch (err) {
363
- log$1(color.red(`${isZh$3 ? `❌ 添加到 ${shellName} 历史记录失败` : `❌ Failed to add to ${shellName} history`}${err ? `: ${String(err)}` : ""}`));
363
+ log$1(color.red(`${isZh$4 ? `❌ 添加到 ${shellName} 历史记录失败` : `❌ Failed to add to ${shellName} history`}${err ? `: ${String(err)}` : ""}`));
364
364
  }
365
365
  }
366
366
 
367
367
  //#endregion
368
368
  //#region src/pi.ts
369
- const isZh$2 = process.env.PI_Lang === "zh";
369
+ const isZh$3 = process.env.PI_Lang === "zh";
370
370
  async function pi(params, pkg, executor = "ni") {
371
371
  await detectNode();
372
372
  const text = pkg ? `Installing ${params} ...` : "Updating dependency ...";
373
373
  const isLatest = executor === "pil";
374
374
  const start = Date.now();
375
375
  let successMsg = "";
376
- if (isLatest) successMsg = await getLatestVersion(pkg, isZh$2);
377
- else successMsg = pkg ? isZh$2 ? `${pkg} 安装成功! 😊` : `Installed ${pkg} successfully! 😊` : isZh$2 ? "依赖更新成功! 😊" : "Updated dependency successfully! 😊";
378
- const failMsg = pkg ? isZh$2 ? `${params} 安装失败 😭` : `Failed to install ${params} 😭` : isZh$2 ? "依赖更新失败 😭" : "Failed to update dependency 😭";
376
+ if (isLatest) successMsg = await getLatestVersion(pkg, isZh$3);
377
+ else successMsg = pkg ? isZh$3 ? `${pkg} 安装成功! 😊` : `Installed ${pkg} successfully! 😊` : isZh$3 ? "依赖更新成功! 😊" : "Updated dependency successfully! 😊";
378
+ const failMsg = pkg ? isZh$3 ? `${params} 安装失败 😭` : `Failed to install ${params} 😭` : isZh$3 ? "依赖更新失败 😭" : "Failed to update dependency 😭";
379
379
  const isSilent = process.env.PI_SILENT === "true";
380
380
  let stdio = isSilent ? "inherit" : [
381
381
  "inherit",
@@ -419,7 +419,7 @@ async function pi(params, pkg, executor = "ni") {
419
419
  let { status, result } = await runCommands(cmdList);
420
420
  if (result && result.includes("pnpm versions with respective Node.js version support")) {
421
421
  log(result);
422
- log(color.yellow(isZh$2 ? "正在尝试使用 npm 再次执行..." : "Trying to use npm to run again..."));
422
+ log(color.yellow(isZh$3 ? "正在尝试使用 npm 再次执行..." : "Trying to use npm to run again..."));
423
423
  const fallbackCommands = isLatest ? latestParams.map((p) => `npm install ${p}`) : [`npm install${newParams ? ` ${newParams}` : runSockets}`];
424
424
  const fallbackResults = await Promise.all(fallbackCommands.map((command) => jsShell(command, { stdio })));
425
425
  const fallbackFailed = fallbackResults.find((r) => r.status !== 0);
@@ -435,7 +435,7 @@ async function pi(params, pkg, executor = "ni") {
435
435
  pushHistory(runCmd);
436
436
  } else if (result && result.includes("Not Found - 404")) {
437
437
  const _pkg = result.match(/\/[^/:]+:/)?.[0].slice(1, -1);
438
- const _result = isZh$2 ? `${_pkg} 包名可能有误或者版本号不存在,并不能在npm中搜索到,请检查` : `${_pkg} the package name may be wrong, and cannot be found in npm, please check`;
438
+ const _result = isZh$3 ? `${_pkg} 包名可能有误或者版本号不存在,并不能在npm中搜索到,请检查` : `${_pkg} the package name may be wrong, and cannot be found in npm, please check`;
439
439
  loading_status.fail(color.red(result ? `${failMsg}\n${_result}` : failMsg));
440
440
  } else loading_status.fail(color.red(result ? `${failMsg}\n${result}` : failMsg));
441
441
  if (result) {
@@ -465,9 +465,24 @@ function getCcommand() {
465
465
 
466
466
  //#endregion
467
467
  //#region src/pfind.ts
468
- function pfind(params) {
468
+ function isNoHistory$1(value) {
469
+ if (!value) return false;
470
+ const normalized = value.toLowerCase();
471
+ return normalized === "1" || normalized === "true" || normalized === "yes";
472
+ }
473
+ async function pfind(params) {
474
+ const hadNoHistoryEnv = process.env.CCOMMAND_NO_HISTORY != null || process.env.NO_HISTORY != null;
475
+ const initialNoHistory = process.env.CCOMMAND_NO_HISTORY ?? process.env.NO_HISTORY;
476
+ const shouldWriteHistory = !(hadNoHistoryEnv && isNoHistory$1(initialNoHistory));
469
477
  const { ccommand } = getCcommand();
470
- return ccommand(`find ${params}`);
478
+ const prevNoHistory = process.env.CCOMMAND_NO_HISTORY;
479
+ if (shouldWriteHistory) delete process.env.CCOMMAND_NO_HISTORY;
480
+ try {
481
+ await ccommand(`find ${params}`);
482
+ } finally {
483
+ if (prevNoHistory == null) delete process.env.CCOMMAND_NO_HISTORY;
484
+ else process.env.CCOMMAND_NO_HISTORY = prevNoHistory;
485
+ }
471
486
  }
472
487
 
473
488
  //#endregion
@@ -582,9 +597,234 @@ async function pix(params) {
582
597
 
583
598
  //#endregion
584
599
  //#region src/prun.ts
585
- function prun(params) {
600
+ async function prun(params) {
601
+ ensurePrunAutoInit();
602
+ const hadNoHistoryEnv = process.env.CCOMMAND_NO_HISTORY != null || process.env.NO_HISTORY != null;
603
+ const initialNoHistory = process.env.CCOMMAND_NO_HISTORY ?? process.env.NO_HISTORY;
586
604
  const { ccommand } = getCcommand();
587
- return ccommand(params);
605
+ const prevNoHistory = process.env.CCOMMAND_NO_HISTORY;
606
+ const shouldWriteHistory = !(hadNoHistoryEnv && isNoHistory(initialNoHistory));
607
+ const { lines, restore } = captureOutput();
608
+ process.env.CCOMMAND_NO_HISTORY = "1";
609
+ try {
610
+ await ccommand(params);
611
+ } finally {
612
+ restore();
613
+ if (prevNoHistory == null) delete process.env.CCOMMAND_NO_HISTORY;
614
+ else process.env.CCOMMAND_NO_HISTORY = prevNoHistory;
615
+ }
616
+ const shortcut = extractShortcutCommand(lines());
617
+ if (shortcut && shouldWriteHistory) writeShellHistory(shortcut);
618
+ }
619
+ const isZh$2 = process.env.PI_Lang === "zh";
620
+ const safeShellValue = /^[\w./:@%+=,-]+$/;
621
+ const ansiEscape = String.fromCharCode(27);
622
+ const ansiRegex = new RegExp(`${ansiEscape}\\[[0-9;]*m`, "g");
623
+ function stripAnsi(value) {
624
+ return value.replace(ansiRegex, "");
625
+ }
626
+ function captureOutput() {
627
+ const stdoutWrite = process.stdout.write.bind(process.stdout);
628
+ const stderrWrite = process.stderr.write.bind(process.stderr);
629
+ let buffer = "";
630
+ const output = [];
631
+ const pushBufferLines = () => {
632
+ let idx = buffer.indexOf("\n");
633
+ while (idx !== -1) {
634
+ output.push(buffer.slice(0, idx));
635
+ buffer = buffer.slice(idx + 1);
636
+ idx = buffer.indexOf("\n");
637
+ }
638
+ };
639
+ process.stdout.write = ((chunk, encoding, cb) => {
640
+ const text = typeof chunk === "string" ? chunk : chunk?.toString(encoding || "utf8");
641
+ if (text) {
642
+ buffer += text;
643
+ pushBufferLines();
644
+ }
645
+ return stdoutWrite(chunk, encoding, cb);
646
+ });
647
+ process.stderr.write = ((chunk, encoding, cb) => {
648
+ const text = typeof chunk === "string" ? chunk : chunk?.toString(encoding || "utf8");
649
+ if (text) {
650
+ buffer += text;
651
+ pushBufferLines();
652
+ }
653
+ return stderrWrite(chunk, encoding, cb);
654
+ });
655
+ const restore = () => {
656
+ if (buffer.trim()) output.push(buffer);
657
+ buffer = "";
658
+ process.stdout.write = stdoutWrite;
659
+ process.stderr.write = stderrWrite;
660
+ };
661
+ return {
662
+ lines: () => output.slice(),
663
+ restore
664
+ };
665
+ }
666
+ function isNoHistory(value) {
667
+ if (!value) return false;
668
+ const normalized = value.toLowerCase();
669
+ return normalized === "1" || normalized === "true" || normalized === "yes";
670
+ }
671
+ function stripTrailingNonAscii(value) {
672
+ let end = value.length;
673
+ while (end > 0) {
674
+ if (value.charCodeAt(end - 1) <= 127) break;
675
+ end -= 1;
676
+ }
677
+ return end === value.length ? value : value.slice(0, end);
678
+ }
679
+ function extractShortcutCommand(logs) {
680
+ for (let i = logs.length - 1; i >= 0; i--) {
681
+ const line = stripAnsi(logs[i]).trim();
682
+ if (!line) continue;
683
+ const shortcutMatch = line.match(/(?:shortcut command|快捷指令)\s*:\s*(prun\b.*)$/i);
684
+ if (shortcutMatch) return shortcutMatch[1].trim();
685
+ const runningMatch = line.match(/(?:is running for you\.\.\.|正在为您执行\.{3})\s+(\S.*)$/i);
686
+ if (runningMatch) {
687
+ const command = runningMatch[1].trim();
688
+ if (command) return command.startsWith("prun ") ? command : `prun ${command}`;
689
+ }
690
+ const unquoted = stripTrailingNonAscii(line).trim().replace(/^['"]|['"]$/g, "");
691
+ if (unquoted.startsWith("prun ")) return unquoted;
692
+ if (unquoted === "prun") return unquoted;
693
+ const idx = unquoted.lastIndexOf("prun ");
694
+ if (idx !== -1) return unquoted.slice(idx).trim();
695
+ }
696
+ return "";
697
+ }
698
+ function resolveHistoryTarget() {
699
+ const shellEnv = process.env.SHELL || "/bin/bash";
700
+ const shellName = process.env.FISH_VERSION && "fish" || process.env.ZSH_VERSION && "zsh" || process.env.BASH_VERSION && "bash" || shellEnv.split("/").pop() || "bash";
701
+ const home = process.env.HOME || os.homedir();
702
+ const histFileEnv = process.env.HISTFILE;
703
+ const xdgDataHome = process.env.XDG_DATA_HOME;
704
+ let historyFile = "";
705
+ let historyKind = "bash";
706
+ if (shellName === "zsh") {
707
+ historyKind = "zsh";
708
+ historyFile = histFileEnv || path.join(home, ".zsh_history");
709
+ } else if (shellName === "fish") {
710
+ historyKind = "fish";
711
+ const base = xdgDataHome || path.join(home, ".local", "share");
712
+ historyFile = path.join(base, "fish", "fish_history");
713
+ } else {
714
+ historyKind = "bash";
715
+ historyFile = histFileEnv || path.join(home, ".bash_history");
716
+ }
717
+ return {
718
+ historyFile,
719
+ historyKind
720
+ };
721
+ }
722
+ function buildHistoryEntry(command, kind) {
723
+ const timestamp = Math.floor(Date.now() / 1e3);
724
+ if (kind === "zsh") return `: ${timestamp}:0;${command}\n`;
725
+ if (kind === "fish") return `- cmd: ${command}\n when: ${timestamp}\n`;
726
+ if (process.env.HISTTIMEFORMAT) return `#${timestamp}\n${command}\n`;
727
+ return `${command}\n`;
728
+ }
729
+ function writeShellHistory(command) {
730
+ try {
731
+ const { historyFile, historyKind } = resolveHistoryTarget();
732
+ if (!historyFile) return;
733
+ if (!fs.existsSync(historyFile)) {
734
+ console.log(color.yellow(isZh$2 ? `未找到 history 文件: ${historyFile}` : `History file not found: ${historyFile}`));
735
+ return;
736
+ }
737
+ const entry = buildHistoryEntry(command, historyKind);
738
+ fs.appendFileSync(historyFile, entry, "utf8");
739
+ } catch (error) {
740
+ console.log(color.red(`${isZh$2 ? "写入 history 失败" : "Failed to write history"}${error ? `: ${String(error)}` : ""}`));
741
+ }
742
+ }
743
+ function shellQuote(value) {
744
+ if (value === "") return "''";
745
+ if (safeShellValue.test(value)) return value;
746
+ return `'${value.replace(/'/g, `'\\''`)}'`;
747
+ }
748
+ function detectShell() {
749
+ const envShell = process.env.SHELL || "";
750
+ if (process.env.FISH_VERSION) return "fish";
751
+ if (process.env.ZSH_VERSION) return "zsh";
752
+ if (process.env.BASH_VERSION) return "bash";
753
+ return envShell.split("/").pop() || "zsh";
754
+ }
755
+ function ensurePrunAutoInit() {
756
+ if (!shouldAutoInit()) return;
757
+ const shell = detectShell();
758
+ const home = process.env.HOME || os.homedir();
759
+ let rcFile = "";
760
+ let initLine = "";
761
+ if (shell === "zsh") {
762
+ const zdotdir = process.env.ZDOTDIR || home;
763
+ rcFile = path.join(zdotdir, ".zshrc");
764
+ initLine = "eval \"$(prun --init zsh)\"";
765
+ } else if (shell === "bash") {
766
+ rcFile = path.join(home, ".bashrc");
767
+ initLine = "eval \"$(prun --init bash)\"";
768
+ } else if (shell === "fish") {
769
+ const configHome = process.env.XDG_CONFIG_HOME || path.join(home, ".config");
770
+ rcFile = path.join(configHome, "fish", "config.fish");
771
+ initLine = "prun --init fish | source";
772
+ } else return;
773
+ try {
774
+ const dir = path.dirname(rcFile);
775
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
776
+ const content = fs.existsSync(rcFile) ? fs.readFileSync(rcFile, "utf8") : "";
777
+ if (!/prun\s+--init/.test(content)) {
778
+ const prefix = content.length && !content.endsWith("\n") ? "\n" : "";
779
+ fs.appendFileSync(rcFile, `${prefix}${initLine}\n`, "utf8");
780
+ }
781
+ } catch {}
782
+ }
783
+ function shouldAutoInit() {
784
+ const auto = process.env.PI_AUTO_INIT || process.env.PRUN_AUTO_INIT;
785
+ if (auto != null) return isNoHistory(auto);
786
+ if (isNoHistory(process.env.PI_NO_AUTO_INIT || process.env.PRUN_NO_AUTO_INIT)) return false;
787
+ if (process.env.CI) return false;
788
+ if (!process.stdout.isTTY || !process.stdin.isTTY) return false;
789
+ return true;
790
+ }
791
+ function printPrunInit(args = []) {
792
+ const shellArg = args[0];
793
+ const binArg = args[1];
794
+ const bin = shellQuote(binArg || process.env.PRUN_BIN || "prun");
795
+ const shell = shellArg || detectShell() || "zsh";
796
+ let script = "";
797
+ if (shell === "zsh") script = [
798
+ "prun() {",
799
+ ` local bin=${bin}`,
800
+ " local -a cmd",
801
+ " cmd=(${=bin})",
802
+ " command \"${cmd[@]}\" \"$@\"",
803
+ " fc -R",
804
+ "}"
805
+ ].join("\n");
806
+ else if (shell === "bash") script = [
807
+ "prun() {",
808
+ ` local bin=${bin}`,
809
+ " local -a cmd",
810
+ " read -r -a cmd <<< \"$bin\"",
811
+ " command \"${cmd[@]}\" \"$@\"",
812
+ " history -n",
813
+ "}"
814
+ ].join("\n");
815
+ else if (shell === "fish") script = [
816
+ "function prun",
817
+ ` set -l bin ${bin}`,
818
+ " set -l cmd (string split -- \" \" $bin)",
819
+ " command $cmd $argv",
820
+ " history --merge",
821
+ "end"
822
+ ].join("\n");
823
+ else {
824
+ console.log(color.red(isZh$2 ? `不支持的 shell: ${shell}` : `Unsupported shell: ${shell}`));
825
+ return;
826
+ }
827
+ console.log(script);
588
828
  }
589
829
 
590
830
  //#endregion
@@ -667,6 +907,10 @@ async function setup() {
667
907
  }
668
908
  const argv = process.argv.slice(2);
669
909
  await help(argv);
910
+ if ((exec === "prun" || exec === "prun.mjs") && argv[0] === "--init") {
911
+ printPrunInit(argv.slice(1));
912
+ return;
913
+ }
670
914
  let params = spaceFormat(argv.join(" ")).trim();
671
915
  if (!await hasPkg(rootPath)) {
672
916
  if (await isGo(rootPath)) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@simon_he/pi",
3
3
  "type": "module",
4
- "version": "0.1.24",
4
+ "version": "0.1.26",
5
5
  "packageManager": "pnpm@10.28.2",
6
6
  "description": "An intelligent cross-platform package manager and CLI tool that autodetects project environments (Node.mjs, Go, Rust, Python) with beautiful loading animations and smart command execution.",
7
7
  "author": {
@@ -90,7 +90,7 @@
90
90
  "test": "vitest"
91
91
  },
92
92
  "dependencies": {
93
- "ccommand": "^1.0.90",
93
+ "ccommand": "^1.1.0",
94
94
  "fast-glob": "^3.3.3",
95
95
  "lazy-js-utils": "^0.1.49",
96
96
  "ora": "^8.2.0",