lism-cli 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,7 +3,8 @@ import {
3
3
  DEFAULT_SKILL_REF,
4
4
  DEFAULT_UI_REF,
5
5
  RAW_GITHUB_BASE,
6
- SKILL_SOURCE_PATH,
6
+ SKILL_NAMES,
7
+ SKILL_SOURCE_BASE,
7
8
  SOURCE_REPO,
8
9
  UI_COMPONENTS_PATH,
9
10
  UI_HELPER_PATH,
@@ -13,13 +14,13 @@ import {
13
14
  preScanLang,
14
15
  setLang,
15
16
  t
16
- } from "./chunk-G4RBP3W6.js";
17
+ } from "./chunk-RTEZODLP.js";
17
18
 
18
19
  // src/createProgram.ts
19
- import { Command as Command3 } from "commander";
20
+ import { Command as Command4 } from "commander";
20
21
 
21
22
  // src/commands/ui/index.ts
22
- import { Command, Option } from "commander";
23
+ import { Command as Command2 } from "commander";
23
24
 
24
25
  // src/commands/ui/add.ts
25
26
  import fs3 from "fs";
@@ -30,209 +31,91 @@ import { confirm, select as select2 } from "@inquirer/prompts";
30
31
  import fs from "fs";
31
32
  import path from "path";
32
33
  import { createJiti } from "jiti";
33
-
34
- // src/invokeCommand.ts
35
- function getInvokeCommand() {
36
- const scriptPath = process.argv[1] ?? "";
37
- const userAgent = process.env.npm_config_user_agent ?? "";
38
- const normalizedScriptPath = normalizePath(scriptPath);
39
- const inDlxCache = /\/_npx\//.test(normalizedScriptPath) || /\/pnpm(?:-cache)?\/(?:[^/]+\/)*dlx-[^/]+\//.test(normalizedScriptPath) || /\/\.yarn\/berry\/cache\//.test(normalizedScriptPath) || /\/\.bun\/install\/cache\//.test(normalizedScriptPath);
40
- const inLocalDependency = isInProjectNodeModules(normalizedScriptPath) && (/\/node_modules\/\.bin\/lism(?:\.(?:cmd|ps1))?$/.test(normalizedScriptPath) || /\/node_modules\/lism-cli\/bin\/lism\.mjs$/.test(normalizedScriptPath));
41
- if (inDlxCache) return getDlxInvokeCommand(userAgent);
42
- if (inLocalDependency) return getLocalInvokeCommand(userAgent);
43
- return "lism";
44
- }
45
- function getDlxInvokeCommand(userAgent) {
46
- if (userAgent.startsWith("pnpm/")) return "pnpm dlx lism-cli";
47
- if (userAgent.startsWith("yarn/")) return "yarn dlx lism-cli";
48
- if (userAgent.startsWith("bun/")) return "bunx lism-cli";
49
- return "npx lism-cli";
50
- }
51
- function getLocalInvokeCommand(userAgent) {
52
- if (userAgent.startsWith("pnpm/")) return "pnpm exec lism";
53
- if (userAgent.startsWith("yarn/")) return "yarn lism";
54
- if (userAgent.startsWith("bun/")) return "bun run lism";
55
- return "npx lism-cli";
56
- }
57
- function isInProjectNodeModules(scriptPath) {
58
- let dir = normalizePath(process.cwd());
59
- while (dir) {
60
- if (scriptPath.startsWith(`${dir}/node_modules/`)) return true;
61
- const parent = dir.slice(0, dir.lastIndexOf("/"));
62
- if (parent === dir) break;
63
- dir = parent;
64
- }
65
- return false;
66
- }
67
- function normalizePath(value) {
68
- return value.replaceAll("\\", "/").replace(/\/+$/, "");
69
- }
70
-
71
- // src/config.ts
72
- var LEGACY_CONFIG_FILE = "lism-ui.json";
73
- var CONFIG_SEARCH = ["lism.config.js", "lism.config.mjs"];
34
+ var CONFIG_SEARCH = ["lism.config.ts", "lism.config.mjs", "lism.config.js"];
74
35
  function resolvePath(filename) {
75
36
  return path.resolve(process.cwd(), filename);
76
37
  }
77
38
  function findConfigFile() {
78
39
  for (const name of CONFIG_SEARCH) {
79
40
  const abs = resolvePath(name);
80
- if (fs.existsSync(abs)) return { path: abs, filename: name, kind: "module" };
41
+ if (fs.existsSync(abs)) return { path: abs, filename: name };
81
42
  }
82
- const legacy = resolvePath(LEGACY_CONFIG_FILE);
83
- if (fs.existsSync(legacy)) return { path: legacy, filename: LEGACY_CONFIG_FILE, kind: "legacy-json" };
84
43
  return null;
85
44
  }
86
- function configExists() {
87
- return findConfigFile() !== null;
88
- }
89
45
  var DEFAULT_CONFIG_FILENAME = "lism.config.js";
90
46
  function getDefaultConfigPath() {
91
47
  return resolvePath(DEFAULT_CONFIG_FILENAME);
92
48
  }
93
49
  async function readConfig() {
94
50
  const found = findConfigFile();
95
- if (!found) {
96
- throw new Error(t("config.notFound"));
97
- }
98
- if (found.kind === "legacy-json") {
99
- logger.warn(t("config.legacyWarning", { filename: LEGACY_CONFIG_FILE, invoke: getInvokeCommand() }));
100
- const raw = fs.readFileSync(found.path, "utf-8");
101
- const parsed = JSON.parse(raw);
102
- return parsed;
103
- }
51
+ if (!found) return null;
104
52
  const jiti = createJiti(import.meta.url, { interopDefault: true });
105
- const mod = await jiti.import(found.path);
106
- const cli = mod?.cli ?? mod;
107
- if (!cli || typeof cli !== "object") {
108
- throw new Error(t("config.cliSectionMissing", { filename: found.filename }));
53
+ let mod;
54
+ try {
55
+ mod = await jiti.import(found.path);
56
+ } catch (err) {
57
+ throw new Error(t("config.loadFailed", { path: found.path, reason: String(err) }));
58
+ }
59
+ const modObj = mod;
60
+ const hasUiKey = modObj?.ui !== void 0;
61
+ const hasCliKey = modObj?.cli !== void 0;
62
+ if (hasUiKey || hasCliKey) {
63
+ if (!hasUiKey && hasCliKey) {
64
+ logger.warn(t("config.cliKeyDeprecated", { filename: found.filename }));
65
+ }
66
+ return normalizeUiConfig(hasUiKey ? modObj.ui : modObj.cli);
67
+ }
68
+ try {
69
+ return normalizeUiConfig(modObj);
70
+ } catch {
71
+ return null;
109
72
  }
110
- validateCliConfig(cli);
111
- return cli;
112
73
  }
113
- function validateCliConfig(cli) {
114
- const c = cli;
74
+ function normalizeUiConfig(raw) {
75
+ const c = raw ?? {};
115
76
  if (c.framework !== "react" && c.framework !== "astro") {
116
77
  throw new Error(t("config.invalidFramework"));
117
78
  }
118
- if (typeof c.componentsDir !== "string" || !c.componentsDir) {
119
- throw new Error(t("config.invalidComponentsDir"));
120
- }
121
- if (typeof c.helperDir !== "string" || !c.helperDir) {
122
- throw new Error(t("config.invalidHelperDir"));
79
+ const dir = c.dir !== void 0 ? c.dir : c.componentsDir;
80
+ if (typeof dir !== "string" || !dir) {
81
+ throw new Error(t("config.invalidDir"));
123
82
  }
83
+ return { framework: c.framework, dir };
124
84
  }
125
- function writeFreshConfig(cli) {
85
+ function writeFreshConfig(ui) {
126
86
  const filePath = getDefaultConfigPath();
127
- const body = renderConfigTemplate(cli);
87
+ if (fs.existsSync(filePath)) {
88
+ throw new Error(t("config.freshConfigExists", { path: filePath }));
89
+ }
90
+ const body = renderConfigTemplate(ui);
128
91
  fs.writeFileSync(filePath, body);
129
92
  return filePath;
130
93
  }
131
- async function hasCliSection(filePath) {
132
- try {
133
- const jiti = createJiti(import.meta.url, { interopDefault: true });
134
- const mod = await jiti.import(filePath);
135
- return !!mod?.cli;
136
- } catch (err) {
137
- throw new Error(t("config.loadFailed", { path: filePath, reason: String(err) }));
138
- }
139
- }
140
- async function patchConfigWithCli(cli, targetPath, options = {}) {
141
- const filePath = targetPath ?? getDefaultConfigPath();
142
- let source = fs.readFileSync(filePath, "utf-8");
143
- const hasExisting = options.existingCli ?? await hasCliSection(filePath);
144
- if (hasExisting) {
145
- if (!options.force) {
146
- return { path: filePath, patched: false };
147
- }
148
- const removed = removeCliSection(source);
149
- if (removed === null) {
150
- return { path: filePath, patched: false };
151
- }
152
- source = removed;
153
- }
154
- const insertAt = findInsertPosition(source);
155
- if (insertAt === -1) {
156
- return { path: filePath, patched: false };
157
- }
158
- const insertion = `
159
- cli: ${renderCliObject(cli, " ")},`;
160
- const updated = source.slice(0, insertAt) + insertion + source.slice(insertAt);
161
- fs.writeFileSync(filePath, updated);
162
- return { path: filePath, patched: true };
163
- }
164
- function removeCliSection(source) {
165
- const match = source.match(/(^|[\n,{])(\s*)cli\s*:\s*\{/);
166
- if (!match) return null;
167
- const sectionStart = match.index + match[1].length + match[2].length;
168
- const openBrace = match.index + match[0].length - 1;
169
- let i = openBrace + 1;
170
- let depth = 1;
171
- while (i < source.length && depth > 0) {
172
- const c = source[i];
173
- if (c === '"' || c === "'" || c === "`") {
174
- const quote = c;
175
- i++;
176
- while (i < source.length && source[i] !== quote) {
177
- if (source[i] === "\\") i++;
178
- i++;
179
- }
180
- i++;
181
- continue;
182
- }
183
- if (c === "/" && source[i + 1] === "/") {
184
- while (i < source.length && source[i] !== "\n") i++;
185
- continue;
186
- }
187
- if (c === "/" && source[i + 1] === "*") {
188
- i += 2;
189
- while (i < source.length - 1 && !(source[i] === "*" && source[i + 1] === "/")) i++;
190
- i += 2;
191
- continue;
192
- }
193
- if (c === "{") depth++;
194
- else if (c === "}") depth--;
195
- i++;
196
- }
197
- if (depth !== 0) return null;
198
- let end = i;
199
- if (source[end] === ",") end++;
200
- const trailing = source.slice(end).match(/^[ \t]*\n/);
201
- if (trailing) end += trailing[0].length;
202
- return source.slice(0, sectionStart) + source.slice(end);
203
- }
204
- function findInsertPosition(source) {
205
- const inline = source.match(/export default\s*(?:[A-Za-z_$][A-Za-z0-9_$]*\s*)?\(?\s*\{/);
206
- if (inline) {
207
- return inline.index + inline[0].length;
208
- }
209
- const varExport = source.match(/export default\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*;?/);
210
- if (varExport) {
211
- const varName = varExport[1];
212
- const escaped = varName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
213
- const decl = source.match(new RegExp(`(?:const|let|var)\\s+${escaped}\\s*=\\s*(?:[A-Za-z_$][A-Za-z0-9_$]*\\s*)?\\(?\\s*\\{`));
214
- if (decl) {
215
- return decl.index + decl[0].length;
216
- }
217
- }
218
- return -1;
219
- }
220
- function renderConfigTemplate(cli) {
221
- return [
94
+ function renderConfigTemplate(ui) {
95
+ const lines = [
96
+ // `.js` でもエディタ補完・typo 検出が効くように LismConfig 型(#449)を JSDoc で付与する
97
+ "/** @type {import('lism-css/config-types').LismConfig} */",
222
98
  "export default {",
223
- //
224
- ` cli: ${renderCliObject(cli, " ")},`,
225
- "};",
226
- ""
227
- ].join("\n");
99
+ " // tokens: {},",
100
+ " // props: {},",
101
+ " // traits: {},",
102
+ " // breakpoints: {},"
103
+ ];
104
+ if (ui) {
105
+ lines.push(` ui: ${renderCliObject(ui, " ")},`);
106
+ }
107
+ lines.push("};", "");
108
+ return lines.join("\n");
109
+ }
110
+ function renderUiSnippet(cli) {
111
+ return `ui: ${renderCliObject(cli, "")},`;
228
112
  }
229
113
  function renderCliObject(cli, indent) {
230
114
  return [
231
115
  "{",
232
116
  //
233
117
  `${indent} framework: ${JSON.stringify(cli.framework)},`,
234
- `${indent} componentsDir: ${JSON.stringify(cli.componentsDir)},`,
235
- `${indent} helperDir: ${JSON.stringify(cli.helperDir)},`,
118
+ `${indent} dir: ${JSON.stringify(cli.dir)},`,
236
119
  `${indent}}`
237
120
  ].join("\n");
238
121
  }
@@ -322,10 +205,11 @@ function isExcludedComponentFile(rel, excludeRootFiles) {
322
205
  if (/(^|\/)[^/]+\.test\.[a-z]+$/.test(rel)) return true;
323
206
  return false;
324
207
  }
208
+ var FETCH_TIMEOUT_MS = 5e3;
325
209
  async function fetchCatalog(options = {}) {
326
210
  const ref = options.ref ?? DEFAULT_UI_REF;
327
211
  const url = `${RAW_GITHUB_BASE}/${SOURCE_REPO}/${ref}/${UI_REGISTRY_INDEX_PATH}`;
328
- const res = await fetch(url);
212
+ const res = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
329
213
  if (!res.ok) {
330
214
  throw new Error(`Failed to fetch registry-index.json (${res.status} ${res.statusText}): ${url}`);
331
215
  }
@@ -399,63 +283,18 @@ async function fetchHelper(name, options = {}) {
399
283
  return { name, files };
400
284
  }
401
285
 
402
- // src/commands/ui/init.ts
403
- import { select, input } from "@inquirer/prompts";
404
- async function runInit(options = {}) {
286
+ // src/commands/ui/promptUiConfig.ts
287
+ import { select } from "@inquirer/prompts";
288
+ async function promptUiConfig(options = {}) {
405
289
  const framework = options.framework ?? await select({
406
- message: t("ui.init.promptFramework"),
290
+ message: t("ui.promptFramework"),
407
291
  choices: [
408
292
  { name: "React", value: "react" },
409
293
  { name: "Astro", value: "astro" }
410
294
  ]
411
295
  });
412
- const componentsDir = options.componentsDir ?? await input({
413
- message: t("ui.init.promptComponentsDir"),
414
- default: "src/components/ui"
415
- });
416
- const helperDir = options.helperDir ?? await input({
417
- message: t("ui.init.promptHelperDir"),
418
- default: `${componentsDir}/_helper`
419
- });
420
- const config = { framework, componentsDir, helperDir };
421
- const found = findConfigFile();
422
- if (found?.kind === "module") {
423
- const { patched, path: outPath } = await patchConfigWithCli(config, found.path, {
424
- force: options.force,
425
- existingCli: options.existingCli
426
- });
427
- if (patched) {
428
- logger.success(t(options.force ? "ui.init.patchedUpdate" : "ui.init.patchedAdd", { path: outPath }));
429
- } else {
430
- logger.warn(t("ui.init.notPatched", { path: outPath }));
431
- }
432
- } else {
433
- const outPath = writeFreshConfig(config);
434
- logger.success(t("ui.init.created", { path: outPath }));
435
- }
436
- return config;
437
- }
438
- async function initCommand(options) {
439
- const found = findConfigFile();
440
- let existingCli = false;
441
- if (found?.kind === "legacy-json") {
442
- logger.warn(t("ui.init.legacyDetected", { filename: found.filename }));
443
- } else if (found?.kind === "module") {
444
- try {
445
- existingCli = await hasCliSection(found.path);
446
- } catch (err) {
447
- logger.error(err instanceof Error ? err.message : String(err));
448
- process.exit(1);
449
- }
450
- if (existingCli) {
451
- if (!options.force) {
452
- logger.warn(t("ui.init.alreadyExists", { filename: found.filename }));
453
- return;
454
- }
455
- logger.warn(t("ui.init.willOverwrite", { filename: found.filename }));
456
- }
457
- }
458
- await runInit({ ...options, existingCli });
296
+ const dir = options.dir ?? "src/components/ui";
297
+ return { framework, dir };
459
298
  }
460
299
 
461
300
  // src/commands/ui/normalize.ts
@@ -463,12 +302,12 @@ var normalizeComponentName = (s) => s.replace(/[-_]/g, "").toLowerCase();
463
302
 
464
303
  // src/commands/ui/add.ts
465
304
  async function addCommand(names, options) {
466
- let config;
467
- if (configExists()) {
468
- config = await readConfig();
469
- } else {
305
+ let config = await readConfig();
306
+ let needsGuidance = false;
307
+ if (!config) {
308
+ needsGuidance = true;
470
309
  logger.info(t("ui.add.noConfig"));
471
- config = await runInit();
310
+ config = await promptUiConfig({ framework: options.uiFramework, dir: options.uiDir });
472
311
  console.log();
473
312
  }
474
313
  const fetchOpts = { ref: options.ref };
@@ -491,11 +330,11 @@ async function addCommand(names, options) {
491
330
  }
492
331
  const resolvedNames = [];
493
332
  const notFound = [];
494
- for (const input2 of names) {
495
- const normalized = normalizeComponentName(input2);
333
+ for (const input of names) {
334
+ const normalized = normalizeComponentName(input);
496
335
  const match = catalog.components.find((c) => normalizeComponentName(c.name) === normalized);
497
336
  if (match) resolvedNames.push(match.name);
498
- else notFound.push(input2);
337
+ else notFound.push(input);
499
338
  }
500
339
  if (notFound.length > 0) {
501
340
  logger.error(t("ui.add.notFound", { list: notFound.join(", ") }));
@@ -520,6 +359,10 @@ async function addCommand(names, options) {
520
359
  const helperFailed = await writeComponent(result.value, config, overwriteAll, overwritePolicy, installedHelpers, fetchOpts);
521
360
  if (helperFailed) hasFailure = true;
522
361
  }
362
+ if (needsGuidance) {
363
+ const filename = findConfigFile()?.filename ?? DEFAULT_CONFIG_FILENAME;
364
+ logger.info(t("ui.add.snippetGuide", { filename, snippet: renderUiSnippet(config) }));
365
+ }
523
366
  if (hasFailure) {
524
367
  logger.error(t("ui.add.someFailed"));
525
368
  process.exit(1);
@@ -548,8 +391,8 @@ async function writeComponent(component, config, overwriteAll, policy, installed
548
391
  logger.info(t("ui.add.deploying", { name: component.name }));
549
392
  const filesToWrite = [...component.files.shared, ...component.files[config.framework]];
550
393
  const componentDirName = component.name;
551
- const componentDir = path4.resolve(process.cwd(), config.componentsDir, componentDirName);
552
- const helperDir = path4.resolve(process.cwd(), config.helperDir);
394
+ const componentDir = path4.resolve(process.cwd(), config.dir, componentDirName);
395
+ const helperDir = path4.resolve(process.cwd(), config.dir, "_helper");
553
396
  let shouldWrite;
554
397
  const hasExisting = hasExistingFiles(filesToWrite, componentDir);
555
398
  if (overwriteAll) {
@@ -637,17 +480,23 @@ Lism UI v${catalog.version}
637
480
  ${t("ui.list.total", { count: catalog.components.length })}`);
638
481
  }
639
482
 
483
+ // src/commands/ui/uiSectionOptions.ts
484
+ import { Option } from "commander";
485
+ function applyUiSectionOptions(command) {
486
+ return command.addOption(new Option("--ui-framework <name>", t("cli.init.opt.uiFramework")).choices(["react", "astro"])).option("--ui-dir <path>", t("cli.init.opt.uiDir"));
487
+ }
488
+
640
489
  // src/commands/ui/index.ts
641
490
  function createUiCommand() {
642
- const ui = new Command("ui").description(t("cli.ui.description"));
643
- ui.command("init").description(t("cli.ui.init.description")).addOption(new Option("--framework <name>", t("cli.ui.init.opt.framework")).choices(["react", "astro"])).option("--components-dir <path>", t("cli.ui.init.opt.componentsDir")).option("--helper-dir <path>", t("cli.ui.init.opt.helperDir")).option("-f, --force", t("cli.ui.init.opt.force"), false).action(initCommand);
644
- ui.command("add").description(t("cli.ui.add.description")).argument("[names...]", t("cli.ui.add.arg.names")).option("-o, --overwrite", t("cli.ui.add.opt.overwrite"), false).option("-a, --all", t("cli.ui.add.opt.all"), false).option("--ref <ref>", t("cli.ui.opt.ref")).action(addCommand);
491
+ const ui = new Command2("ui").description(t("cli.ui.description"));
492
+ const add = ui.command("add").description(t("cli.ui.add.description")).argument("[names...]", t("cli.ui.add.arg.names")).option("-o, --overwrite", t("cli.ui.add.opt.overwrite"), false).option("-a, --all", t("cli.ui.add.opt.all"), false);
493
+ applyUiSectionOptions(add).option("--ref <ref>", t("cli.ui.opt.ref")).action(addCommand);
645
494
  ui.command("list").description(t("cli.ui.list.description")).option("--ref <ref>", t("cli.ui.opt.ref")).action(listCommand);
646
495
  return ui;
647
496
  }
648
497
 
649
498
  // src/commands/skill/index.ts
650
- import { Command as Command2 } from "commander";
499
+ import { Command as Command3 } from "commander";
651
500
 
652
501
  // src/commands/skill/add.ts
653
502
  import fs5 from "fs";
@@ -656,15 +505,18 @@ import { checkbox, confirm as confirm2 } from "@inquirer/prompts";
656
505
 
657
506
  // src/commands/skill/paths.ts
658
507
  var SKILL_PATHS = {
659
- claude: ".claude/skills/lism-css-guide",
660
- codex: ".agents/skills/lism-css-guide",
661
- cursor: ".cursor/skills/lism-css-guide",
662
- windsurf: ".windsurf/skills/lism-css-guide",
663
- cline: ".cline/skills/lism-css-guide",
664
- copilot: ".github/skills/lism-css-guide",
665
- gemini: ".gemini/skills/lism-css-guide",
666
- junie: ".junie/skills/lism-css-guide"
508
+ claude: ".claude/skills",
509
+ codex: ".agents/skills",
510
+ cursor: ".cursor/skills",
511
+ windsurf: ".windsurf/skills",
512
+ cline: ".cline/skills",
513
+ copilot: ".github/skills",
514
+ gemini: ".gemini/skills",
515
+ junie: ".junie/skills"
667
516
  };
517
+ function skillDestDir(tool, skill) {
518
+ return `${SKILL_PATHS[tool]}/${skill}`;
519
+ }
668
520
  var ALL_SKILL_TOOLS = Object.keys(SKILL_PATHS);
669
521
  var TOOL_MARKERS = {
670
522
  claude: [".claude"],
@@ -683,9 +535,9 @@ import fs4 from "fs";
683
535
  import os2 from "os";
684
536
  import path5 from "path";
685
537
  import { downloadTemplate as downloadTemplate2 } from "giget";
686
- async function fetchSkillSource(ref = DEFAULT_SKILL_REF) {
538
+ async function fetchSkillSource(skillName, ref = DEFAULT_SKILL_REF) {
687
539
  const tmpDir = fs4.mkdtempSync(path5.join(os2.tmpdir(), "lism-skill-"));
688
- await downloadTemplate2(`github:${SOURCE_REPO}/${SKILL_SOURCE_PATH}#${ref}`, {
540
+ await downloadTemplate2(`github:${SOURCE_REPO}/${SKILL_SOURCE_BASE}/${skillName}#${ref}`, {
689
541
  dir: tmpDir,
690
542
  force: true,
691
543
  forceClean: true
@@ -765,8 +617,17 @@ function resolveExplicitTools(options) {
765
617
  function autoDetectTools(cwd) {
766
618
  return ALL_SKILL_TOOLS.filter((tool) => TOOL_MARKERS[tool].some((marker) => fs5.existsSync(path6.resolve(cwd, marker))));
767
619
  }
768
- async function skillAddCommand(options) {
620
+ function resolveSkills(skillArg) {
621
+ if (skillArg === void 0) return [...SKILL_NAMES];
622
+ return SKILL_NAMES.includes(skillArg) ? [skillArg] : null;
623
+ }
624
+ async function skillAddCommand(skillArg, options) {
769
625
  const cwd = process.cwd();
626
+ const skills = resolveSkills(skillArg);
627
+ if (skills === null) {
628
+ logger.error(t("skill.unknownSkill", { name: skillArg, list: SKILL_NAMES.join(", ") }));
629
+ process.exit(1);
630
+ }
770
631
  let targets = resolveExplicitTools(options);
771
632
  if (targets.length === 0) {
772
633
  const detected = autoDetectTools(cwd);
@@ -787,23 +648,26 @@ async function skillAddCommand(options) {
787
648
  return;
788
649
  }
789
650
  const ref = options.ref ?? DEFAULT_SKILL_REF;
790
- logger.info(t("skill.add.fetching", { ref }));
791
- const { dir: srcDir } = await fetchSkillSource(ref);
792
- try {
793
- for (const tool of targets) {
794
- await deploySkillTo(srcDir, tool, options);
651
+ for (const skill of skills) {
652
+ logger.info(t("skill.add.fetching", { skill, ref }));
653
+ const { dir: srcDir } = await fetchSkillSource(skill, ref);
654
+ try {
655
+ for (const tool of targets) {
656
+ await deploySkillTo(srcDir, skill, tool, options);
657
+ }
658
+ } finally {
659
+ cleanupTempDir(srcDir);
795
660
  }
796
- logger.success(t("common.done"));
797
- } finally {
798
- cleanupTempDir(srcDir);
799
661
  }
662
+ logger.success(t("common.done"));
800
663
  }
801
- async function deploySkillTo(srcDir, tool, options) {
802
- const destDir = path6.resolve(process.cwd(), SKILL_PATHS[tool]);
664
+ async function deploySkillTo(srcDir, skill, tool, options) {
665
+ const relDest = skillDestDir(tool, skill);
666
+ const destDir = path6.resolve(process.cwd(), relDest);
803
667
  const existing = fs5.existsSync(destDir);
804
668
  if (existing && !options.overwrite) {
805
669
  const diff = compareSkillDirs(destDir, srcDir);
806
- const label = `${tool} (${SKILL_PATHS[tool]})`;
670
+ const label = `${tool} (${relDest})`;
807
671
  if (!hasDiff(diff)) {
808
672
  logger.log(t("skill.add.skippedSame", { label }));
809
673
  return;
@@ -817,7 +681,7 @@ async function deploySkillTo(srcDir, tool, options) {
817
681
  for (const rel of diff.localOnly) logger.log(` - ${rel}`);
818
682
  }
819
683
  const go = await confirm2({
820
- message: t("skill.add.confirmOverwrite", { path: SKILL_PATHS[tool] }),
684
+ message: t("skill.add.confirmOverwrite", { path: relDest }),
821
685
  default: false
822
686
  });
823
687
  if (!go) {
@@ -833,39 +697,90 @@ async function deploySkillTo(srcDir, tool, options) {
833
697
  // src/commands/skill/check.ts
834
698
  import fs6 from "fs";
835
699
  import path7 from "path";
700
+
701
+ // src/invokeCommand.ts
702
+ function getInvokeCommand() {
703
+ const scriptPath = process.argv[1] ?? "";
704
+ const userAgent = process.env.npm_config_user_agent ?? "";
705
+ const normalizedScriptPath = normalizePath(scriptPath);
706
+ const inDlxCache = /\/_npx\//.test(normalizedScriptPath) || /\/pnpm(?:-cache)?\/(?:[^/]+\/)*dlx-[^/]+\//.test(normalizedScriptPath) || /\/\.yarn\/berry\/cache\//.test(normalizedScriptPath) || /\/\.bun\/install\/cache\//.test(normalizedScriptPath);
707
+ const inLocalDependency = isInProjectNodeModules(normalizedScriptPath) && (/\/node_modules\/\.bin\/lism-cli(?:\.(?:cmd|ps1))?$/.test(normalizedScriptPath) || /\/node_modules\/lism-cli\/bin\/lism-cli\.mjs$/.test(normalizedScriptPath));
708
+ if (inDlxCache) return getDlxInvokeCommand(userAgent);
709
+ if (inLocalDependency) return getLocalInvokeCommand(userAgent);
710
+ return "lism-cli";
711
+ }
712
+ function getDlxInvokeCommand(userAgent) {
713
+ if (userAgent.startsWith("pnpm/")) return "pnpm dlx lism-cli";
714
+ if (userAgent.startsWith("yarn/")) return "yarn dlx lism-cli";
715
+ if (userAgent.startsWith("bun/")) return "bunx lism-cli";
716
+ return "npx lism-cli";
717
+ }
718
+ function getLocalInvokeCommand(userAgent) {
719
+ if (userAgent.startsWith("pnpm/")) return "pnpm exec lism-cli";
720
+ if (userAgent.startsWith("yarn/")) return "yarn lism-cli";
721
+ if (userAgent.startsWith("bun/")) return "bun run lism-cli";
722
+ return "npx lism-cli";
723
+ }
724
+ function isInProjectNodeModules(scriptPath) {
725
+ let dir = normalizePath(process.cwd());
726
+ while (dir) {
727
+ if (scriptPath.startsWith(`${dir}/node_modules/`)) return true;
728
+ const parent = dir.slice(0, dir.lastIndexOf("/"));
729
+ if (parent === dir) break;
730
+ dir = parent;
731
+ }
732
+ return false;
733
+ }
734
+ function normalizePath(value) {
735
+ return value.replaceAll("\\", "/").replace(/\/+$/, "");
736
+ }
737
+
738
+ // src/commands/skill/check.ts
836
739
  async function skillCheckCommand(options = {}) {
837
740
  const cwd = process.cwd();
838
- const installed = ALL_SKILL_TOOLS.filter((tool) => fs6.existsSync(path7.join(cwd, SKILL_PATHS[tool], "SKILL.md")));
741
+ const installed = [];
742
+ for (const skill of SKILL_NAMES) {
743
+ for (const tool of ALL_SKILL_TOOLS) {
744
+ if (fs6.existsSync(path7.join(cwd, skillDestDir(tool, skill), "SKILL.md"))) {
745
+ installed.push({ skill, tool });
746
+ }
747
+ }
748
+ }
839
749
  if (installed.length === 0) {
840
750
  logger.info(t("skill.check.noneInstalled", { invoke: getInvokeCommand() }));
841
751
  return;
842
752
  }
843
753
  const ref = options.ref ?? DEFAULT_SKILL_REF;
844
- logger.info(t("skill.check.fetching", { ref }));
845
- const { dir: remoteDir } = await fetchSkillSource(ref);
846
- try {
847
- let outdatedCount = 0;
848
- for (const tool of installed) {
849
- const localDir = path7.resolve(cwd, SKILL_PATHS[tool]);
850
- const diff = compareSkillDirs(localDir, remoteDir);
851
- const label = `${tool.padEnd(9)} ${SKILL_PATHS[tool]}`;
852
- if (!hasDiff(diff)) {
853
- logger.log(t("skill.check.upToDate", { label }));
854
- continue;
754
+ let outdatedCount = 0;
755
+ for (const skill of SKILL_NAMES) {
756
+ const pairs = installed.filter((p) => p.skill === skill);
757
+ if (pairs.length === 0) continue;
758
+ logger.info(t("skill.check.fetching", { skill, ref }));
759
+ const { dir: remoteDir } = await fetchSkillSource(skill, ref);
760
+ try {
761
+ for (const { tool } of pairs) {
762
+ const relDest = skillDestDir(tool, skill);
763
+ const localDir = path7.resolve(cwd, relDest);
764
+ const diff = compareSkillDirs(localDir, remoteDir);
765
+ const label = `${tool.padEnd(9)} ${relDest}`;
766
+ if (!hasDiff(diff)) {
767
+ logger.log(t("skill.check.upToDate", { label }));
768
+ continue;
769
+ }
770
+ outdatedCount += 1;
771
+ logger.warn(` ! ${label}`);
772
+ logger.log(formatDiffSummary(diff));
773
+ if (options.verbose) logger.log(formatDiffDetails(diff));
855
774
  }
856
- outdatedCount += 1;
857
- logger.warn(` ! ${label}`);
858
- logger.log(formatDiffSummary(diff));
859
- if (options.verbose) logger.log(formatDiffDetails(diff));
860
- }
861
- logger.log("");
862
- if (outdatedCount === 0) {
863
- logger.success(t("skill.check.allLatest"));
864
- } else {
865
- logger.info(t("skill.check.outdated", { count: outdatedCount, invoke: getInvokeCommand() }));
775
+ } finally {
776
+ cleanupTempDir(remoteDir);
866
777
  }
867
- } finally {
868
- cleanupTempDir(remoteDir);
778
+ }
779
+ logger.log("");
780
+ if (outdatedCount === 0) {
781
+ logger.success(t("skill.check.allLatest"));
782
+ } else {
783
+ logger.info(t("skill.check.outdated", { count: outdatedCount, invoke: getInvokeCommand() }));
869
784
  }
870
785
  }
871
786
  function formatDiffSummary(diff) {
@@ -885,16 +800,16 @@ function formatDiffDetails(diff) {
885
800
 
886
801
  // src/commands/skill/update.ts
887
802
  async function skillUpdateCommand(options) {
888
- await skillAddCommand({ ...options, overwrite: true });
803
+ await skillAddCommand(void 0, { ...options, overwrite: true });
889
804
  }
890
805
 
891
806
  // src/commands/skill/index.ts
892
807
  function createSkillCommand() {
893
- const skill = new Command2("skill").description(t("cli.skill.description"));
808
+ const skill = new Command3("skill").description(t("cli.skill.description"));
894
809
  const toolOptDescription = (tool) => t("cli.skill.opt.toolPath", { path: SKILL_PATHS[tool] });
895
810
  const toolFlags = (cmd) => cmd.option("--all", t("cli.skill.opt.all")).option("--claude", toolOptDescription("claude")).option("--codex", toolOptDescription("codex")).option("--cursor", toolOptDescription("cursor")).option("--windsurf", toolOptDescription("windsurf")).option("--cline", toolOptDescription("cline")).option("--copilot", toolOptDescription("copilot")).option("--gemini", toolOptDescription("gemini")).option("--junie", toolOptDescription("junie"));
896
811
  toolFlags(
897
- skill.command("add").description(t("cli.skill.add.description")).option("-o, --overwrite", t("cli.skill.add.opt.overwrite"), false).option("--ref <ref>", t("cli.skill.opt.ref"))
812
+ skill.command("add").description(t("cli.skill.add.description")).argument("[skill]", t("cli.skill.add.arg.skill")).option("-o, --overwrite", t("cli.skill.add.opt.overwrite"), false).option("--ref <ref>", t("cli.skill.opt.ref"))
898
813
  ).action(skillAddCommand);
899
814
  skill.command("check").description(t("cli.skill.check.description")).option("--ref <ref>", t("cli.skill.opt.ref")).option("-v, --verbose", t("cli.skill.check.opt.verbose")).action(skillCheckCommand);
900
815
  toolFlags(skill.command("update").description(t("cli.skill.update.description")).option("--ref <ref>", t("cli.skill.opt.ref"))).action(
@@ -903,16 +818,53 @@ function createSkillCommand() {
903
818
  return skill;
904
819
  }
905
820
 
821
+ // src/commands/init.ts
822
+ import { confirm as confirm3 } from "@inquirer/prompts";
823
+ async function initCommand(options = {}) {
824
+ const found = findConfigFile();
825
+ if (found) {
826
+ logger.warn(t("init.alreadyExists", { filename: found.filename }));
827
+ return;
828
+ }
829
+ const ui = await resolveUiConfig(options);
830
+ const outPath = writeFreshConfig(ui);
831
+ logger.success(t("init.created", { path: outPath }));
832
+ }
833
+ async function resolveUiConfig(options) {
834
+ const hasFramework = options.uiFramework !== void 0;
835
+ const hasDir = options.uiDir !== void 0;
836
+ const uiOptions = { framework: options.uiFramework, dir: options.uiDir };
837
+ if (!process.stdin.isTTY) {
838
+ if (hasFramework) return promptUiConfig(uiOptions);
839
+ if (hasDir) {
840
+ logger.error(t("init.uiFrameworkRequired"));
841
+ process.exit(1);
842
+ }
843
+ return null;
844
+ }
845
+ if (hasFramework) {
846
+ const useUi2 = await confirm3({ message: t("init.promptUseUi"), default: true });
847
+ return useUi2 ? promptUiConfig(uiOptions) : null;
848
+ }
849
+ if (hasDir) {
850
+ return promptUiConfig(uiOptions);
851
+ }
852
+ const useUi = await confirm3({ message: t("init.promptUseUi"), default: false });
853
+ return useUi ? promptUiConfig(uiOptions) : null;
854
+ }
855
+
906
856
  // src/createProgram.ts
907
857
  function createLismProgram() {
908
- const program2 = new Command3();
909
- program2.name("lism").description(t("cli.description")).version(CLI_VERSION).option("--lang <code>", t("cli.opt.lang"));
858
+ const program2 = new Command4();
859
+ program2.name("lism-cli").description(t("cli.description")).version(CLI_VERSION).option("--lang <code>", t("cli.opt.lang"));
910
860
  program2.hook("preAction", (thisCommand) => {
911
861
  const opts = thisCommand.optsWithGlobals();
912
862
  const lang = opts.lang;
913
863
  if (typeof lang === "string") setLang(lang);
914
864
  });
915
865
  program2.command("create").description(t("cli.create.description")).argument("[targetDir]", t("cli.create.arg.targetDir")).option("-t, --template <name>", t("cli.create.opt.template")).option("-f, --force", t("cli.create.opt.force"), false).action(createCommand);
866
+ const init = program2.command("init").description(t("cli.init.description"));
867
+ applyUiSectionOptions(init).action(initCommand);
916
868
  program2.addCommand(createUiCommand());
917
869
  program2.addCommand(createSkillCommand());
918
870
  return program2;
@@ -921,4 +873,8 @@ function createLismProgram() {
921
873
  // src/index.ts
922
874
  preScanLang(process.argv.slice(2));
923
875
  var program = createLismProgram();
924
- program.parse();
876
+ program.parseAsync().catch((err) => {
877
+ if (err instanceof Error && err.name === "ExitPromptError") process.exit(130);
878
+ logger.error(err instanceof Error ? err.message : String(err));
879
+ process.exit(1);
880
+ });