@luxkit/cli 1.1.1 → 1.1.2

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
@@ -18,48 +18,29 @@
18
18
 
19
19
  ### What is lux?
20
20
 
21
- `lux` is a CLI tool that initializes project formatting configs and VSCode workspace settings with a single command. It generates ESLint, Prettier, CSpell, EditorConfig files and VSCode settings from battle-tested presets — with smart merge and conflict resolution.
21
+ `lux` is a CLI tool that sets up project lint configs and VSCode workspace settings with a single command — saving you from repetitive configuration overhead and wasted tokens. It generates ESLint, Prettier, CSpell, Stylelint,EditorConfig configs and VSCode settings from presets — with smart merge and conflict resolution. Configurations can be customized to your needs.
22
22
 
23
23
  <div align="center">
24
24
  <img src="https://github.com/TTT1231/lux/blob/main/demo.gif?raw=true" alt="lux demo" width="640" />
25
25
  </div>
26
26
 
27
- ### ✨ Key Highlights
28
-
29
- | Feature | Description |
30
- | :------------------------- | :--------------------------------------------------------------------------------------------------- |
31
- | 🎯 **One Command Setup** | `lux fmt web-vue` generates all linting & formatting configs instantly |
32
- | 🔧 **6 Fmt Presets** | `web-vue` · `web-react` · `electron-vue` · `uniapp` · `node` · `nest` — each with curated rules |
33
- | 🖥️ **7 VSCode Presets** | `web-vue` · `web-react` · `electron-vue` · `uniapp` · `node` · `nest` · `go` — settings + extensions |
34
- | 🔀 **Smart Merge** | Preset wins for linting keys; user wins for personal preferences |
35
- | 🛡️ **Conflict Resolution** | `neverOverwrite` / `forceOverwrite` lists + `--force` flag |
36
- | 📦 **Auto Install** | Detects bun / pnpm / yarn / npm and installs devDependencies |
37
- | 🔍 **Fuzzy Matching** | Typo a preset name? Levenshtein distance finds the closest match |
38
- | 🧪 **Dry Run** | Preview all changes with `--dry-run` before writing anything |
39
- | 🔗 **Script Injection** | Auto-injects `<pm> lint` / `<pm> format` scripts into package.json |
40
- | 🌐 **Proxy Management** | Persistent proxy config with `set` / `unset` — copy to CMD / PowerShell / Bash |
41
- | 🔄 **Self-Update** | `lux update` checks and installs the latest version automatically |
42
-
43
- <br />
44
-
45
27
  ### Quick Start
46
28
 
47
29
  ```bash
48
30
  # Install globally (pick your package manager)
49
31
  npm install -g @luxkit/cli
50
- # or
51
- bun add -g @luxkit/cli
52
32
 
53
- # Initialize formatting configs
54
- lux fmt web-vue # Generate ESLint, Prettier, CSpell
33
+ # Initialize skill and preset
34
+ lux init && lux init --preset
35
+
36
+ # Lint usage
37
+ lux fmt web-vue # Configure web-vue lint — ESLint, Prettier, CSpell
55
38
  lux fmt web-vue --stylelint # Also include Stylelint
56
39
  lux fmt web-vue --editorconfig # Also include EditorConfig
57
40
 
58
- # Initialize VSCode settings
59
- lux vscode web-vue # Generate .vscode/settings.json + extensions.json
60
-
61
- # Initialize AI coding tool skills
62
- lux init # Select tool interactively, copy skills to project
41
+ # VSCode config (optional)
42
+ # If you've already configured globally, you can skip this
43
+ lux vscode web-vue # Generate .vscode/settings.json + extensions.json (per-project)
63
44
 
64
45
  # List available presets
65
46
  lux fmt list
@@ -68,15 +49,26 @@ lux vscode list
68
49
 
69
50
  <br />
70
51
 
52
+ ### Custom Configuration
53
+
54
+ ```bash
55
+ # Check if skill and presets are initialized (skip if already done)
56
+ lux init && lux init --preset
57
+
58
+ # Use an agent to customize a preset
59
+ /lux help me configure my web react lint template to fit my development project style
60
+ ```
61
+
71
62
  ### CLI Commands
72
63
 
73
64
  | Command | Description |
74
65
  | :-------------------------- | :---------------------------------------------------------------- |
75
- | `lux fmt <preset>` | Initialize formatting config files |
76
- | `lux fmt list` | List available fmt presets |
77
- | `lux vscode <preset>` | Initialize VSCode workspace settings |
66
+ | `lux fmt <preset>` | Initialize lint configs |
67
+ | `lux fmt list` | List available lint presets |
68
+ | `lux vscode <preset>` | Configure VSCode settings (per-project) |
78
69
  | `lux vscode list` | List available VSCode presets |
79
- | `lux init` | Initialize AI coding tool skills in current project |
70
+ | `lux init` | Initialize skills |
71
+ | `lux init --preset` | Initialize all presets |
80
72
  | `lux set <key=value> [...]` | Persist proxy env vars (e.g. `https_proxy=http://127.0.0.1:7890`) |
81
73
  | `lux unset` | Clear all stored proxy configuration |
82
74
  | `lux show env` | Display stored proxy environment variables |
@@ -88,19 +80,6 @@ lux vscode list
88
80
 
89
81
  <br />
90
82
 
91
- ### Available Presets
92
-
93
- | Preset | Fmt | VSCode | Stack |
94
- | :------------- | :-: | :----: | :------------------------- |
95
- | `web-vue` | ✅ | ✅ | Vue 3 / Vite / TS / CSS |
96
- | `web-react` | ✅ | ✅ | React / Vite / TS / CSS |
97
- | `electron-vue` | ✅ | ✅ | Electron + Vue / Web stack |
98
- | `node` | ✅ | ✅ | Node.js backend |
99
- | `nest` | ✅ | ✅ | NestJS backend |
100
- | `go` | — | ✅ | Go backend |
101
-
102
- <br />
103
-
104
83
  ### Options
105
84
 
106
85
  ```bash
@@ -147,37 +126,6 @@ lux fmt web-vue
147
126
 
148
127
  <br />
149
128
 
150
- ### Tech Stack
151
-
152
- | Category | Technology |
153
- | :------- | :----------------------------------------- |
154
- | Language | TypeScript 6.0 (ESM-only) |
155
- | Runtime | Node.js 18+ |
156
- | Build | tsup |
157
- | Test | Vitest (unit + acceptance) |
158
- | CLI | Commander.js |
159
- | Output | Chalk |
160
- | Bundle | Minimal runtime deps (chalk + commander + @clack/prompts) |
161
-
162
- <br />
163
-
164
- ### Development
165
-
166
- ```bash
167
- git clone git@github.com:TTT1231/lux.git
168
- cd lux
169
- bun install
170
-
171
- bun link # Register `lux` globally for testing
172
- lux fmt web-vue # Test it on any project
173
-
174
- bun test # Run tests
175
- bun build # Build to dist/
176
- bun code:check:all # lint + format + spell check
177
- ```
178
-
179
- <br />
180
-
181
129
  ### 🤝 Support
182
130
 
183
131
  If you have any questions or run into issues, feel free to [open an issue](https://github.com/TTT1231/lux/issues) on GitHub.
package/dist/index.js CHANGED
@@ -12,13 +12,13 @@ var electronVueFmt = {
12
12
  name: "electron-vue",
13
13
  description: "Vue 3 + Electron desktop app",
14
14
  eslint: () => `import withVue from '@vue/eslint-config-typescript'
15
- import withPrettier from '@vue/eslint-config-prettier/skip-formatting'
15
+ import prettierConfig from '@vue/eslint-config-prettier/skip-formatting'
16
16
  import pluginVue from 'eslint-plugin-vue'
17
17
 
18
18
  export default [
19
19
  ...pluginVue.configs['flat/recommended'],
20
20
  ...withVue(),
21
- ...withPrettier(),
21
+ prettierConfig,
22
22
  {
23
23
  rules: {
24
24
  'vue/multi-word-component-names': 'off',
@@ -79,7 +79,8 @@ out/
79
79
  version: "0.2",
80
80
  language: "en,en-US",
81
81
  allowCompoundWords: true,
82
- words: ["vite", "pinia", "vueuse", "unplugin", "electron", "electron-builder"]
82
+ words: ["vite", "pinia", "vueuse", "unplugin", "electron", "electron-builder"],
83
+ ignorePaths: ["**/*.svg", "**/*.png"]
83
84
  },
84
85
  null,
85
86
  2
@@ -115,18 +116,9 @@ trim_trailing_whitespace = false
115
116
  ]
116
117
  },
117
118
  scripts: {
118
- lint: "eslint . --cache --cache-location node_modules/.cache/.eslintcache",
119
- "lint:fix": 'eslint "src/**/*.{js,ts,vue}" --fix --cache --cache-location node_modules/.cache/.eslintcache',
120
- format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"',
121
- "format:check": 'prettier --check "src/**/*.{ts,js,json,vue,css,scss}"',
122
- stylelint: 'stylelint "src/**/*.{css,scss,vue}"',
123
- "stylelint:fix": 'stylelint "src/**/*.{css,scss,vue}" --fix',
124
- cspell: 'cspell --gitignore "src/**/*"',
125
- "type:check": "vue-tsc --noEmit",
126
- "code:check": "<pm> lint && <pm> format:check",
127
- "code:fix": "<pm> lint:fix && <pm> format",
128
- "code:check:all": "<pm> lint && <pm> format:check && <pm> stylelint && <pm> cspell",
129
- "code:fix:all": "<pm> lint:fix && <pm> format && <pm> stylelint:fix"
119
+ lint: 'eslint . --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore "src/**/*" && vue-tsc --noEmit && stylelint "src/**/*.{css,scss,vue}" --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
120
+ "lint:fix": 'eslint . --cache --cache-location node_modules/.cache/eslint --fix && stylelint "src/**/*.{css,scss,vue}" --fix --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
121
+ format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"'
130
122
  }
131
123
  };
132
124
 
@@ -163,7 +155,8 @@ coverage/
163
155
  version: "0.2",
164
156
  language: "en,en-US",
165
157
  allowCompoundWords: true,
166
- words: ["nestjs", "typeorm", "dtos"]
158
+ words: ["nestjs", "typeorm", "dtos"],
159
+ ignorePaths: ["**/*.svg", "**/*.png"]
167
160
  },
168
161
  null,
169
162
  2
@@ -184,12 +177,10 @@ trim_trailing_whitespace = false
184
177
  dependencies: {
185
178
  dev: ["prettier", "cspell"]
186
179
  },
187
- // NestJS: only append new scripts, don't conflict with existing ones
188
180
  scripts: {
189
- cspell: 'cspell --gitignore "src/**/*"',
190
- "type:check": "tsc --noEmit",
191
- "code:check": "<pm> lint && <pm> format:check",
192
- "code:check:all": "<pm> lint && <pm> format:check && <pm> cspell"
181
+ lint: 'eslint "{src,apps,libs,test}/**/*.ts" --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore "src/**/*" && tsc --noEmit',
182
+ "lint:fix": 'eslint "{src,apps,libs,test}/**/*.ts" --cache --cache-location node_modules/.cache/eslint --fix',
183
+ format: 'prettier --write "src/**/*.{ts,js,json}"'
193
184
  }
194
185
  };
195
186
 
@@ -268,7 +259,8 @@ coverage/
268
259
  version: "0.2",
269
260
  language: "en,en-US",
270
261
  allowCompoundWords: true,
271
- words: []
262
+ words: [],
263
+ ignorePaths: ["**/*.svg", "**/*.png"]
272
264
  },
273
265
  null,
274
266
  2
@@ -298,16 +290,9 @@ trim_trailing_whitespace = false
298
290
  ]
299
291
  },
300
292
  scripts: {
301
- lint: "eslint . --cache --cache-location node_modules/.cache/.eslintcache",
302
- "lint:fix": 'eslint "src/**/*.{js,ts}" --fix --cache --cache-location node_modules/.cache/.eslintcache',
303
- format: 'prettier --write "src/**/*.{ts,js,json}"',
304
- "format:check": 'prettier --check "src/**/*.{ts,js,json}"',
305
- cspell: 'cspell --gitignore "src/**/*"',
306
- "type:check": "tsc --noEmit",
307
- "code:check": "<pm> lint && <pm> format:check",
308
- "code:fix": "<pm> lint:fix && <pm> format",
309
- "code:check:all": "<pm> lint && <pm> format:check && <pm> cspell",
310
- "code:fix:all": "<pm> lint:fix && <pm> format"
293
+ lint: 'eslint . --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore "src/**/*" && tsc --noEmit',
294
+ "lint:fix": "eslint . --cache --cache-location node_modules/.cache/eslint --fix",
295
+ format: 'prettier --write "src/**/*.{ts,js,json}"'
311
296
  }
312
297
  };
313
298
 
@@ -316,13 +301,13 @@ var uniappFmt = {
316
301
  name: "uniapp",
317
302
  description: "Vue 3 + UniApp WeChat mini program",
318
303
  eslint: () => `import withVue from '@vue/eslint-config-typescript'
319
- import withPrettier from '@vue/eslint-config-prettier/skip-formatting'
304
+ import prettierConfig from '@vue/eslint-config-prettier/skip-formatting'
320
305
  import pluginVue from 'eslint-plugin-vue'
321
306
 
322
307
  export default [
323
308
  ...pluginVue.configs['flat/recommended'],
324
309
  ...withVue(),
325
- ...withPrettier(),
310
+ prettierConfig,
326
311
  {
327
312
  rules: {
328
313
  'vue/multi-word-component-names': 'off',
@@ -381,7 +366,8 @@ unpackage/
381
366
  version: "0.2",
382
367
  language: "en,en-US",
383
368
  allowCompoundWords: true,
384
- words: ["vite", "pinia", "vueuse", "unplugin", "uniapp"]
369
+ words: ["vite", "pinia", "vueuse", "unplugin", "uniapp"],
370
+ ignorePaths: ["**/*.svg", "**/*.png"]
385
371
  },
386
372
  null,
387
373
  2
@@ -417,18 +403,9 @@ trim_trailing_whitespace = false
417
403
  ]
418
404
  },
419
405
  scripts: {
420
- lint: "eslint . --cache --cache-location node_modules/.cache/.eslintcache",
421
- "lint:fix": 'eslint "src/**/*.{js,ts,vue}" --fix --cache --cache-location node_modules/.cache/.eslintcache',
422
- format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"',
423
- "format:check": 'prettier --check "src/**/*.{ts,js,json,vue,css,scss}"',
424
- stylelint: 'stylelint "src/**/*.{css,scss,vue}"',
425
- "stylelint:fix": 'stylelint "src/**/*.{css,scss,vue}" --fix',
426
- cspell: 'cspell --gitignore "src/**/*"',
427
- "type:check": "vue-tsc --noEmit",
428
- "code:check": "<pm> lint && <pm> format:check",
429
- "code:fix": "<pm> lint:fix && <pm> format",
430
- "code:check:all": "<pm> lint && <pm> format:check && <pm> stylelint && <pm> cspell",
431
- "code:fix:all": "<pm> lint:fix && <pm> format && <pm> stylelint:fix"
406
+ lint: 'eslint . --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore "src/**/*" && vue-tsc --noEmit && stylelint "src/**/*.{css,scss,vue}" --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
407
+ "lint:fix": 'eslint . --cache --cache-location node_modules/.cache/eslint --fix && stylelint "src/**/*.{css,scss,vue}" --fix --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
408
+ format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"'
432
409
  }
433
410
  };
434
411
 
@@ -507,7 +484,8 @@ dist/
507
484
  version: "0.2",
508
485
  language: "en,en-US",
509
486
  allowCompoundWords: true,
510
- words: ["vite", "react", "zustand", "tanstack"]
487
+ words: ["vite", "react", "zustand", "tanstack"],
488
+ ignorePaths: ["**/*.svg", "**/*.png"]
511
489
  },
512
490
  null,
513
491
  2
@@ -545,18 +523,9 @@ trim_trailing_whitespace = false
545
523
  ]
546
524
  },
547
525
  scripts: {
548
- lint: "eslint . --cache --cache-location node_modules/.cache/.eslintcache",
549
- "lint:fix": 'eslint "src/**/*.{js,ts,jsx,tsx}" --fix --cache --cache-location node_modules/.cache/.eslintcache',
550
- format: 'prettier --write "src/**/*.{ts,js,json,jsx,tsx,css,scss}"',
551
- "format:check": 'prettier --check "src/**/*.{ts,js,json,jsx,tsx,css,scss}"',
552
- stylelint: 'stylelint "src/**/*.{css,scss}"',
553
- "stylelint:fix": 'stylelint "src/**/*.{css,scss}" --fix',
554
- cspell: 'cspell --gitignore "src/**/*"',
555
- "type:check": "tsc --noEmit",
556
- "code:check": "<pm> lint && <pm> format:check",
557
- "code:fix": "<pm> lint:fix && <pm> format",
558
- "code:check:all": "<pm> lint && <pm> format:check && <pm> stylelint && <pm> cspell",
559
- "code:fix:all": "<pm> lint:fix && <pm> format && <pm> stylelint:fix"
526
+ lint: 'eslint . --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore "src/**/*" && tsc --noEmit && stylelint "src/**/*.{css,scss}" --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
527
+ "lint:fix": 'eslint . --cache --cache-location node_modules/.cache/eslint --fix && stylelint "src/**/*.{css,scss}" --fix --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
528
+ format: 'prettier --write "src/**/*.{ts,js,json,jsx,tsx,css,scss}"'
560
529
  }
561
530
  };
562
531
 
@@ -565,13 +534,13 @@ var webVueFmt = {
565
534
  name: "web-vue",
566
535
  description: "Vue 3 Web frontend (Vite + Vue + TypeScript)",
567
536
  eslint: () => `import withVue from '@vue/eslint-config-typescript'
568
- import withPrettier from '@vue/eslint-config-prettier/skip-formatting'
537
+ import prettierConfig from '@vue/eslint-config-prettier/skip-formatting'
569
538
  import pluginVue from 'eslint-plugin-vue'
570
539
 
571
540
  export default [
572
541
  ...pluginVue.configs['flat/recommended'],
573
542
  ...withVue(),
574
- ...withPrettier(),
543
+ prettierConfig,
575
544
  {
576
545
  rules: {
577
546
  'vue/multi-word-component-names': 'off',
@@ -628,7 +597,8 @@ dist/
628
597
  version: "0.2",
629
598
  language: "en,en-US",
630
599
  allowCompoundWords: true,
631
- words: ["vite", "pinia", "vueuse", "unplugin"]
600
+ words: ["vite", "pinia", "vueuse", "unplugin"],
601
+ ignorePaths: ["**/*.svg", "**/*.png"]
632
602
  },
633
603
  null,
634
604
  2
@@ -664,18 +634,9 @@ trim_trailing_whitespace = false
664
634
  ]
665
635
  },
666
636
  scripts: {
667
- lint: "eslint . --cache --cache-location node_modules/.cache/.eslintcache",
668
- "lint:fix": 'eslint "src/**/*.{js,ts,vue}" --fix --cache --cache-location node_modules/.cache/.eslintcache',
669
- format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"',
670
- "format:check": 'prettier --check "src/**/*.{ts,js,json,vue,css,scss}"',
671
- stylelint: 'stylelint "src/**/*.{css,scss,vue}"',
672
- "stylelint:fix": 'stylelint "src/**/*.{css,scss,vue}" --fix',
673
- cspell: 'cspell --gitignore "src/**/*"',
674
- "type:check": "vue-tsc --noEmit",
675
- "code:check": "<pm> lint && <pm> format:check",
676
- "code:fix": "<pm> lint:fix && <pm> format",
677
- "code:check:all": "<pm> lint && <pm> format:check && <pm> stylelint && <pm> cspell",
678
- "code:fix:all": "<pm> lint:fix && <pm> format && <pm> stylelint:fix"
637
+ lint: 'eslint . --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore "src/**/*" && vue-tsc --noEmit && stylelint "src/**/*.{css,scss,vue}" --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
638
+ "lint:fix": 'eslint . --cache --cache-location node_modules/.cache/eslint --fix && stylelint "src/**/*.{css,scss,vue}" --fix --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
639
+ format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"'
679
640
  }
680
641
  };
681
642
 
@@ -1113,6 +1074,15 @@ function materializeVscodePreset(cwd, presetName) {
1113
1074
  }
1114
1075
  logger.log(`Local preset created at ${presetDir}`);
1115
1076
  }
1077
+ function materializeVscodePresetFromBuiltin(presetName, preset) {
1078
+ const presetDir = getLocalPresetDir("vscode", presetName);
1079
+ ensureDir(presetDir);
1080
+ const settings = preset.settings();
1081
+ writeJson(path4.join(presetDir, "settings.json"), settings);
1082
+ const extensions = preset.extensions();
1083
+ writeJson(path4.join(presetDir, "extensions.json"), { recommendations: extensions });
1084
+ logger.log(`Local preset created at ${presetDir}`);
1085
+ }
1116
1086
  var InvalidPackageJsonError = class extends Error {
1117
1087
  constructor(filePath) {
1118
1088
  super(`package.json exists but is not valid JSON: ${filePath}`);
@@ -1220,7 +1190,9 @@ function applyLocalVscodePreset(cwd, presetName, opts) {
1220
1190
  if (extensionsData) {
1221
1191
  let presetRecommendations = extensionsData.recommendations ?? [];
1222
1192
  if (opts.noStylelint) {
1223
- presetRecommendations = presetRecommendations.filter((ext) => ext !== STYLELINT_EXTENSION);
1193
+ presetRecommendations = presetRecommendations.filter(
1194
+ (ext) => ext !== STYLELINT_EXTENSION
1195
+ );
1224
1196
  }
1225
1197
  if (opts.dryRun) {
1226
1198
  result.created.push(".vscode/extensions.json");
@@ -1273,8 +1245,10 @@ function mergeTemplateIntoProject(templatePkg, projectPkg, pm, opts, result) {
1273
1245
  const existingScripts = merged.scripts ?? {};
1274
1246
  const newScripts = { ...existingScripts };
1275
1247
  for (const [key, value] of Object.entries(templatePkg.scripts)) {
1276
- if (opts.noStylelint && key.startsWith("stylelint")) continue;
1277
- const resolved = value.replace(/<pm>/g, prefix);
1248
+ let resolved = value.replace(/<pm>/g, prefix);
1249
+ if (opts.noStylelint) {
1250
+ resolved = resolved.replace(/\s*&&\s*stylelint\s+"[^"]*".*/g, "");
1251
+ }
1278
1252
  if (existingScripts[key] !== void 0 && !opts.force) {
1279
1253
  result.scriptsSkipped++;
1280
1254
  if (opts.dryRun) {
@@ -1325,8 +1299,7 @@ function resolveLocalDeps(deps) {
1325
1299
  function filterStylelintScripts(scripts) {
1326
1300
  const filtered = {};
1327
1301
  for (const [key, value] of Object.entries(scripts)) {
1328
- if (key.startsWith("stylelint")) continue;
1329
- filtered[key] = value.replace(/\s*&&\s*<pm>\s+stylelint\S*/g, "");
1302
+ filtered[key] = value.replace(/\s*&&\s*stylelint\s+"[^"]*".*/g, "");
1330
1303
  }
1331
1304
  return filtered;
1332
1305
  }
@@ -1419,18 +1392,26 @@ async function executeLocalPath(cwd, presetName, options) {
1419
1392
  const existingDeps = projectPkg.devDependencies ?? {};
1420
1393
  const missing = depsToInstall.filter((dep) => !existingDeps[dep]);
1421
1394
  if (missing.length === 0) return;
1422
- if (options.install === false) {
1423
- const resolved = resolveLocalDeps(templatePkg.devDependencies);
1424
- const added = await addDepsToManifest(resolved, cwd);
1425
- if (added.length > 0) {
1426
- logger.success(`Added to package.json (skipped install): ${added.join(", ")}`);
1427
- } else {
1428
- logger.log("All dependencies already in package.json");
1429
- }
1395
+ if (opts.dryRun) {
1396
+ logger.log(`[dry-run] Would add to package.json: ${missing.join(", ")}`);
1430
1397
  return;
1431
1398
  }
1432
- if (opts.dryRun) {
1433
- logger.log(`[dry-run] Would install: ${missing.join(", ")}`);
1399
+ if (options.install === false) {
1400
+ try {
1401
+ const filteredTemplateDeps = Object.fromEntries(
1402
+ Object.entries(templatePkg.devDependencies).filter(([k]) => missing.includes(k))
1403
+ );
1404
+ const resolved = resolveLocalDeps(filteredTemplateDeps);
1405
+ const added = await addDepsToManifest(resolved, cwd);
1406
+ if (added.length > 0) {
1407
+ logger.success(`Added to package.json (skipped install): ${added.join(", ")}`);
1408
+ } else {
1409
+ logger.log("All dependencies already in package.json");
1410
+ }
1411
+ } catch (error) {
1412
+ const message = error instanceof Error ? error.message : String(error);
1413
+ logger.warn(`Failed to fetch versions: ${message}. You can add dependencies manually.`);
1414
+ }
1434
1415
  return;
1435
1416
  }
1436
1417
  try {
@@ -1478,17 +1459,22 @@ async function executeBuiltinPath(cwd, presetName, preset, options) {
1478
1459
  if (!preset.dependencies?.dev) return;
1479
1460
  const devDeps = opts.noStylelint ? preset.dependencies.dev.filter(isNotStylelintDep) : preset.dependencies.dev;
1480
1461
  const finalDeps = opts.noEditorconfig ? devDeps.filter(isNotEditorconfigDep) : devDeps;
1481
- if (options.install === false) {
1482
- const added = await addDepsToManifest(finalDeps, cwd);
1483
- if (added.length > 0) {
1484
- logger.success(`Added to package.json (skipped install): ${added.join(", ")}`);
1485
- } else {
1486
- logger.log("All dependencies already in package.json");
1487
- }
1462
+ if (opts.dryRun) {
1463
+ logger.log(`[dry-run] Would add to package.json: ${finalDeps.join(", ")}`);
1488
1464
  return;
1489
1465
  }
1490
- if (opts.dryRun) {
1491
- logger.log(`[dry-run] Would install: ${finalDeps.join(", ")}`);
1466
+ if (options.install === false) {
1467
+ try {
1468
+ const added = await addDepsToManifest(finalDeps, cwd);
1469
+ if (added.length > 0) {
1470
+ logger.success(`Added to package.json (skipped install): ${added.join(", ")}`);
1471
+ } else {
1472
+ logger.log("All dependencies already in package.json");
1473
+ }
1474
+ } catch (error) {
1475
+ const message = error instanceof Error ? error.message : String(error);
1476
+ logger.warn(`Failed to fetch versions: ${message}. You can add dependencies manually.`);
1477
+ }
1492
1478
  return;
1493
1479
  }
1494
1480
  try {
@@ -1622,206 +1608,6 @@ var INIT_TOOLS = [
1622
1608
  }
1623
1609
  ];
1624
1610
 
1625
- // src/generators/init.ts
1626
- import fs4 from "fs";
1627
- import path6 from "path";
1628
- function resolveSkillsDir() {
1629
- const entryDir = path6.dirname(process.argv[1] ?? "");
1630
- return path6.resolve(entryDir, "skills");
1631
- }
1632
- function listFilesRecursive(dir, base) {
1633
- const entries = fs4.readdirSync(dir, { withFileTypes: true });
1634
- const files = [];
1635
- for (const entry of entries) {
1636
- const childBase = `${base}/${entry.name}`;
1637
- const fullPath = path6.join(dir, entry.name);
1638
- if (entry.isDirectory()) {
1639
- files.push(...listFilesRecursive(fullPath, childBase));
1640
- } else {
1641
- files.push(childBase);
1642
- }
1643
- }
1644
- return files;
1645
- }
1646
- function generateInitSkills(targetBaseDir, cwd) {
1647
- const skillsDir = resolveSkillsDir();
1648
- if (!fs4.existsSync(skillsDir)) {
1649
- logger.error(`Bundled skills directory not found: ${skillsDir}`);
1650
- logger.error('Please run "lux build" or reinstall lux.');
1651
- return { copiedFiles: [], targetDir: targetBaseDir };
1652
- }
1653
- const targetPath = path6.resolve(cwd, targetBaseDir);
1654
- try {
1655
- fs4.cpSync(skillsDir, targetPath, { recursive: true, force: true });
1656
- } catch (error) {
1657
- const message = error instanceof Error ? error.message : String(error);
1658
- logger.error(`Failed to copy skills to ${targetPath}: ${message}`);
1659
- return { copiedFiles: [], targetDir: targetBaseDir };
1660
- }
1661
- const copiedFiles = fs4.existsSync(targetPath) ? listFilesRecursive(targetPath, targetBaseDir) : [];
1662
- return { copiedFiles, targetDir: targetBaseDir };
1663
- }
1664
-
1665
- // src/commands/init.ts
1666
- function registerInitCommand(program2) {
1667
- program2.command("init").description("Initialize AI coding tool skills in current project").action(async () => {
1668
- const toolOptions = INIT_TOOLS.map((tool2) => ({
1669
- value: tool2.name,
1670
- label: tool2.label
1671
- }));
1672
- const selected = await select({
1673
- message: "Which AI coding tool do you use?",
1674
- options: toolOptions
1675
- });
1676
- if (isCancel(selected)) {
1677
- cancel("Operation cancelled.");
1678
- return;
1679
- }
1680
- const tool = INIT_TOOLS.find((t) => t.name === selected);
1681
- if (!tool) {
1682
- logger.error(`Unknown tool: ${String(selected)}`);
1683
- return;
1684
- }
1685
- const cwd = process.cwd();
1686
- const result = generateInitSkills(tool.targetDir, cwd);
1687
- if (result.copiedFiles.length === 0) {
1688
- logger.warn("No skill files were copied.");
1689
- return;
1690
- }
1691
- for (const file of result.copiedFiles) {
1692
- logger.log(` ${file}`);
1693
- }
1694
- outro(`Skills installed to ${tool.targetDir}/`);
1695
- });
1696
- }
1697
-
1698
- // src/utils/config.ts
1699
- import fs5 from "fs";
1700
- import os2 from "os";
1701
- import path7 from "path";
1702
- var CONFIG_DIR = ".lux";
1703
- var ENV_FILE = "env.txt";
1704
- function getEnvConfigPath() {
1705
- return path7.join(os2.homedir(), CONFIG_DIR, ENV_FILE);
1706
- }
1707
- function getEnvConfig() {
1708
- let content;
1709
- try {
1710
- content = fs5.readFileSync(getEnvConfigPath(), "utf-8");
1711
- } catch {
1712
- return {};
1713
- }
1714
- const result = {};
1715
- for (const line of content.split("\n")) {
1716
- const trimmed = line.trim();
1717
- if (!trimmed || trimmed.startsWith("#")) continue;
1718
- const eqIndex = trimmed.indexOf("=");
1719
- if (eqIndex === -1) continue;
1720
- const key = trimmed.slice(0, eqIndex).trim();
1721
- const raw = trimmed.slice(eqIndex + 1).trim();
1722
- const value = raw.replace(/^["']|["']$/g, "");
1723
- if (key) result[key] = value;
1724
- }
1725
- return result;
1726
- }
1727
- function setEnvConfig(data) {
1728
- const lines = Object.entries(data).filter(([, v]) => v !== "").map(([k, v]) => `${k}="${v}"`);
1729
- writeFile(getEnvConfigPath(), lines.join("\n") + "\n");
1730
- }
1731
- function clearEnvConfig() {
1732
- try {
1733
- fs5.unlinkSync(getEnvConfigPath());
1734
- } catch {
1735
- }
1736
- }
1737
-
1738
- // src/commands/show.ts
1739
- function handleShowEnv() {
1740
- const config = getEnvConfig();
1741
- const entries = Object.entries(config);
1742
- if (entries.length === 0) {
1743
- logger.log("No env config.");
1744
- return;
1745
- }
1746
- for (const [key, value] of entries) {
1747
- logger.log(`${key}="${value}"`);
1748
- }
1749
- }
1750
- function registerShowCommand(program2) {
1751
- const show = program2.command("show");
1752
- show.command("env").description("Display stored proxy environment variables").action(() => handleShowEnv());
1753
- }
1754
-
1755
- // src/utils/version.ts
1756
- import { existsSync, readFileSync } from "fs";
1757
- import { dirname, join } from "path";
1758
- import { fileURLToPath } from "url";
1759
- var PACKAGE_NAME = "@luxkit/cli";
1760
- var cachedVersion;
1761
- function getCurrentVersion() {
1762
- if (cachedVersion) return cachedVersion;
1763
- const __dirname = dirname(fileURLToPath(import.meta.url));
1764
- const candidates = [
1765
- join(__dirname, "..", "package.json"),
1766
- join(__dirname, "..", "..", "package.json")
1767
- ];
1768
- const pkgPath = candidates.find((p) => existsSync(p));
1769
- if (!pkgPath) {
1770
- throw new Error("Cannot locate package.json for version reading");
1771
- }
1772
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
1773
- cachedVersion = pkg.version;
1774
- return cachedVersion;
1775
- }
1776
-
1777
- // src/commands/update.ts
1778
- var GLOBAL_UPDATE_CMDS = {
1779
- npm: ["npm", ["install", "-g", `${PACKAGE_NAME}@latest`]],
1780
- bun: ["bun", ["install", "-g", `${PACKAGE_NAME}@latest`]]
1781
- };
1782
- function detectGlobalPackageManager() {
1783
- return process.execPath.toLowerCase().includes("bun") ? "bun" : "npm";
1784
- }
1785
- async function fetchLatestVersion() {
1786
- const { stdout, exitCode } = await execFileNoThrow("npm", ["view", PACKAGE_NAME, "version"]);
1787
- if (exitCode !== 0 || !stdout) {
1788
- throw new Error(`Failed to fetch latest version from npm registry.`);
1789
- }
1790
- const lines = stdout.split("\n").filter((line) => line.trim().length > 0);
1791
- return lines[lines.length - 1].trim();
1792
- }
1793
- async function performUpdate(pm) {
1794
- const [command, args] = GLOBAL_UPDATE_CMDS[pm];
1795
- const { exitCode, stderr } = await execFileNoThrow(command, args);
1796
- if (exitCode !== 0) {
1797
- const hint = /EACCES|permission/i.test(stderr) ? "\nTry running with elevated privileges (e.g. sudo)." : "";
1798
- throw new Error(`Update failed: ${stderr}${hint}`);
1799
- }
1800
- }
1801
- function registerUpdateCommand(program2) {
1802
- program2.command("update").description("Update @luxkit/cli to the latest version").option("--check", "Only check for updates without installing").action(async (options) => {
1803
- try {
1804
- const current = getCurrentVersion();
1805
- const latest = await fetchLatestVersion();
1806
- if (current === latest) {
1807
- logger.log(`Already up to date (v${current})`);
1808
- return;
1809
- }
1810
- if (options.check) {
1811
- logger.log(`Update available: v${current} \u2192 v${latest}`);
1812
- return;
1813
- }
1814
- const pm = detectGlobalPackageManager();
1815
- await performUpdate(pm);
1816
- logger.log(`Updated to v${latest}`);
1817
- } catch (err) {
1818
- const message = err instanceof Error ? err.message : String(err);
1819
- logger.error(message);
1820
- logger.warn(`You can also update manually: npm install -g ${PACKAGE_NAME}@latest`);
1821
- }
1822
- });
1823
- }
1824
-
1825
1611
  // src/presets/vscode/web-vue.ts
1826
1612
  var webVueVscode = {
1827
1613
  name: "web-vue",
@@ -2580,6 +2366,226 @@ var VSCODE_PRESETS = [
2580
2366
  goVscode
2581
2367
  ];
2582
2368
 
2369
+ // src/generators/init.ts
2370
+ import fs4 from "fs";
2371
+ import path6 from "path";
2372
+ function resolveSkillsDir() {
2373
+ const entryDir = path6.dirname(process.argv[1] ?? "");
2374
+ return path6.resolve(entryDir, "skills");
2375
+ }
2376
+ function listFilesRecursive(dir, base) {
2377
+ const entries = fs4.readdirSync(dir, { withFileTypes: true });
2378
+ const files = [];
2379
+ for (const entry of entries) {
2380
+ const childBase = `${base}/${entry.name}`;
2381
+ const fullPath = path6.join(dir, entry.name);
2382
+ if (entry.isDirectory()) {
2383
+ files.push(...listFilesRecursive(fullPath, childBase));
2384
+ } else {
2385
+ files.push(childBase);
2386
+ }
2387
+ }
2388
+ return files;
2389
+ }
2390
+ function generateInitSkills(targetBaseDir, cwd) {
2391
+ const skillsDir = resolveSkillsDir();
2392
+ if (!fs4.existsSync(skillsDir)) {
2393
+ logger.error(`Bundled skills directory not found: ${skillsDir}`);
2394
+ logger.error('Please run "lux build" or reinstall lux.');
2395
+ return { copiedFiles: [], targetDir: targetBaseDir };
2396
+ }
2397
+ const targetPath = path6.resolve(cwd, targetBaseDir);
2398
+ try {
2399
+ fs4.cpSync(skillsDir, targetPath, { recursive: true, force: true });
2400
+ } catch (error) {
2401
+ const message = error instanceof Error ? error.message : String(error);
2402
+ logger.error(`Failed to copy skills to ${targetPath}: ${message}`);
2403
+ return { copiedFiles: [], targetDir: targetBaseDir };
2404
+ }
2405
+ const copiedFiles = fs4.existsSync(targetPath) ? listFilesRecursive(targetPath, targetBaseDir) : [];
2406
+ return { copiedFiles, targetDir: targetBaseDir };
2407
+ }
2408
+
2409
+ // src/commands/init.ts
2410
+ function registerInitCommand(program2) {
2411
+ program2.command("init").description("Initialize skills or materialize presets").option("--preset", "Materialize all presets to ~/.lux/preset/ without writing to cwd").action(async (options) => {
2412
+ if (options.preset) {
2413
+ materializeAllPresets();
2414
+ return;
2415
+ }
2416
+ const toolOptions = INIT_TOOLS.map((tool2) => ({
2417
+ value: tool2.name,
2418
+ label: tool2.label
2419
+ }));
2420
+ const selected = await select({
2421
+ message: "Which AI coding tool do you use?",
2422
+ options: toolOptions
2423
+ });
2424
+ if (isCancel(selected)) {
2425
+ cancel("Operation cancelled.");
2426
+ return;
2427
+ }
2428
+ const tool = INIT_TOOLS.find((t) => t.name === selected);
2429
+ if (!tool) {
2430
+ logger.error(`Unknown tool: ${String(selected)}`);
2431
+ return;
2432
+ }
2433
+ const cwd = process.cwd();
2434
+ const result = generateInitSkills(tool.targetDir, cwd);
2435
+ if (result.copiedFiles.length === 0) {
2436
+ logger.warn("No skill files were copied.");
2437
+ return;
2438
+ }
2439
+ for (const file of result.copiedFiles) {
2440
+ logger.log(` ${file}`);
2441
+ }
2442
+ outro(`Skills installed to ${tool.targetDir}/`);
2443
+ });
2444
+ }
2445
+ function materializeAllPresets() {
2446
+ const opts = {
2447
+ cwd: process.cwd(),
2448
+ force: false,
2449
+ dryRun: false,
2450
+ noStylelint: false,
2451
+ noEditorconfig: false
2452
+ };
2453
+ for (const preset of FMT_PRESETS) {
2454
+ materializeFmtPreset(preset.name, preset, opts);
2455
+ }
2456
+ for (const preset of VSCODE_PRESETS) {
2457
+ materializeVscodePresetFromBuiltin(preset.name, preset);
2458
+ }
2459
+ logger.success("All presets materialized to ~/.lux/preset/");
2460
+ }
2461
+
2462
+ // src/utils/config.ts
2463
+ import fs5 from "fs";
2464
+ import os2 from "os";
2465
+ import path7 from "path";
2466
+ var CONFIG_DIR = ".lux";
2467
+ var ENV_FILE = "env.txt";
2468
+ function getEnvConfigPath() {
2469
+ return path7.join(os2.homedir(), CONFIG_DIR, ENV_FILE);
2470
+ }
2471
+ function getEnvConfig() {
2472
+ let content;
2473
+ try {
2474
+ content = fs5.readFileSync(getEnvConfigPath(), "utf-8");
2475
+ } catch {
2476
+ return {};
2477
+ }
2478
+ const result = {};
2479
+ for (const line of content.split("\n")) {
2480
+ const trimmed = line.trim();
2481
+ if (!trimmed || trimmed.startsWith("#")) continue;
2482
+ const eqIndex = trimmed.indexOf("=");
2483
+ if (eqIndex === -1) continue;
2484
+ const key = trimmed.slice(0, eqIndex).trim();
2485
+ const raw = trimmed.slice(eqIndex + 1).trim();
2486
+ const value = raw.replace(/^["']|["']$/g, "");
2487
+ if (key) result[key] = value;
2488
+ }
2489
+ return result;
2490
+ }
2491
+ function setEnvConfig(data) {
2492
+ const lines = Object.entries(data).filter(([, v]) => v !== "").map(([k, v]) => `${k}="${v}"`);
2493
+ writeFile(getEnvConfigPath(), lines.join("\n") + "\n");
2494
+ }
2495
+ function clearEnvConfig() {
2496
+ try {
2497
+ fs5.unlinkSync(getEnvConfigPath());
2498
+ } catch {
2499
+ }
2500
+ }
2501
+
2502
+ // src/commands/show.ts
2503
+ function handleShowEnv() {
2504
+ const config = getEnvConfig();
2505
+ const entries = Object.entries(config);
2506
+ if (entries.length === 0) {
2507
+ logger.log("No env config.");
2508
+ return;
2509
+ }
2510
+ for (const [key, value] of entries) {
2511
+ logger.log(`${key}="${value}"`);
2512
+ }
2513
+ }
2514
+ function registerShowCommand(program2) {
2515
+ const show = program2.command("show");
2516
+ show.command("env").description("Display stored proxy environment variables").action(() => handleShowEnv());
2517
+ }
2518
+
2519
+ // src/utils/version.ts
2520
+ import { existsSync, readFileSync } from "fs";
2521
+ import { dirname, join } from "path";
2522
+ import { fileURLToPath } from "url";
2523
+ var PACKAGE_NAME = "@luxkit/cli";
2524
+ var cachedVersion;
2525
+ function getCurrentVersion() {
2526
+ if (cachedVersion) return cachedVersion;
2527
+ const __dirname = dirname(fileURLToPath(import.meta.url));
2528
+ const candidates = [
2529
+ join(__dirname, "..", "package.json"),
2530
+ join(__dirname, "..", "..", "package.json")
2531
+ ];
2532
+ const pkgPath = candidates.find((p) => existsSync(p));
2533
+ if (!pkgPath) {
2534
+ throw new Error("Cannot locate package.json for version reading");
2535
+ }
2536
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
2537
+ cachedVersion = pkg.version;
2538
+ return cachedVersion;
2539
+ }
2540
+
2541
+ // src/commands/update.ts
2542
+ var GLOBAL_UPDATE_CMDS = {
2543
+ npm: ["npm", ["install", "-g", `${PACKAGE_NAME}@latest`]],
2544
+ bun: ["bun", ["install", "-g", `${PACKAGE_NAME}@latest`]]
2545
+ };
2546
+ function detectGlobalPackageManager() {
2547
+ return process.execPath.toLowerCase().includes("bun") ? "bun" : "npm";
2548
+ }
2549
+ async function fetchLatestVersion() {
2550
+ const { stdout, exitCode } = await execFileNoThrow("npm", ["view", PACKAGE_NAME, "version"]);
2551
+ if (exitCode !== 0 || !stdout) {
2552
+ throw new Error(`Failed to fetch latest version from npm registry.`);
2553
+ }
2554
+ const lines = stdout.split("\n").filter((line) => line.trim().length > 0);
2555
+ return lines[lines.length - 1].trim();
2556
+ }
2557
+ async function performUpdate(pm) {
2558
+ const [command, args] = GLOBAL_UPDATE_CMDS[pm];
2559
+ const { exitCode, stderr } = await execFileNoThrow(command, args);
2560
+ if (exitCode !== 0) {
2561
+ const hint = /EACCES|permission/i.test(stderr) ? "\nTry running with elevated privileges (e.g. sudo)." : "";
2562
+ throw new Error(`Update failed: ${stderr}${hint}`);
2563
+ }
2564
+ }
2565
+ function registerUpdateCommand(program2) {
2566
+ program2.command("update").description("Update @luxkit/cli to the latest version").option("--check", "Only check for updates without installing").action(async (options) => {
2567
+ try {
2568
+ const current = getCurrentVersion();
2569
+ const latest = await fetchLatestVersion();
2570
+ if (current === latest) {
2571
+ logger.log(`Already up to date (v${current})`);
2572
+ return;
2573
+ }
2574
+ if (options.check) {
2575
+ logger.log(`Update available: v${current} \u2192 v${latest}`);
2576
+ return;
2577
+ }
2578
+ const pm = detectGlobalPackageManager();
2579
+ await performUpdate(pm);
2580
+ logger.log(`Updated to v${latest}`);
2581
+ } catch (err) {
2582
+ const message = err instanceof Error ? err.message : String(err);
2583
+ logger.error(message);
2584
+ logger.warn(`You can also update manually: npm install -g ${PACKAGE_NAME}@latest`);
2585
+ }
2586
+ });
2587
+ }
2588
+
2583
2589
  // src/generators/vscode.ts
2584
2590
  var STYLELINT_SETTINGS_PREFIXES2 = [
2585
2591
  "stylelint.",
@@ -0,0 +1,67 @@
1
+ # Custom Preset Configuration
2
+
3
+ > ⚠️ **Always use `lux init --preset` to initialize presets.** Running `lux fmt` / `lux vscode` directly generates config files in cwd and requires `package.json` — only use in real project directories.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ lux init --preset # materialize all built-in presets to ~/.lux/preset/
9
+ ```
10
+
11
+ After materialization, edit files under `~/.lux/preset/<type>/<preset-name>/` to customize. Changes take effect on the next `lux fmt` / `lux vscode` run.
12
+
13
+ `lux init --preset` can be run repeatedly — each run overwrites with built-in defaults. To reset a single preset, use `lux fmt <name> --reset` or `lux vscode <name> --reset` (deletes local preset dir only; re-generated from built-in on next run).
14
+
15
+ ## Storage Location
16
+
17
+ `~/.lux/preset/` (i.e. `os.homedir()/.lux/preset/`). Override with `LUX_HOME` env var.
18
+
19
+ ```
20
+ ~/.lux/preset/
21
+ ├── fmt/ # fmt presets (web-vue, web-react, electron-vue, uniapp, node, nest)
22
+ └── vscode/ # vscode presets (same + go)
23
+ ```
24
+
25
+ ## File Reference
26
+
27
+ ### fmt preset
28
+
29
+ | File | Description |
30
+ | :--------------------- | :--------------------------------------------- |
31
+ | `eslint.config.mjs` | ESLint flat config |
32
+ | `.prettierrc` | Prettier JSON config |
33
+ | `.prettierignore` | Prettier ignore rules |
34
+ | `stylelint.config.mjs` | Stylelint config |
35
+ | `.stylelintignore` | Stylelint ignore rules |
36
+ | `cspell.json` | CSpell dictionary config |
37
+ | `.editorconfig` | EditorConfig config |
38
+ | `package.json` | Template with `devDependencies` and `scripts` |
39
+
40
+ ### vscode preset
41
+
42
+ | File | Description |
43
+ | :---------------- | :-------------------------- |
44
+ | `settings.json` | VSCode workspace settings |
45
+ | `extensions.json` | VSCode extension recommendations |
46
+
47
+ ## Template Placeholders (fmt preset)
48
+
49
+ | Placeholder | Replaced with |
50
+ | :---------- | :------------ |
51
+ | `<pm>` | Package manager prefix (`bun run` / `pnpm run` / `npm run`) |
52
+ | `<lockfile>` | Project lockfile name (`bun.lock` / `pnpm-lock.yaml` etc.) |
53
+ | `"<latest>"` | Resolved to latest version at install time, or pin like `"3.3.0"` |
54
+
55
+ ## Workflow
56
+
57
+ ```
58
+ lux init --preset
59
+ → materialize all built-in presets to ~/.lux/preset/ (no cwd writes)
60
+
61
+ lux fmt web-vue (in target project)
62
+ → local preset exists → use local version (with custom edits)
63
+ → local preset missing → generate from built-in + materialize (also writes to cwd)
64
+
65
+ lux vscode web-vue (in target project)
66
+ → same as above
67
+ ```
@@ -13,6 +13,7 @@ lux fmt list
13
13
  - `--force` — overwrite existing config files (default: skip)
14
14
  - `--dry-run` — preview what would be generated, write nothing
15
15
  - `--no-install` — write deps to package.json but skip install
16
+ - `--reset` — reset local preset, re-materialize from built-in defaults
16
17
 
17
18
  Presets: `web-vue` `web-react` `electron-vue` `uniapp` `node` `nest`
18
19
 
@@ -23,12 +24,17 @@ lux vscode <preset> [--dry-run]
23
24
  lux vscode list
24
25
  ```
25
26
 
27
+ - `--force` — overwrite existing settings (default: skip)
28
+ - `--dry-run` — preview what would be generated, write nothing
29
+ - `--reset` — reset local preset, re-materialize from built-in defaults
30
+
26
31
  Presets: `web-vue` `web-react` `electron-vue` `uniapp` `node` `nest` `go`
27
32
 
28
- ## init — lux skill(human interaction)
33
+ ## init — initialize skills or presets
29
34
 
30
35
  ```bash
31
- lux init
36
+ lux init # interactively select AI tool, copy skill files to project
37
+ lux init --preset # materialize all built-in presets to ~/.lux/preset/ (no cwd writes, no package.json required)
32
38
  ```
33
39
 
34
40
  ## vpn — proxy clipboard helper
@@ -53,3 +59,7 @@ lux show env # show stored proxy env
53
59
  lux update # update to latest
54
60
  lux update --check
55
61
  ```
62
+
63
+ ## Custom presets
64
+
65
+ To customize preset rules, see `references/custom-preset-setting.md`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luxkit/cli",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "One-click project formatting & VSCode config CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,16 +15,9 @@
15
15
  "scripts": {
16
16
  "build": "tsup && node scripts/copy-assets.mjs",
17
17
  "dev": "tsup --watch",
18
- "lint": "eslint .",
18
+ "lint": "eslint . --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore \"src/**/*\" && tsc --noEmit",
19
+ "lint:fix": "eslint . --cache --cache-location node_modules/.cache/eslint --fix",
19
20
  "format": "prettier --write \"src/**/*.{ts,js,json}\"",
20
- "cspell": "cspell --gitignore \"src/**/*\"",
21
- "format:check": "prettier --check \"src/**/*.{ts,js,json}\"",
22
- "type:check": "tsc --noEmit",
23
- "code:check": "bun run lint && bun run format:check",
24
- "code:check:all": "bun run lint && bun run format:check && bun run cspell",
25
- "lint:fix": "eslint \"src/**/*.{js,ts}\" --fix",
26
- "code:fix": "bun run lint:fix && bun run format",
27
- "code:fix:all": "bun run lint:fix && bun run format",
28
21
  "prepublishOnly": "cross-env NODE_ENV=production bun run build",
29
22
  "test": "vitest run",
30
23
  "test:watch": "vitest",