@luxkit/cli 1.1.44 → 1.1.45

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.45
4
+
5
+ - Added global package manager override: `lux set lux_package_manager=pnpm` forces a specific package manager across all projects
6
+ - Supports `auto`, `bun`, `pnpm`, `yarn`, `npm` values — `auto` (default) uses lockfile detection as before
7
+ - Warns when global config conflicts with a project's existing lockfile
8
+
3
9
  ## 1.1.44
4
10
 
5
11
  - Merged English and Chinese README into a single file with in-page anchor switching
package/README.md CHANGED
@@ -95,22 +95,22 @@ lux fmt list
95
95
 
96
96
  ### 📖CLI Commands
97
97
 
98
- | Command | Description |
99
- | :-------------------------- | :---------------------------------------------------------------- |
100
- | `lux fmt <preset>` | Generate lint configs |
101
- | `lux fmt list` | List available lint presets |
102
- | `lux vscode <preset>` | Generate VSCode config (per-project) |
103
- | `lux vscode list` | List available VSCode presets |
104
- | `lux init` | Initialize Skill files to AI Agent |
105
- | `lux init --preset` | Initialize all built-in presets to `~/.lux/preset/` |
106
- | `lux set <key=value> [...]` | Persist proxy env vars (e.g. `https_proxy=http://127.0.0.1:7890`) |
107
- | `lux unset` | Clear all stored proxy configuration |
108
- | `lux show env` | Display stored proxy environment variables |
109
- | `lux vpn cmd` | Copy CMD proxy commands to clipboard |
110
- | `lux vpn pw` | Copy PowerShell proxy commands to clipboard |
111
- | `lux vpn bash` | Copy Bash proxy commands to clipboard |
112
- | `lux update` | Update `@luxkit/cli` to the latest version |
113
- | `lux update --check` | Check for available updates without installing |
98
+ | Command | Description |
99
+ | :-------------------------- | :--------------------------------------------------------------------------------- |
100
+ | `lux fmt <preset>` | Generate lint configs |
101
+ | `lux fmt list` | List available lint presets |
102
+ | `lux vscode <preset>` | Generate VSCode config (per-project) |
103
+ | `lux vscode list` | List available VSCode presets |
104
+ | `lux init` | Initialize Skill files to AI Agent |
105
+ | `lux init --preset` | Initialize all built-in presets to `~/.lux/preset/` |
106
+ | `lux set <key=value> [...]` | Set config values (proxy, global lux package management`lux_package_manager=pnpm`) |
107
+ | `lux unset` | Clear all stored configuration |
108
+ | `lux show env` | Display stored configuration |
109
+ | `lux vpn cmd` | Copy CMD proxy commands to clipboard |
110
+ | `lux vpn pw` | Copy PowerShell proxy commands to clipboard |
111
+ | `lux vpn bash` | Copy Bash proxy commands to clipboard |
112
+ | `lux update` | Update `@luxkit/cli` to the latest version |
113
+ | `lux update --check` | Check for available updates without installing |
114
114
 
115
115
  <br />
116
116
 
@@ -216,7 +216,7 @@ npm uninstall -g @luxkit/cli
216
216
  | :----------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------- |
217
217
  | `package.json` errors | Ensure a valid `package.json` exists in the project root |
218
218
  | Preset not found | Run `lux fmt list` to see all available presets — lux auto-suggests via fuzzy matching |
219
- | Wrong package manager detected | Ensure the lockfile exists (`bun.lock` / `package-lock.json` / `pnpm-lock.yaml`) |
219
+ | Wrong package manager detected | Ensure the lockfile exists (`bun.lock` / `package-lock.json` / `pnpm-lock.yaml`), or set globally: `lux set lux_package_manager=pnpm` |
220
220
  | Skip dependency install | Use `--no-install` to only write to `package.json`, install manually |
221
221
  | Preview before applying | Use `--dry-run` to see all operations without writing |
222
222
  | Flags have no effect | Custom presets must include the corresponding config files and deps for `--stylelint`/`--cspell`/`--editorconfig`/`--husky`/`--lint-staged` to work |
@@ -340,22 +340,22 @@ lux fmt list
340
340
 
341
341
  ## 📖命令参考
342
342
 
343
- | 命令 | 说明 |
344
- | :-------------------------- | :----------------------------------------------------------- |
345
- | `lux fmt <preset>` | 生成 Lint 配置 |
346
- | `lux fmt list` | 列出可用的 Lint 预设 |
347
- | `lux vscode <preset>` | 生成 VSCode 配置(项目内) |
348
- | `lux vscode list` | 列出可用的 VSCode 预设 |
349
- | `lux init` | 初始化 Skill 文件到 AI Agent |
350
- | `lux init --preset` | 初始化所有内置预设到 `~/.lux/preset/` |
351
- | `lux set <key=value> [...]` | 设置代理环境变量(如 `https_proxy="http://127.0.0.1:7890"`) |
352
- | `lux unset` | 清除所有代理配置 |
353
- | `lux show env` | 显示已配置的代理环境变量 |
354
- | `lux vpn cmd` | 复制 CMD 代理命令到剪贴板 |
355
- | `lux vpn pw` | 复制 PowerShell 代理命令到剪贴板 |
356
- | `lux vpn bash` | 复制 Bash 代理命令到剪贴板 |
357
- | `lux update` | 更新 `@luxkit/cli` 到最新版本 |
358
- | `lux update --check` | 检查可用更新,不执行安装 |
343
+ | 命令 | 说明 |
344
+ | :-------------------------- | :---------------------------------------------------------- |
345
+ | `lux fmt <preset>` | 生成 Lint 配置 |
346
+ | `lux fmt list` | 列出可用的 Lint 预设 |
347
+ | `lux vscode <preset>` | 生成 VSCode 配置(项目内) |
348
+ | `lux vscode list` | 列出可用的 VSCode 预设 |
349
+ | `lux init` | 初始化 Skill 文件到 AI Agent |
350
+ | `lux init --preset` | 初始化所有内置预设到 `~/.lux/preset/` |
351
+ | `lux set <key=value> [...]` | 设置配置值(代理、全局lux包管理`lux_package_manager=pnpm`) |
352
+ | `lux unset` | 清除所有已存储的配置 |
353
+ | `lux show env` | 显示已存储的配置 |
354
+ | `lux vpn cmd` | 复制 CMD 代理命令到剪贴板 |
355
+ | `lux vpn pw` | 复制 PowerShell 代理命令到剪贴板 |
356
+ | `lux vpn bash` | 复制 Bash 代理命令到剪贴板 |
357
+ | `lux update` | 更新 `@luxkit/cli` 到最新版本 |
358
+ | `lux update --check` | 检查可用更新,不执行安装 |
359
359
 
360
360
  <br />
361
361
 
@@ -457,14 +457,14 @@ npm uninstall -g @luxkit/cli
457
457
 
458
458
  ## 🔍故障排查
459
459
 
460
- | 问题 | 解决方案 |
461
- | :---------------------- | :----------------------------------------------------------------------------------------------------------------- |
462
- | `package.json` 相关错误 | 确保项目根目录存在合法的 `package.json` |
463
- | 预设未找到 | 运行 `lux fmt list` 查看所有可用预设,lux 会自动模糊匹配建议 |
464
- | 包管理器检测不正确 | 确保 lockfile 存在(`bun.lock` / `package-lock.json` / `pnpm-lock.yaml`) |
465
- | 跳过依赖安装 | 使用 `--no-install` 仅写入 `package.json`,手动安装 |
466
- | 预览操作结果 | 使用 `--dry-run` 查看将执行的所有操作 |
467
- | flag 无效果 | 自定义预设需包含对应的配置文件和依赖,`--stylelint`/`--cspell`/`--editorconfig`/`--husky`/`--lint-staged` 才能生效 |
460
+ | 问题 | 解决方案 |
461
+ | :---------------------- | :------------------------------------------------------------------------------------------------------------------------ |
462
+ | `package.json` 相关错误 | 确保项目根目录存在合法的 `package.json` |
463
+ | 预设未找到 | 运行 `lux fmt list` 查看所有可用预设,lux 会自动模糊匹配建议 |
464
+ | 包管理器检测不正确 | 确保 lockfile 存在(`bun.lock` / `package-lock.json` / `pnpm-lock.yaml`),或全局指定:`lux set lux_package_manager=pnpm` |
465
+ | 跳过依赖安装 | 使用 `--no-install` 仅写入 `package.json`,手动安装 |
466
+ | 预览操作结果 | 使用 `--dry-run` 查看将执行的所有操作 |
467
+ | flag 无效果 | 自定义预设需包含对应的配置文件和依赖,`--stylelint`/`--cspell`/`--editorconfig`/`--husky`/`--lint-staged` 才能生效 |
468
468
 
469
469
  <br />
470
470
 
package/dist/index.js CHANGED
@@ -4,8 +4,8 @@
4
4
  import { program } from "commander";
5
5
 
6
6
  // src/commands/fmt.ts
7
- import fs3 from "fs";
8
- import path5 from "path";
7
+ import fs4 from "fs";
8
+ import path6 from "path";
9
9
  import chalk2 from "chalk";
10
10
 
11
11
  // src/presets/fmt/electron-vue.ts
@@ -875,7 +875,7 @@ function generateAllFmt(preset, opts) {
875
875
  }
876
876
 
877
877
  // src/utils/deps.ts
878
- import path3 from "path";
878
+ import path4 from "path";
879
879
  import { spawn } from "child_process";
880
880
 
881
881
  // src/utils/execFileNoThrow.ts
@@ -899,13 +899,76 @@ async function execFileNoThrow(command, args, options) {
899
899
  }
900
900
  }
901
901
 
902
+ // src/utils/config.ts
903
+ import fs2 from "fs";
904
+ import os from "os";
905
+ import path3 from "path";
906
+ var CONFIG_DIR = ".lux";
907
+ var ENV_FILE = "env.txt";
908
+ function getEnvConfigPath() {
909
+ return path3.join(os.homedir(), CONFIG_DIR, ENV_FILE);
910
+ }
911
+ function getEnvConfig() {
912
+ let content;
913
+ try {
914
+ content = fs2.readFileSync(getEnvConfigPath(), "utf-8");
915
+ } catch {
916
+ return {};
917
+ }
918
+ const result = {};
919
+ for (const line of content.split("\n")) {
920
+ const trimmed = line.trim();
921
+ if (!trimmed || trimmed.startsWith("#")) continue;
922
+ const eqIndex = trimmed.indexOf("=");
923
+ if (eqIndex === -1) continue;
924
+ const key = trimmed.slice(0, eqIndex).trim();
925
+ const raw = trimmed.slice(eqIndex + 1).trim();
926
+ const value = raw.replace(/^["']|["']$/g, "");
927
+ if (key) result[key] = value;
928
+ }
929
+ return result;
930
+ }
931
+ function setEnvConfig(data) {
932
+ const lines = Object.entries(data).filter(([, v]) => v !== "").map(([k, v]) => `${k}="${v}"`);
933
+ writeFile(getEnvConfigPath(), lines.join("\n") + "\n");
934
+ }
935
+ function clearEnvConfig() {
936
+ try {
937
+ fs2.unlinkSync(getEnvConfigPath());
938
+ } catch {
939
+ }
940
+ }
941
+
902
942
  // src/utils/deps.ts
903
- function detectPackageManager(cwd) {
943
+ var PM_LOCKFILE_MAP = {
944
+ bun: ["bun.lockb", "bun.lock"],
945
+ pnpm: ["pnpm-lock.yaml"],
946
+ yarn: ["yarn.lock"],
947
+ npm: ["package-lock.json"]
948
+ };
949
+ function detectFromLockfile(cwd) {
904
950
  if (fileExists(`${cwd}/bun.lockb`) || fileExists(`${cwd}/bun.lock`)) return "bun";
905
951
  if (fileExists(`${cwd}/pnpm-lock.yaml`)) return "pnpm";
906
952
  if (fileExists(`${cwd}/yarn.lock`)) return "yarn";
907
953
  return "npm";
908
954
  }
955
+ function detectPackageManager(cwd) {
956
+ const env = getEnvConfig();
957
+ const configured = env.lux_package_manager;
958
+ if (configured && configured !== "auto") {
959
+ const pm = configured;
960
+ const lockfiles = PM_LOCKFILE_MAP[pm] ?? [];
961
+ const hasMatch = lockfiles.some((f) => fileExists(`${cwd}/${f}`));
962
+ if (!hasMatch) {
963
+ const detected = detectFromLockfile(cwd);
964
+ if (detected !== "npm") {
965
+ logger.warn(`Global config is ${pm} but detected ${detected} lockfile`);
966
+ }
967
+ }
968
+ return pm;
969
+ }
970
+ return detectFromLockfile(cwd);
971
+ }
909
972
  function getLockfileName(pm) {
910
973
  switch (pm) {
911
974
  case "bun":
@@ -939,7 +1002,7 @@ async function fetchPackageVersion(pkg) {
939
1002
  return lines[lines.length - 1].trim();
940
1003
  }
941
1004
  async function addDepsToManifest(packages, cwd) {
942
- const pkgPath = path3.join(cwd, "package.json");
1005
+ const pkgPath = path4.join(cwd, "package.json");
943
1006
  const pkg = readJson(pkgPath);
944
1007
  if (!pkg) {
945
1008
  throw new Error("package.json not found");
@@ -963,7 +1026,7 @@ async function addDepsToManifest(packages, cwd) {
963
1026
  }
964
1027
  async function installDevDeps(packages, cwd, pm) {
965
1028
  const manager = pm ?? detectPackageManager(cwd);
966
- const pkg = readJson(path3.join(cwd, "package.json"));
1029
+ const pkg = readJson(path4.join(cwd, "package.json"));
967
1030
  if (!pkg) {
968
1031
  throw new Error("package.json not found");
969
1032
  }
@@ -999,9 +1062,9 @@ async function installDevDeps(packages, cwd, pm) {
999
1062
  }
1000
1063
 
1001
1064
  // src/core/local-preset.ts
1002
- import fs2 from "fs";
1003
- import os from "os";
1004
- import path4 from "path";
1065
+ import fs3 from "fs";
1066
+ import os2 from "os";
1067
+ import path5 from "path";
1005
1068
 
1006
1069
  // src/core/merge-settings.ts
1007
1070
  var USER_PRIORITY_KEYS = /* @__PURE__ */ new Set([
@@ -1082,27 +1145,27 @@ var STYLELINT_EXTENSION = "stylelint.vscode-stylelint";
1082
1145
  var HUSKY_DEPS = /* @__PURE__ */ new Set(["husky"]);
1083
1146
  var LINTSTAGED_DEPS = /* @__PURE__ */ new Set(["lint-staged"]);
1084
1147
  function getLuxDir() {
1085
- return process.env.LUX_HOME || path4.join(os.homedir(), ".lux");
1148
+ return process.env.LUX_HOME || path5.join(os2.homedir(), ".lux");
1086
1149
  }
1087
1150
  function getLocalPresetDir(type, presetName) {
1088
1151
  if (!isValidPresetName(presetName)) {
1089
1152
  throw new Error(`Invalid preset name: "${presetName}"`);
1090
1153
  }
1091
- return path4.join(getLuxDir(), "preset", type, presetName);
1154
+ return path5.join(getLuxDir(), "preset", type, presetName);
1092
1155
  }
1093
1156
  function isValidPresetName(name) {
1094
1157
  return name.length > 0 && !name.includes("/") && !name.includes("\\") && !name.includes("..");
1095
1158
  }
1096
1159
  function listCustomPresets() {
1097
- const fmtDir = path4.join(getLuxDir(), "preset", "fmt");
1098
- if (!fs2.existsSync(fmtDir)) return [];
1099
- const entries = fs2.readdirSync(fmtDir, { withFileTypes: true });
1160
+ const fmtDir = path5.join(getLuxDir(), "preset", "fmt");
1161
+ if (!fs3.existsSync(fmtDir)) return [];
1162
+ const entries = fs3.readdirSync(fmtDir, { withFileTypes: true });
1100
1163
  const result = [];
1101
1164
  for (const entry of entries) {
1102
1165
  if (!entry.isDirectory()) continue;
1103
1166
  if (!isValidPresetName(entry.name)) continue;
1104
- const pkgPath = path4.join(fmtDir, entry.name, "package.json");
1105
- if (fs2.existsSync(pkgPath)) {
1167
+ const pkgPath = path5.join(fmtDir, entry.name, "package.json");
1168
+ if (fs3.existsSync(pkgPath)) {
1106
1169
  result.push(entry.name);
1107
1170
  }
1108
1171
  }
@@ -1110,19 +1173,19 @@ function listCustomPresets() {
1110
1173
  }
1111
1174
  function isValidCustomPreset(name) {
1112
1175
  if (!isValidPresetName(name)) return false;
1113
- const presetDir = path4.join(getLuxDir(), "preset", "fmt", name);
1114
- if (!fs2.existsSync(presetDir)) return false;
1115
- const pkgPath = path4.join(presetDir, "package.json");
1116
- return fs2.existsSync(pkgPath);
1176
+ const presetDir = path5.join(getLuxDir(), "preset", "fmt", name);
1177
+ if (!fs3.existsSync(presetDir)) return false;
1178
+ const pkgPath = path5.join(presetDir, "package.json");
1179
+ return fs3.existsSync(pkgPath);
1117
1180
  }
1118
1181
  function localPresetExists(type, presetName) {
1119
1182
  const dir = getLocalPresetDir(type, presetName);
1120
- return fs2.existsSync(dir);
1183
+ return fs3.existsSync(dir);
1121
1184
  }
1122
1185
  function resetLocalPreset(type, presetName) {
1123
1186
  const dir = getLocalPresetDir(type, presetName);
1124
- if (fs2.existsSync(dir)) {
1125
- fs2.rmSync(dir, { recursive: true, force: true });
1187
+ if (fs3.existsSync(dir)) {
1188
+ fs3.rmSync(dir, { recursive: true, force: true });
1126
1189
  logger.log(`Reset local preset: ${dir}`);
1127
1190
  }
1128
1191
  }
@@ -1137,24 +1200,24 @@ function materializeFmtPreset(presetName, preset, opts) {
1137
1200
  const content = getContent(preset);
1138
1201
  if (content === void 0) continue;
1139
1202
  const resolved = opts.lockfile ? content.replace(/<lockfile>/g, opts.lockfile) : content.replace(/<lockfile>\n?/g, "");
1140
- writeFile(path4.join(presetDir, filename), resolved);
1203
+ writeFile(path5.join(presetDir, filename), resolved);
1141
1204
  }
1142
1205
  const templatePkg = buildTemplatePackageJson(preset);
1143
- writeJson(path4.join(presetDir, "package.json"), templatePkg);
1206
+ writeJson(path5.join(presetDir, "package.json"), templatePkg);
1144
1207
  logger.log(`Local preset created at ${presetDir}`);
1145
1208
  }
1146
1209
  function materializeVscodePreset(cwd, presetName) {
1147
1210
  const presetDir = getLocalPresetDir("vscode", presetName);
1148
1211
  ensureDir(presetDir);
1149
- const settingsSrc = path4.join(cwd, ".vscode", "settings.json");
1212
+ const settingsSrc = path5.join(cwd, ".vscode", "settings.json");
1150
1213
  if (fileExists(settingsSrc)) {
1151
- const content = fs2.readFileSync(settingsSrc, "utf-8");
1152
- writeFile(path4.join(presetDir, "settings.json"), content);
1214
+ const content = fs3.readFileSync(settingsSrc, "utf-8");
1215
+ writeFile(path5.join(presetDir, "settings.json"), content);
1153
1216
  }
1154
- const extensionsSrc = path4.join(cwd, ".vscode", "extensions.json");
1217
+ const extensionsSrc = path5.join(cwd, ".vscode", "extensions.json");
1155
1218
  if (fileExists(extensionsSrc)) {
1156
- const content = fs2.readFileSync(extensionsSrc, "utf-8");
1157
- writeFile(path4.join(presetDir, "extensions.json"), content);
1219
+ const content = fs3.readFileSync(extensionsSrc, "utf-8");
1220
+ writeFile(path5.join(presetDir, "extensions.json"), content);
1158
1221
  }
1159
1222
  logger.log(`Local preset created at ${presetDir}`);
1160
1223
  }
@@ -1162,9 +1225,9 @@ function materializeVscodePresetFromBuiltin(presetName, preset) {
1162
1225
  const presetDir = getLocalPresetDir("vscode", presetName);
1163
1226
  ensureDir(presetDir);
1164
1227
  const settings = preset.settings();
1165
- writeJson(path4.join(presetDir, "settings.json"), settings);
1228
+ writeJson(path5.join(presetDir, "settings.json"), settings);
1166
1229
  const extensions = preset.extensions();
1167
- writeJson(path4.join(presetDir, "extensions.json"), { recommendations: extensions });
1230
+ writeJson(path5.join(presetDir, "extensions.json"), { recommendations: extensions });
1168
1231
  logger.log(`Local preset created at ${presetDir}`);
1169
1232
  }
1170
1233
  var InvalidPackageJsonError = class extends Error {
@@ -1183,25 +1246,25 @@ function applyLocalFmtPreset(cwd, presetName, opts) {
1183
1246
  scriptsSkipped: 0
1184
1247
  };
1185
1248
  const presetDir = getLocalPresetDir("fmt", presetName);
1186
- if (!fs2.existsSync(presetDir)) {
1249
+ if (!fs3.existsSync(presetDir)) {
1187
1250
  logger.warn(`Local preset not found at ${presetDir}`);
1188
1251
  return result;
1189
1252
  }
1190
- const projectPkgPath = path4.join(cwd, "package.json");
1253
+ const projectPkgPath = path5.join(cwd, "package.json");
1191
1254
  if (fileExists(projectPkgPath)) {
1192
1255
  try {
1193
- JSON.parse(fs2.readFileSync(projectPkgPath, "utf-8"));
1256
+ JSON.parse(fs3.readFileSync(projectPkgPath, "utf-8"));
1194
1257
  } catch {
1195
1258
  throw new InvalidPackageJsonError(projectPkgPath);
1196
1259
  }
1197
1260
  }
1198
- const entries = fs2.readdirSync(presetDir).filter((name) => name !== "package.json" && fs2.statSync(path4.join(presetDir, name)).isFile());
1261
+ const entries = fs3.readdirSync(presetDir).filter((name) => name !== "package.json" && fs3.statSync(path5.join(presetDir, name)).isFile());
1199
1262
  for (const filename of entries) {
1200
1263
  if (opts.noStylelint && STYLELINT_FILES.has(filename)) continue;
1201
1264
  if (opts.noEditorconfig && filename === EDITORCONFIG_FILE) continue;
1202
1265
  if (opts.noCspell && filename === CSPELL_FILE) continue;
1203
1266
  if (opts.noLintStaged && filename === LINTSTAGED_FILE) continue;
1204
- const destPath = path4.join(cwd, filename);
1267
+ const destPath = path5.join(cwd, filename);
1205
1268
  const exists = fileExists(destPath);
1206
1269
  if (exists && !opts.force) {
1207
1270
  result.skipped.push(filename);
@@ -1215,14 +1278,14 @@ function applyLocalFmtPreset(cwd, presetName, opts) {
1215
1278
  logger.log(`[dry-run] Would copy ${filename} from local preset`);
1216
1279
  continue;
1217
1280
  }
1218
- const content = fs2.readFileSync(path4.join(presetDir, filename), "utf-8");
1281
+ const content = fs3.readFileSync(path5.join(presetDir, filename), "utf-8");
1219
1282
  writeFile(destPath, content);
1220
1283
  (exists ? result.overwritten : result.created).push(filename);
1221
1284
  }
1222
- const templatePkg = readJson(path4.join(presetDir, "package.json"));
1285
+ const templatePkg = readJson(path5.join(presetDir, "package.json"));
1223
1286
  const projectPkg = readJson(projectPkgPath);
1224
1287
  if (templatePkg && projectPkg) {
1225
- const pm = fileExists(path4.join(cwd, "package.json")) ? detectPackageManager(cwd) : void 0;
1288
+ const pm = fileExists(path5.join(cwd, "package.json")) ? detectPackageManager(cwd) : void 0;
1226
1289
  const merged = mergeTemplateIntoProject(templatePkg, projectPkg, pm, opts, result);
1227
1290
  if (!opts.dryRun) {
1228
1291
  writeJson(projectPkgPath, merged);
@@ -1239,16 +1302,16 @@ function applyLocalVscodePreset(cwd, presetName, opts) {
1239
1302
  scriptsSkipped: 0
1240
1303
  };
1241
1304
  const presetDir = getLocalPresetDir("vscode", presetName);
1242
- if (!fs2.existsSync(presetDir)) {
1305
+ if (!fs3.existsSync(presetDir)) {
1243
1306
  logger.warn(`Local preset not found at ${presetDir}`);
1244
1307
  return result;
1245
1308
  }
1246
- const settingsSrc = path4.join(presetDir, "settings.json");
1309
+ const settingsSrc = path5.join(presetDir, "settings.json");
1247
1310
  if (fileExists(settingsSrc)) {
1248
1311
  const presetSettings = readJson(settingsSrc);
1249
1312
  const filteredSettings = opts.noStylelint ? filterStylelintSettings(presetSettings ?? {}) : presetSettings;
1250
1313
  if (filteredSettings) {
1251
- const settingsDest = path4.join(cwd, ".vscode", "settings.json");
1314
+ const settingsDest = path5.join(cwd, ".vscode", "settings.json");
1252
1315
  const existingSettings = readJson(settingsDest);
1253
1316
  if (existingSettings) {
1254
1317
  if (opts.dryRun) {
@@ -1270,7 +1333,7 @@ function applyLocalVscodePreset(cwd, presetName, opts) {
1270
1333
  }
1271
1334
  }
1272
1335
  }
1273
- const extensionsSrc = path4.join(presetDir, "extensions.json");
1336
+ const extensionsSrc = path5.join(presetDir, "extensions.json");
1274
1337
  if (fileExists(extensionsSrc)) {
1275
1338
  const extensionsData = readJson(extensionsSrc);
1276
1339
  if (extensionsData) {
@@ -1284,7 +1347,7 @@ function applyLocalVscodePreset(cwd, presetName, opts) {
1284
1347
  result.created.push(".vscode/extensions.json");
1285
1348
  logger.log("[dry-run] Would create .vscode/extensions.json from local preset");
1286
1349
  } else {
1287
- const extensionsDest = path4.join(cwd, ".vscode", "extensions.json");
1350
+ const extensionsDest = path5.join(cwd, ".vscode", "extensions.json");
1288
1351
  const existingExtensions = readJson(extensionsDest);
1289
1352
  const existingRecommendations = existingExtensions?.recommendations ?? [];
1290
1353
  const merged = [.../* @__PURE__ */ new Set([...existingRecommendations, ...presetRecommendations])];
@@ -1396,11 +1459,11 @@ function filterScripts(scripts, noStylelint, noEditorconfig, noCspell, noLintSta
1396
1459
  return filtered;
1397
1460
  }
1398
1461
  function detectPresetCapabilities(presetName) {
1399
- const presetDir = path4.join(getLuxDir(), "preset", "fmt", presetName);
1400
- const entries = fs2.readdirSync(presetDir);
1462
+ const presetDir = path5.join(getLuxDir(), "preset", "fmt", presetName);
1463
+ const entries = fs3.readdirSync(presetDir);
1401
1464
  const hasStylelintFile = entries.some((f) => STYLELINT_FILES.has(f));
1402
1465
  const pkg = readJson(
1403
- path4.join(presetDir, "package.json")
1466
+ path5.join(presetDir, "package.json")
1404
1467
  );
1405
1468
  const hasStylelintDep = pkg?.devDependencies ? Object.keys(pkg.devDependencies).some((d) => isNotStylelintDep(d) === false) : false;
1406
1469
  const hasEditorconfigFile = entries.includes(EDITORCONFIG_FILE);
@@ -1468,10 +1531,10 @@ function registerFmtCommand(program2) {
1468
1531
  return;
1469
1532
  }
1470
1533
  const cwd = process.cwd();
1471
- const pkgPath = path5.join(cwd, "package.json");
1534
+ const pkgPath = path6.join(cwd, "package.json");
1472
1535
  if (fileExists(pkgPath)) {
1473
1536
  try {
1474
- JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
1537
+ JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
1475
1538
  } catch {
1476
1539
  logger.error(
1477
1540
  "package.json exists but is not valid JSON. Fix it first, then re-run this command."
@@ -1568,9 +1631,9 @@ async function executeLocalPath(cwd, presetName, options) {
1568
1631
  `Added ${result.scriptsAdded} script${result.scriptsAdded > 1 ? "s" : ""} to package.json${result.scriptsSkipped > 0 ? ` (${result.scriptsSkipped} skipped)` : ""}`
1569
1632
  );
1570
1633
  }
1571
- const pm = fileExists(path5.join(cwd, "package.json")) ? detectPackageManager(cwd) : void 0;
1634
+ const pm = fileExists(path6.join(cwd, "package.json")) ? detectPackageManager(cwd) : void 0;
1572
1635
  if (!pm) return;
1573
- const templatePkgPath = path5.join(getLocalPresetDir("fmt", presetName), "package.json");
1636
+ const templatePkgPath = path6.join(getLocalPresetDir("fmt", presetName), "package.json");
1574
1637
  const templatePkg = readJson(templatePkgPath);
1575
1638
  if (!templatePkg?.devDependencies) return;
1576
1639
  const depsToInstall = filterDeps(
@@ -1581,7 +1644,7 @@ async function executeLocalPath(cwd, presetName, options) {
1581
1644
  opts.noHusky,
1582
1645
  opts.noLintStaged
1583
1646
  );
1584
- const projectPkgPath = path5.join(cwd, "package.json");
1647
+ const projectPkgPath = path6.join(cwd, "package.json");
1585
1648
  const projectPkg = readJson(projectPkgPath);
1586
1649
  if (!projectPkg) return;
1587
1650
  const existingDeps = projectPkg.devDependencies ?? {};
@@ -1633,7 +1696,7 @@ async function executeLocalPath(cwd, presetName, options) {
1633
1696
  }
1634
1697
  }
1635
1698
  async function executeBuiltinPath(cwd, presetName, preset, options) {
1636
- const pm = fileExists(path5.join(cwd, "package.json")) ? detectPackageManager(cwd) : void 0;
1699
+ const pm = fileExists(path6.join(cwd, "package.json")) ? detectPackageManager(cwd) : void 0;
1637
1700
  const noHusky = options.husky !== true && options.lintStaged !== true;
1638
1701
  const noLintStaged = options.lintStaged !== true;
1639
1702
  const opts = {
@@ -1788,7 +1851,7 @@ function summarizeFiles(filenames) {
1788
1851
  return [...categories].join(", ");
1789
1852
  }
1790
1853
  async function injectScripts(scripts, opts, pm) {
1791
- const pkgPath = path5.join(opts.cwd, "package.json");
1854
+ const pkgPath = path6.join(opts.cwd, "package.json");
1792
1855
  const pkg = readJson(pkgPath);
1793
1856
  if (!pkg) {
1794
1857
  logger.warn("package.json not found, skipping script injection");
@@ -1823,7 +1886,7 @@ async function injectScripts(scripts, opts, pm) {
1823
1886
  }
1824
1887
  }
1825
1888
  async function initHusky(cwd, pm, opts) {
1826
- const pkgPath = path5.join(cwd, "package.json");
1889
+ const pkgPath = path6.join(cwd, "package.json");
1827
1890
  const pkg = readJson(pkgPath);
1828
1891
  if (!pkg) {
1829
1892
  logger.warn("package.json not found, skipping husky setup");
@@ -1833,8 +1896,8 @@ async function initHusky(cwd, pm, opts) {
1833
1896
  const isYarn = pm === "yarn";
1834
1897
  const initScriptName = isYarn ? "postinstall" : "prepare";
1835
1898
  const hookCommand = opts.noLintStaged ? `${prefix} lint` : `${prefix} lint-staged`;
1836
- const huskyDir = path5.join(cwd, ".husky");
1837
- const preCommitPath = path5.join(huskyDir, "pre-commit");
1899
+ const huskyDir = path6.join(cwd, ".husky");
1900
+ const preCommitPath = path6.join(huskyDir, "pre-commit");
1838
1901
  if (opts.dryRun) {
1839
1902
  logger.log(`[dry-run] Would create .husky/pre-commit with: ${hookCommand}`);
1840
1903
  logger.log(`[dry-run] Would inject "${initScriptName}": "husky" script`);
@@ -1847,7 +1910,7 @@ async function initHusky(cwd, pm, opts) {
1847
1910
  ensureDir(huskyDir);
1848
1911
  writeFile(preCommitPath, `${hookCommand}
1849
1912
  `);
1850
- fs3.chmodSync(preCommitPath, 493);
1913
+ fs4.chmodSync(preCommitPath, 493);
1851
1914
  }
1852
1915
  const scripts = pkg.scripts ?? {};
1853
1916
  if (scripts[initScriptName] !== void 0 && !opts.force) {
@@ -2651,18 +2714,18 @@ var VSCODE_PRESETS = [
2651
2714
  ];
2652
2715
 
2653
2716
  // src/generators/init.ts
2654
- import fs4 from "fs";
2655
- import path6 from "path";
2717
+ import fs5 from "fs";
2718
+ import path7 from "path";
2656
2719
  function resolveSkillsDir() {
2657
- const entryDir = path6.dirname(process.argv[1] ?? "");
2658
- return path6.resolve(entryDir, "skills");
2720
+ const entryDir = path7.dirname(process.argv[1] ?? "");
2721
+ return path7.resolve(entryDir, "skills");
2659
2722
  }
2660
2723
  function listFilesRecursive(dir, base) {
2661
- const entries = fs4.readdirSync(dir, { withFileTypes: true });
2724
+ const entries = fs5.readdirSync(dir, { withFileTypes: true });
2662
2725
  const files = [];
2663
2726
  for (const entry of entries) {
2664
2727
  const childBase = `${base}/${entry.name}`;
2665
- const fullPath = path6.join(dir, entry.name);
2728
+ const fullPath = path7.join(dir, entry.name);
2666
2729
  if (entry.isDirectory()) {
2667
2730
  files.push(...listFilesRecursive(fullPath, childBase));
2668
2731
  } else {
@@ -2673,20 +2736,20 @@ function listFilesRecursive(dir, base) {
2673
2736
  }
2674
2737
  function generateInitSkills(targetBaseDir, cwd) {
2675
2738
  const skillsDir = resolveSkillsDir();
2676
- if (!fs4.existsSync(skillsDir)) {
2739
+ if (!fs5.existsSync(skillsDir)) {
2677
2740
  logger.error(`Bundled skills directory not found: ${skillsDir}`);
2678
2741
  logger.error('Please run "lux build" or reinstall lux.');
2679
2742
  return { copiedFiles: [], targetDir: targetBaseDir };
2680
2743
  }
2681
- const targetPath = path6.resolve(cwd, targetBaseDir);
2744
+ const targetPath = path7.resolve(cwd, targetBaseDir);
2682
2745
  try {
2683
- fs4.cpSync(skillsDir, targetPath, { recursive: true, force: true });
2746
+ fs5.cpSync(skillsDir, targetPath, { recursive: true, force: true });
2684
2747
  } catch (error) {
2685
2748
  const message = error instanceof Error ? error.message : String(error);
2686
2749
  logger.error(`Failed to copy skills to ${targetPath}: ${message}`);
2687
2750
  return { copiedFiles: [], targetDir: targetBaseDir };
2688
2751
  }
2689
- const copiedFiles = fs4.existsSync(targetPath) ? listFilesRecursive(targetPath, targetBaseDir) : [];
2752
+ const copiedFiles = fs5.existsSync(targetPath) ? listFilesRecursive(targetPath, targetBaseDir) : [];
2690
2753
  return { copiedFiles, targetDir: targetBaseDir };
2691
2754
  }
2692
2755
 
@@ -2746,46 +2809,6 @@ function materializeAllPresets() {
2746
2809
  logger.success("All presets materialized to ~/.lux/preset/");
2747
2810
  }
2748
2811
 
2749
- // src/utils/config.ts
2750
- import fs5 from "fs";
2751
- import os2 from "os";
2752
- import path7 from "path";
2753
- var CONFIG_DIR = ".lux";
2754
- var ENV_FILE = "env.txt";
2755
- function getEnvConfigPath() {
2756
- return path7.join(os2.homedir(), CONFIG_DIR, ENV_FILE);
2757
- }
2758
- function getEnvConfig() {
2759
- let content;
2760
- try {
2761
- content = fs5.readFileSync(getEnvConfigPath(), "utf-8");
2762
- } catch {
2763
- return {};
2764
- }
2765
- const result = {};
2766
- for (const line of content.split("\n")) {
2767
- const trimmed = line.trim();
2768
- if (!trimmed || trimmed.startsWith("#")) continue;
2769
- const eqIndex = trimmed.indexOf("=");
2770
- if (eqIndex === -1) continue;
2771
- const key = trimmed.slice(0, eqIndex).trim();
2772
- const raw = trimmed.slice(eqIndex + 1).trim();
2773
- const value = raw.replace(/^["']|["']$/g, "");
2774
- if (key) result[key] = value;
2775
- }
2776
- return result;
2777
- }
2778
- function setEnvConfig(data) {
2779
- const lines = Object.entries(data).filter(([, v]) => v !== "").map(([k, v]) => `${k}="${v}"`);
2780
- writeFile(getEnvConfigPath(), lines.join("\n") + "\n");
2781
- }
2782
- function clearEnvConfig() {
2783
- try {
2784
- fs5.unlinkSync(getEnvConfigPath());
2785
- } catch {
2786
- }
2787
- }
2788
-
2789
2812
  // src/commands/show.ts
2790
2813
  function handleShowEnv() {
2791
2814
  const config = getEnvConfig();
@@ -3041,7 +3064,8 @@ function executeVscodeBuiltinPath(cwd, presetName, preset, options) {
3041
3064
 
3042
3065
  // src/commands/vpn.ts
3043
3066
  import { spawnSync } from "child_process";
3044
- var ALLOWED_KEYS = ["https_proxy", "http_proxy", "all_proxy"];
3067
+ var ALLOWED_KEYS = ["https_proxy", "http_proxy", "all_proxy", "lux_package_manager"];
3068
+ var VALID_PM_VALUES = ["auto", "bun", "pnpm", "yarn", "npm"];
3045
3069
  function buildCommands(shell, env) {
3046
3070
  const entries = Object.entries(env);
3047
3071
  if (shell === "cmd") {
@@ -3099,6 +3123,12 @@ function handleSet(args) {
3099
3123
  logger.error(`Invalid key: "${key}". Allowed keys: ${ALLOWED_KEYS.join(", ")}`);
3100
3124
  return;
3101
3125
  }
3126
+ if (key === "lux_package_manager" && !VALID_PM_VALUES.includes(value)) {
3127
+ logger.error(
3128
+ `Invalid package manager: "${value}". Allowed values: ${VALID_PM_VALUES.join(", ")}`
3129
+ );
3130
+ return;
3131
+ }
3102
3132
  merged[key] = value;
3103
3133
  }
3104
3134
  setEnvConfig(merged);
@@ -3123,6 +3153,9 @@ registerVscodeCommand(program);
3123
3153
  registerVpnCommand(program);
3124
3154
  registerShowCommand(program);
3125
3155
  registerUpdateCommand(program);
3126
- program.command("set").description("Set proxy env vars using key=value pairs").argument("[args...]", "key=value pairs (e.g. https_proxy=http://127.0.0.1:7890)").action((args) => handleSet(args));
3127
- program.command("unset").description("Clear stored proxy configuration").action(() => handleUnset());
3156
+ program.command("set").description("Set config values using key=value pairs (proxy, lux_package_manager)").argument(
3157
+ "[args...]",
3158
+ "key=value pairs (e.g. https_proxy=http://127.0.0.1:7890, lux_package_manager=pnpm)"
3159
+ ).action((args) => handleSet(args));
3160
+ program.command("unset").description("Clear stored configuration").action(() => handleUnset());
3128
3161
  program.parse();
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: lux
3
- description: Use when setting up ESLint, Prettier, CSpell, Stylelint, EditorConfig, VSCode workspace settings, proxy env
3
+ description: Use it only when users want to configure lint, ESLint, Prettier,Husky etc, and VSCode with lux.
4
4
  ---
5
5
 
6
6
  ## fmt — generate lint/format configs
@@ -73,10 +73,20 @@ lux vpn bash # copy Bash proxy commands
73
73
 
74
74
  ```bash
75
75
  lux set https_proxy=http://127.0.0.1:7890
76
- lux unset # clear all proxy config
77
- lux show env # show stored proxy env
76
+ lux unset # clear all config
77
+ lux show env # show stored config
78
78
  ```
79
79
 
80
+ ## package manager — global override
81
+
82
+ ```bash
83
+ lux set lux_package_manager=pnpm # force pnpm globally (auto, bun, pnpm, yarn, npm)
84
+ lux set lux_package_manager=auto # revert to lockfile detection
85
+ lux unset # clear all config (reverts to auto)
86
+ ```
87
+
88
+ By default lux detects the package manager from lockfile (`bun.lock`, `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`). Setting `lux_package_manager` overrides this globally — lux will use the configured PM for all projects, with a warning if the project's lockfile doesn't match.
89
+
80
90
  ## update — self-update
81
91
 
82
92
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luxkit/cli",
3
- "version": "1.1.44",
3
+ "version": "1.1.45",
4
4
  "description": "One-click project formatting & VSCode config CLI",
5
5
  "type": "module",
6
6
  "bin": {