lism-cli 0.0.1

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 ADDED
@@ -0,0 +1,124 @@
1
+ # @lism-css/cli
2
+
3
+ [Lism CSS](https://lism-css.com) / [Lism UI](https://lism-css.com) のための CLI ツール。`lism` コマンドで新規プロジェクト生成・UI コンポーネント追加・AI スキル配置を行います。
4
+
5
+ ## 前提条件
6
+
7
+ - Node.js 18 以上
8
+
9
+ ## コマンド体系
10
+
11
+ ```
12
+ lism create [targetDir] [--template <name>] # examples から新規プロジェクト
13
+ lism ui { init | add <names...> | list } # Lism UI コンポーネントの追加
14
+ lism skill { add | check | update } # AI エージェント向け SKILL.md 配置
15
+ ```
16
+
17
+ ## 使い方
18
+
19
+ ### プロジェクトを新規作成
20
+
21
+ ```bash
22
+ # 対話モード
23
+ pnpm dlx @lism-css/cli create
24
+
25
+ # テンプレート名・出力先を指定
26
+ pnpm dlx @lism-css/cli create --template astro-minimal ./my-app
27
+ ```
28
+
29
+ 同じ動作は `pnpm create lism` / `npm create lism@latest` でも呼び出せます(`create-lism` パッケージ経由)。
30
+
31
+ ### UI コンポーネントの追加
32
+
33
+ ```bash
34
+ # 単一 / 複数
35
+ pnpm dlx @lism-css/cli ui add accordion
36
+ pnpm dlx @lism-css/cli ui add accordion modal tabs
37
+
38
+ # 全コンポーネント
39
+ pnpm dlx @lism-css/cli ui add --all
40
+
41
+ # 一覧
42
+ pnpm dlx @lism-css/cli ui list
43
+
44
+ # 特定の ref(ブランチ / タグ / コミット)から取得
45
+ pnpm dlx @lism-css/cli ui add accordion --ref dev
46
+ ```
47
+
48
+ コンポーネントは [`packages/lism-ui/src/components`](https://github.com/lism-css/lism-css/tree/main/packages/lism-ui/src/components) から [giget](https://github.com/unjs/giget) 経由で直接取得されます。`lism-ui` を更新するだけで CLI 側も自動で追従します。
49
+
50
+ 初回実行時に `lism.config.js` が無い場合は対話式セットアップが走り、`cli` セクションを書き込みます。
51
+
52
+ ```
53
+ ? フレームワークを選択してください: React
54
+ ? コンポーネントの出力先ディレクトリ: src/components/ui
55
+ ? helper の出力先ディレクトリ: src/components/ui/_helper
56
+ ✔ lism.config.js を作成しました。
57
+ ```
58
+
59
+ `ui init` で設定の生成のみ行うこともできます。既存の `lism.config.js` には `cli` セクションのみ追記されます。
60
+
61
+ ### AI エージェント向けスキルの配置
62
+
63
+ `SKILL.md` を各種 AI ツールの所定ディレクトリへ展開します。
64
+
65
+ ```bash
66
+ # 対話モード(使用中のツールを自動検出)
67
+ pnpm dlx @lism-css/cli skill add
68
+
69
+ # ツールを明示指定
70
+ pnpm dlx @lism-css/cli skill add --claude --cursor
71
+
72
+ # 全ツール
73
+ pnpm dlx @lism-css/cli skill add --all
74
+
75
+ # 同梱スキルとローカルのファイル差分をチェック(変更 / 追加 / 削除のサマリ表示)
76
+ pnpm dlx @lism-css/cli skill check
77
+
78
+ # 上書き更新
79
+ pnpm dlx @lism-css/cli skill update --claude
80
+ ```
81
+
82
+ 配置先の対応表:
83
+
84
+ | ツール | 配置先 |
85
+ |--------|--------|
86
+ | `--claude` | `.claude/skills/lism-css-guide` |
87
+ | `--codex` | `.agents/skills/lism-css-guide` |
88
+ | `--cursor` | `.cursor/skills/lism-css-guide` |
89
+ | `--windsurf` | `.windsurf/skills/lism-css-guide` |
90
+ | `--cline` | `.cline/skills/lism-css-guide` |
91
+ | `--copilot` | `.github/skills/lism-css-guide` |
92
+ | `--gemini` | `.gemini/skills/lism-css-guide` |
93
+ | `--junie` | `.junie/skills/lism-css-guide` |
94
+
95
+ ## lism.config.js
96
+
97
+ `ui init` / `ui add` が生成・読み込む設定ファイル。CSS の設定(`tokens` 等)と CLI 設定を同居できます。
98
+
99
+ ```js
100
+ export default {
101
+ cli: {
102
+ framework: 'react', // 'react' | 'astro'
103
+ componentsDir: 'src/components/ui',
104
+ helperDir: 'src/components/ui/_helper',
105
+ },
106
+ };
107
+ ```
108
+
109
+ `lism.config.mjs` も同様に読み込まれます。旧 `lism-ui.json` は廃止予定(互換ロードのみ。起動時に deprecation 警告)。
110
+
111
+ > **Note:** TypeScript の `lism.config.ts` は現在未対応です。`lism-css` 本体の SCSS ビルド(`vite-plugin-lism-css` / `lism-css build`)も `.ts` を読み込まない設計のため、設定ファイルは `.js` / `.mjs` で記述してください。
112
+
113
+ ## パッケージが見つからないエラーが出る場合
114
+
115
+ npm / pnpm の Safe Supply Chain 機能により、公開から間もないパッケージがブロックされる場合があります。
116
+
117
+ ```bash
118
+ npm exec --safe-chain-skip-minimum-package-age @lism-css/cli create
119
+ pnpm --safe-chain-skip-minimum-package-age dlx @lism-css/cli create
120
+ ```
121
+
122
+ ## License
123
+
124
+ MIT
package/bin/lism.mjs ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/index.js';
@@ -0,0 +1,137 @@
1
+ // src/commands/create.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { downloadTemplate } from "giget";
5
+ import { select, input, confirm } from "@inquirer/prompts";
6
+
7
+ // src/logger.ts
8
+ import pc from "picocolors";
9
+ var logger = {
10
+ info(msg) {
11
+ console.log(pc.cyan(msg));
12
+ },
13
+ success(msg) {
14
+ console.log(pc.green(msg));
15
+ },
16
+ warn(msg) {
17
+ console.log(pc.yellow(`WARN: ${msg}`));
18
+ },
19
+ error(msg) {
20
+ console.error(pc.red(`ERROR: ${msg}`));
21
+ },
22
+ log(msg) {
23
+ console.log(msg);
24
+ }
25
+ };
26
+
27
+ // src/version.ts
28
+ var LISM_CSS_VERSION = "0.14.0" ? "0.14.0" : "unknown";
29
+ var CLI_VERSION = "0.0.1" ? "0.0.1" : "unknown";
30
+
31
+ // src/constants.ts
32
+ var SOURCE_REPO = "lism-css/lism-css";
33
+ var RAW_GITHUB_BASE = "https://raw.githubusercontent.com";
34
+ var DEFAULT_UI_REF = "main";
35
+ var DEFAULT_SKILL_REF = "main";
36
+ var DEFAULT_TEMPLATES_REF = "main";
37
+ var UI_COMPONENTS_PATH = "packages/lism-ui/src/components";
38
+ var UI_HELPER_PATH = "packages/lism-ui/src/helper";
39
+ var UI_REGISTRY_INDEX_PATH = "packages/lism-ui/registry-index.json";
40
+ var SKILL_SOURCE_PATH = "skills/lism-css-guide";
41
+ var EXAMPLES_PATH = "examples";
42
+
43
+ // src/commands/create.ts
44
+ var TEMPLATES = [{ name: "astro-minimal", label: "Astro (minimal)", description: "Astro \u30D9\u30FC\u30B9\u306E\u6700\u5C0F\u69CB\u6210" }];
45
+ async function runCreate({ template, targetDir, force = false }) {
46
+ const tpl = await resolveTemplate(template);
47
+ const outDir = path.resolve(process.cwd(), await resolveTargetDir(targetDir, tpl.name));
48
+ if (fs.existsSync(outDir) && fs.readdirSync(outDir).length > 0 && !force) {
49
+ const ok = await confirm({
50
+ message: `${outDir} \u306F\u65E2\u306B\u5B58\u5728\u3057\u3001\u7A7A\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002\u4E0A\u66F8\u304D\u3057\u307E\u3059\u304B\uFF1F`,
51
+ default: false
52
+ });
53
+ if (!ok) {
54
+ logger.warn("\u4E2D\u65AD\u3057\u307E\u3057\u305F\u3002");
55
+ return;
56
+ }
57
+ }
58
+ const ref = DEFAULT_TEMPLATES_REF;
59
+ logger.info(`\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8 "${tpl.name}" \u3092\u53D6\u5F97\u4E2D\uFF08ref: ${ref}\uFF09...`);
60
+ await downloadTemplate(`github:${SOURCE_REPO}/${EXAMPLES_PATH}/${tpl.name}#${ref}`, {
61
+ dir: outDir,
62
+ force: true,
63
+ forceClean: force
64
+ });
65
+ rewriteWorkspaceDeps(outDir);
66
+ logger.success(`${outDir} \u306B\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u751F\u6210\u3057\u307E\u3057\u305F\u3002`);
67
+ logger.log("");
68
+ logger.log("\u6B21\u306E\u30B3\u30DE\u30F3\u30C9\u3067\u958B\u767A\u3092\u958B\u59CB\u3067\u304D\u307E\u3059:");
69
+ const rel = path.relative(process.cwd(), outDir) || ".";
70
+ logger.log(` cd ${rel}`);
71
+ logger.log(" npm install # or pnpm install / yarn");
72
+ logger.log(" npm run dev");
73
+ }
74
+ async function createCommand(targetDir, options) {
75
+ await runCreate({ template: options.template, targetDir, force: options.force });
76
+ }
77
+ async function resolveTemplate(requested) {
78
+ if (requested) {
79
+ const found = TEMPLATES.find((t) => t.name === requested);
80
+ if (!found) {
81
+ throw new Error(`\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8 "${requested}" \u306F\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002\u5229\u7528\u53EF\u80FD: ${TEMPLATES.map((t) => t.name).join(", ")}`);
82
+ }
83
+ return found;
84
+ }
85
+ const picked = await select({
86
+ message: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044:",
87
+ choices: TEMPLATES.map((t) => ({ name: `${t.label} \u2014 ${t.description}`, value: t.name }))
88
+ });
89
+ return TEMPLATES.find((t) => t.name === picked);
90
+ }
91
+ async function resolveTargetDir(provided, templateName) {
92
+ if (provided) return provided;
93
+ return input({
94
+ message: "\u51FA\u529B\u5148\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA:",
95
+ default: `./${templateName}`
96
+ });
97
+ }
98
+ function rewriteWorkspaceDeps(projectDir) {
99
+ const pkgPath = path.join(projectDir, "package.json");
100
+ if (!fs.existsSync(pkgPath)) return;
101
+ try {
102
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
103
+ let touched = false;
104
+ for (const key of ["dependencies", "devDependencies", "peerDependencies"]) {
105
+ const deps = pkg[key];
106
+ if (!deps) continue;
107
+ for (const [name, value] of Object.entries(deps)) {
108
+ if (typeof value !== "string") continue;
109
+ if (value.startsWith("workspace:")) {
110
+ deps[name] = `^${LISM_CSS_VERSION}`;
111
+ touched = true;
112
+ }
113
+ }
114
+ }
115
+ if (touched) {
116
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
117
+ logger.log(` package.json \u306E workspace:* \u3092 ^${LISM_CSS_VERSION} \u306B\u7F6E\u63DB\u3057\u307E\u3057\u305F\u3002`);
118
+ }
119
+ } catch (err) {
120
+ logger.warn(`package.json \u306E\u66F8\u304D\u63DB\u3048\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${String(err)}`);
121
+ }
122
+ }
123
+
124
+ export {
125
+ logger,
126
+ SOURCE_REPO,
127
+ RAW_GITHUB_BASE,
128
+ DEFAULT_UI_REF,
129
+ DEFAULT_SKILL_REF,
130
+ UI_COMPONENTS_PATH,
131
+ UI_HELPER_PATH,
132
+ UI_REGISTRY_INDEX_PATH,
133
+ SKILL_SOURCE_PATH,
134
+ CLI_VERSION,
135
+ runCreate,
136
+ createCommand
137
+ };
package/dist/index.js ADDED
@@ -0,0 +1,875 @@
1
+ import {
2
+ CLI_VERSION,
3
+ DEFAULT_SKILL_REF,
4
+ DEFAULT_UI_REF,
5
+ RAW_GITHUB_BASE,
6
+ SKILL_SOURCE_PATH,
7
+ SOURCE_REPO,
8
+ UI_COMPONENTS_PATH,
9
+ UI_HELPER_PATH,
10
+ UI_REGISTRY_INDEX_PATH,
11
+ createCommand,
12
+ logger
13
+ } from "./chunk-4TQG5MGT.js";
14
+
15
+ // src/createProgram.ts
16
+ import { Command as Command3 } from "commander";
17
+
18
+ // src/commands/ui/index.ts
19
+ import { Command, Option } from "commander";
20
+
21
+ // src/commands/ui/add.ts
22
+ import fs3 from "fs";
23
+ import path4 from "path";
24
+ import { confirm, select as select2 } from "@inquirer/prompts";
25
+
26
+ // src/config.ts
27
+ import fs from "fs";
28
+ import path from "path";
29
+ import { createJiti } from "jiti";
30
+ var LEGACY_CONFIG_FILE = "lism-ui.json";
31
+ var CONFIG_SEARCH = ["lism.config.js", "lism.config.mjs"];
32
+ function resolvePath(filename) {
33
+ return path.resolve(process.cwd(), filename);
34
+ }
35
+ function findConfigFile() {
36
+ for (const name of CONFIG_SEARCH) {
37
+ const abs = resolvePath(name);
38
+ if (fs.existsSync(abs)) return { path: abs, filename: name, kind: "module" };
39
+ }
40
+ const legacy = resolvePath(LEGACY_CONFIG_FILE);
41
+ if (fs.existsSync(legacy)) return { path: legacy, filename: LEGACY_CONFIG_FILE, kind: "legacy-json" };
42
+ return null;
43
+ }
44
+ function configExists() {
45
+ return findConfigFile() !== null;
46
+ }
47
+ var DEFAULT_CONFIG_FILENAME = "lism.config.js";
48
+ function getDefaultConfigPath() {
49
+ return resolvePath(DEFAULT_CONFIG_FILENAME);
50
+ }
51
+ async function readConfig() {
52
+ const found = findConfigFile();
53
+ if (!found) {
54
+ throw new Error("lism.config.js / lism.config.mjs / lism-ui.json \u306E\u3044\u305A\u308C\u3082\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002");
55
+ }
56
+ if (found.kind === "legacy-json") {
57
+ logger.warn(`[deprecated] ${LEGACY_CONFIG_FILE} \u306F\u5EC3\u6B62\u4E88\u5B9A\u3067\u3059\u3002"lism ui init" \u3067 lism.config.js \u3078\u79FB\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
58
+ const raw = fs.readFileSync(found.path, "utf-8");
59
+ const parsed = JSON.parse(raw);
60
+ return parsed;
61
+ }
62
+ const jiti = createJiti(import.meta.url, { interopDefault: true });
63
+ const mod = await jiti.import(found.path);
64
+ const cli = mod?.cli ?? mod;
65
+ if (!cli || typeof cli !== "object") {
66
+ throw new Error(`${found.filename} \u304B\u3089 CLI \u8A2D\u5B9A\uFF08cli \u30AD\u30FC\uFF09\u3092\u8AAD\u307F\u8FBC\u3081\u307E\u305B\u3093\u3067\u3057\u305F\u3002`);
67
+ }
68
+ validateCliConfig(cli);
69
+ return cli;
70
+ }
71
+ function validateCliConfig(cli) {
72
+ const c = cli;
73
+ if (c.framework !== "react" && c.framework !== "astro") {
74
+ throw new Error(`cli.framework \u306F "react" \u307E\u305F\u306F "astro" \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
75
+ }
76
+ if (typeof c.componentsDir !== "string" || !c.componentsDir) {
77
+ throw new Error("cli.componentsDir \u306F\u6587\u5B57\u5217\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
78
+ }
79
+ if (typeof c.helperDir !== "string" || !c.helperDir) {
80
+ throw new Error("cli.helperDir \u306F\u6587\u5B57\u5217\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
81
+ }
82
+ }
83
+ function writeFreshConfig(cli) {
84
+ const filePath = getDefaultConfigPath();
85
+ const body = renderConfigTemplate(cli);
86
+ fs.writeFileSync(filePath, body);
87
+ return filePath;
88
+ }
89
+ async function hasCliSection(filePath) {
90
+ try {
91
+ const jiti = createJiti(import.meta.url, { interopDefault: true });
92
+ const mod = await jiti.import(filePath);
93
+ return !!mod?.cli;
94
+ } catch (err) {
95
+ throw new Error(`${filePath} \u3092\u8AAD\u307F\u8FBC\u3081\u307E\u305B\u3093\u3067\u3057\u305F\uFF08\u69CB\u6587\u30A8\u30E9\u30FC\u7B49\uFF09\u3002\u4FEE\u6B63\u3057\u3066\u304B\u3089\u518D\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044: ${String(err)}`);
96
+ }
97
+ }
98
+ async function patchConfigWithCli(cli, targetPath, options = {}) {
99
+ const filePath = targetPath ?? getDefaultConfigPath();
100
+ let source = fs.readFileSync(filePath, "utf-8");
101
+ const hasExisting = options.existingCli ?? await hasCliSection(filePath);
102
+ if (hasExisting) {
103
+ if (!options.force) {
104
+ return { path: filePath, patched: false };
105
+ }
106
+ const removed = removeCliSection(source);
107
+ if (removed === null) {
108
+ return { path: filePath, patched: false };
109
+ }
110
+ source = removed;
111
+ }
112
+ const insertAt = findInsertPosition(source);
113
+ if (insertAt === -1) {
114
+ return { path: filePath, patched: false };
115
+ }
116
+ const insertion = `
117
+ cli: ${renderCliObject(cli, " ")},`;
118
+ const updated = source.slice(0, insertAt) + insertion + source.slice(insertAt);
119
+ fs.writeFileSync(filePath, updated);
120
+ return { path: filePath, patched: true };
121
+ }
122
+ function removeCliSection(source) {
123
+ const match = source.match(/(^|[\n,{])(\s*)cli\s*:\s*\{/);
124
+ if (!match) return null;
125
+ const sectionStart = match.index + match[1].length + match[2].length;
126
+ const openBrace = match.index + match[0].length - 1;
127
+ let i = openBrace + 1;
128
+ let depth = 1;
129
+ while (i < source.length && depth > 0) {
130
+ const c = source[i];
131
+ if (c === '"' || c === "'" || c === "`") {
132
+ const quote = c;
133
+ i++;
134
+ while (i < source.length && source[i] !== quote) {
135
+ if (source[i] === "\\") i++;
136
+ i++;
137
+ }
138
+ i++;
139
+ continue;
140
+ }
141
+ if (c === "/" && source[i + 1] === "/") {
142
+ while (i < source.length && source[i] !== "\n") i++;
143
+ continue;
144
+ }
145
+ if (c === "/" && source[i + 1] === "*") {
146
+ i += 2;
147
+ while (i < source.length - 1 && !(source[i] === "*" && source[i + 1] === "/")) i++;
148
+ i += 2;
149
+ continue;
150
+ }
151
+ if (c === "{") depth++;
152
+ else if (c === "}") depth--;
153
+ i++;
154
+ }
155
+ if (depth !== 0) return null;
156
+ let end = i;
157
+ if (source[end] === ",") end++;
158
+ const trailing = source.slice(end).match(/^[ \t]*\n/);
159
+ if (trailing) end += trailing[0].length;
160
+ return source.slice(0, sectionStart) + source.slice(end);
161
+ }
162
+ function findInsertPosition(source) {
163
+ const inline = source.match(/export default\s*(?:[A-Za-z_$][A-Za-z0-9_$]*\s*)?\(?\s*\{/);
164
+ if (inline) {
165
+ return inline.index + inline[0].length;
166
+ }
167
+ const varExport = source.match(/export default\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*;?/);
168
+ if (varExport) {
169
+ const varName = varExport[1];
170
+ const escaped = varName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
171
+ const decl = source.match(new RegExp(`(?:const|let|var)\\s+${escaped}\\s*=\\s*(?:[A-Za-z_$][A-Za-z0-9_$]*\\s*)?\\(?\\s*\\{`));
172
+ if (decl) {
173
+ return decl.index + decl[0].length;
174
+ }
175
+ }
176
+ return -1;
177
+ }
178
+ function renderConfigTemplate(cli) {
179
+ return [
180
+ "export default {",
181
+ //
182
+ ` cli: ${renderCliObject(cli, " ")},`,
183
+ "};",
184
+ ""
185
+ ].join("\n");
186
+ }
187
+ function renderCliObject(cli, indent) {
188
+ return [
189
+ "{",
190
+ //
191
+ `${indent} framework: ${JSON.stringify(cli.framework)},`,
192
+ `${indent} componentsDir: ${JSON.stringify(cli.componentsDir)},`,
193
+ `${indent} helperDir: ${JSON.stringify(cli.helperDir)},`,
194
+ `${indent}}`
195
+ ].join("\n");
196
+ }
197
+
198
+ // src/commands/ui/fetcher.ts
199
+ import fs2 from "fs";
200
+ import os from "os";
201
+ import path3 from "path";
202
+ import { downloadTemplate } from "giget";
203
+
204
+ // src/transform.ts
205
+ import path2 from "path";
206
+ function resolveHelperPlaceholder(content, fileRelPath, componentDir, helperDir) {
207
+ if (!content.includes("{{HELPER}}")) return content;
208
+ const fileDir = path2.join(componentDir, path2.dirname(fileRelPath));
209
+ const relativePath = path2.relative(fileDir, helperDir);
210
+ const posixPath = relativePath.split(path2.sep).join("/");
211
+ return content.replace(/\{\{HELPER\}\}/g, posixPath);
212
+ }
213
+ function classifyFile(relativePath) {
214
+ if (relativePath.startsWith("react/")) return "react";
215
+ if (relativePath.startsWith("astro/")) return "astro";
216
+ return "shared";
217
+ }
218
+ function stripFrameworkPrefix(relativePath) {
219
+ if (relativePath.startsWith("react/")) return relativePath.slice("react/".length);
220
+ if (relativePath.startsWith("astro/")) return relativePath.slice("astro/".length);
221
+ return relativePath;
222
+ }
223
+ function replaceHelperImports(content) {
224
+ const helpers = /* @__PURE__ */ new Set();
225
+ const replaced = content.replace(
226
+ /(from\s+['"])(\.\.\/)+helper\/([^'"]+)(['"])/g,
227
+ (_m, prefix, _dots, helperPath, suffix) => {
228
+ const helperName = helperPath.replace(/\.[^.]+$/, "");
229
+ helpers.add(helperName);
230
+ return `${prefix}{{HELPER}}/${helperPath}${suffix}`;
231
+ }
232
+ );
233
+ return { content: replaced, helpers: [...helpers] };
234
+ }
235
+ function flattenSiblingImports(content) {
236
+ let replaced = content.replace(/(from\s+['"])\.\.\/([^'"]+)(['"])/g, "$1./$2$3");
237
+ replaced = replaced.replace(/(import\s+['"])\.\.\/([^'"]+)(['"])/g, "$1./$2$3");
238
+ return replaced;
239
+ }
240
+ function transformComponentFile(relativePath, rawContent) {
241
+ const { content: helperResolved, helpers } = replaceHelperImports(rawContent);
242
+ const category = classifyFile(relativePath);
243
+ const needsFlatten = category !== "shared";
244
+ const outPath = needsFlatten ? stripFrameworkPrefix(relativePath) : relativePath;
245
+ const outContent = needsFlatten ? flattenSiblingImports(helperResolved) : helperResolved;
246
+ return {
247
+ file: { path: outPath, content: outContent, category },
248
+ helpers
249
+ };
250
+ }
251
+
252
+ // src/commands/ui/fetcher.ts
253
+ function walkFiles(root) {
254
+ const results = [];
255
+ const stack = [""];
256
+ while (stack.length > 0) {
257
+ const rel = stack.pop();
258
+ const abs = path3.join(root, rel);
259
+ if (!fs2.existsSync(abs)) continue;
260
+ for (const entry of fs2.readdirSync(abs, { withFileTypes: true })) {
261
+ const childRel = rel ? path3.join(rel, entry.name) : entry.name;
262
+ if (entry.isDirectory()) stack.push(childRel);
263
+ else if (entry.isFile()) results.push(childRel.split(path3.sep).join("/"));
264
+ }
265
+ }
266
+ return results;
267
+ }
268
+ function makeTmpDir(prefix) {
269
+ return fs2.mkdtempSync(path3.join(os.tmpdir(), prefix));
270
+ }
271
+ function cleanupTmpDir(dir) {
272
+ try {
273
+ fs2.rmSync(dir, { recursive: true, force: true });
274
+ } catch {
275
+ }
276
+ }
277
+ function isExcludedComponentFile(rel, excludeRootFiles) {
278
+ if (!rel.includes("/") && excludeRootFiles.has(rel)) return true;
279
+ if (/(^|\/)[^/]+\.stories\.[a-z]+$/.test(rel)) return true;
280
+ if (/(^|\/)[^/]+\.test\.[a-z]+$/.test(rel)) return true;
281
+ return false;
282
+ }
283
+ async function fetchCatalog(options = {}) {
284
+ const ref = options.ref ?? DEFAULT_UI_REF;
285
+ const url = `${RAW_GITHUB_BASE}/${SOURCE_REPO}/${ref}/${UI_REGISTRY_INDEX_PATH}`;
286
+ const res = await fetch(url);
287
+ if (!res.ok) {
288
+ throw new Error(`Failed to fetch registry-index.json (${res.status} ${res.statusText}): ${url}`);
289
+ }
290
+ return await res.json();
291
+ }
292
+ async function fetchComponent(name, excludeRootFiles, options = {}) {
293
+ const ref = options.ref ?? DEFAULT_UI_REF;
294
+ const tmp = makeTmpDir("lism-ui-component-");
295
+ try {
296
+ await downloadTemplate(`github:${SOURCE_REPO}/${UI_COMPONENTS_PATH}/${name}#${ref}`, {
297
+ dir: tmp,
298
+ force: true,
299
+ forceClean: true
300
+ });
301
+ const files = walkFiles(tmp);
302
+ const helperSet = /* @__PURE__ */ new Set();
303
+ const classified = { shared: [], react: [], astro: [] };
304
+ for (const rel of files) {
305
+ if (isExcludedComponentFile(rel, excludeRootFiles)) continue;
306
+ const raw = fs2.readFileSync(path3.join(tmp, rel), "utf-8");
307
+ const { file, helpers } = transformComponentFile(rel, raw);
308
+ for (const h of helpers) helperSet.add(h);
309
+ classified[file.category].push({ path: file.path, content: file.content });
310
+ }
311
+ return {
312
+ name,
313
+ helpers: [...helperSet].sort(),
314
+ files: classified
315
+ };
316
+ } finally {
317
+ cleanupTmpDir(tmp);
318
+ }
319
+ }
320
+ var helperTreeCache = /* @__PURE__ */ new Map();
321
+ async function loadHelperTree(ref) {
322
+ const cached = helperTreeCache.get(ref);
323
+ if (cached) return cached;
324
+ const promise = (async () => {
325
+ const tmp = makeTmpDir("lism-ui-helper-");
326
+ try {
327
+ await downloadTemplate(`github:${SOURCE_REPO}/${UI_HELPER_PATH}#${ref}`, {
328
+ dir: tmp,
329
+ force: true,
330
+ forceClean: true
331
+ });
332
+ const byName = /* @__PURE__ */ new Map();
333
+ for (const rel of walkFiles(tmp)) {
334
+ if (/(^|\/)[^/]+\.test\.[a-z]+$/.test(rel)) continue;
335
+ const base = path3.basename(rel).replace(/\.[^.]+$/, "");
336
+ const entry = { path: rel, content: fs2.readFileSync(path3.join(tmp, rel), "utf-8") };
337
+ const list = byName.get(base);
338
+ if (list) list.push(entry);
339
+ else byName.set(base, [entry]);
340
+ }
341
+ return { byName };
342
+ } finally {
343
+ cleanupTmpDir(tmp);
344
+ }
345
+ })();
346
+ helperTreeCache.set(ref, promise);
347
+ promise.catch(() => helperTreeCache.delete(ref));
348
+ return promise;
349
+ }
350
+ async function fetchHelper(name, options = {}) {
351
+ const ref = options.ref ?? DEFAULT_UI_REF;
352
+ const tree = await loadHelperTree(ref);
353
+ const files = tree.byName.get(name);
354
+ if (!files || files.length === 0) {
355
+ throw new Error(`helper "${name}" \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\uFF08${UI_HELPER_PATH}\uFF09`);
356
+ }
357
+ return { name, files };
358
+ }
359
+
360
+ // src/commands/ui/init.ts
361
+ import { select, input } from "@inquirer/prompts";
362
+ async function runInit(options = {}) {
363
+ const framework = options.framework ?? await select({
364
+ message: "\u30D5\u30EC\u30FC\u30E0\u30EF\u30FC\u30AF\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044:",
365
+ choices: [
366
+ { name: "React", value: "react" },
367
+ { name: "Astro", value: "astro" }
368
+ ]
369
+ });
370
+ const componentsDir = options.componentsDir ?? await input({
371
+ message: "\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8\u306E\u51FA\u529B\u5148\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA:",
372
+ default: "src/components/ui"
373
+ });
374
+ const helperDir = options.helperDir ?? await input({
375
+ message: "helper \u306E\u51FA\u529B\u5148\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA:",
376
+ default: `${componentsDir}/_helper`
377
+ });
378
+ const config = { framework, componentsDir, helperDir };
379
+ const found = findConfigFile();
380
+ if (found?.kind === "module") {
381
+ const { patched, path: outPath } = await patchConfigWithCli(config, found.path, {
382
+ force: options.force,
383
+ existingCli: options.existingCli
384
+ });
385
+ if (patched) {
386
+ logger.success(`${outPath} \u306B cli \u30BB\u30AF\u30B7\u30E7\u30F3\u3092${options.force ? "\u66F4\u65B0" : "\u8FFD\u8A18"}\u3057\u307E\u3057\u305F\u3002`);
387
+ } else {
388
+ logger.warn(`${outPath} \u306B\u65E2\u306B cli \u30BB\u30AF\u30B7\u30E7\u30F3\u304C\u542B\u307E\u308C\u3066\u3044\u308B\u304B\u3001export default \u304C\u691C\u51FA\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u624B\u52D5\u3067\u8FFD\u8A18\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
389
+ }
390
+ } else {
391
+ const outPath = writeFreshConfig(config);
392
+ logger.success(`${outPath} \u3092\u4F5C\u6210\u3057\u307E\u3057\u305F\u3002`);
393
+ }
394
+ return config;
395
+ }
396
+ async function initCommand(options) {
397
+ const found = findConfigFile();
398
+ let existingCli = false;
399
+ if (found?.kind === "legacy-json") {
400
+ logger.warn(`${found.filename} \u3092\u691C\u51FA\u3057\u307E\u3057\u305F\u3002lism.config.js \u3078\u79FB\u884C\u3057\u307E\u3059\u3002\u53E4\u3044\u30D5\u30A1\u30A4\u30EB\u306F\u5F8C\u3067\u524A\u9664\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
401
+ } else if (found?.kind === "module") {
402
+ try {
403
+ existingCli = await hasCliSection(found.path);
404
+ } catch (err) {
405
+ logger.error(err instanceof Error ? err.message : String(err));
406
+ process.exit(1);
407
+ }
408
+ if (existingCli) {
409
+ if (!options.force) {
410
+ logger.warn(`${found.filename} \u306B\u306F\u65E2\u306B cli \u30BB\u30AF\u30B7\u30E7\u30F3\u304C\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u4E0A\u66F8\u304D\u3059\u308B\u306B\u306F --force \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
411
+ return;
412
+ }
413
+ logger.warn(`${found.filename} \u306E\u65E2\u5B58\u306E cli \u30BB\u30AF\u30B7\u30E7\u30F3\u3092\u4E0A\u66F8\u304D\u3057\u307E\u3059\u3002`);
414
+ }
415
+ }
416
+ await runInit({ ...options, existingCli });
417
+ }
418
+
419
+ // src/commands/ui/normalize.ts
420
+ var normalizeComponentName = (s) => s.replace(/[-_]/g, "").toLowerCase();
421
+
422
+ // src/commands/ui/add.ts
423
+ async function addCommand(names, options) {
424
+ let config;
425
+ if (configExists()) {
426
+ config = await readConfig();
427
+ } else {
428
+ logger.info("lism.config.js \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u3092\u958B\u59CB\u3057\u307E\u3059...\n");
429
+ config = await runInit();
430
+ console.log();
431
+ }
432
+ const fetchOpts = { ref: options.ref };
433
+ let catalog;
434
+ try {
435
+ catalog = await fetchCatalog(fetchOpts);
436
+ } catch (err) {
437
+ const refInfo = options.ref ? ` (ref: ${options.ref})` : "";
438
+ const reason = err instanceof Error ? err.message : String(err);
439
+ logger.error(`\u30AB\u30BF\u30ED\u30B0\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F${refInfo}: ${reason}`);
440
+ process.exit(1);
441
+ }
442
+ if (options.all) {
443
+ names = catalog.components.map((c) => c.name);
444
+ logger.info(`\u5168 ${names.length} \u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8\u3092\u8FFD\u52A0\u3057\u307E\u3059...`);
445
+ }
446
+ if (names.length === 0) {
447
+ logger.error("\u8FFD\u52A0\u3059\u308B\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8\u540D\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
448
+ process.exit(1);
449
+ }
450
+ const resolvedNames = [];
451
+ const notFound = [];
452
+ for (const input2 of names) {
453
+ const normalized = normalizeComponentName(input2);
454
+ const match = catalog.components.find((c) => normalizeComponentName(c.name) === normalized);
455
+ if (match) resolvedNames.push(match.name);
456
+ else notFound.push(input2);
457
+ }
458
+ if (notFound.length > 0) {
459
+ logger.error(`\u898B\u3064\u304B\u3089\u306A\u3044\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8: ${notFound.join(", ")}`);
460
+ process.exit(1);
461
+ }
462
+ const excludeRootFiles = new Set(catalog.excludeComponentFiles);
463
+ const results = await Promise.allSettled(resolvedNames.map((n) => fetchComponent(n, excludeRootFiles, fetchOpts)));
464
+ const installedHelpers = /* @__PURE__ */ new Set();
465
+ let overwritePolicy = "per-component";
466
+ if (!options.overwrite && resolvedNames.length > 1) {
467
+ overwritePolicy = await askOverwritePolicy();
468
+ }
469
+ const overwriteAll = options.overwrite || overwritePolicy === "all";
470
+ let hasFailure = false;
471
+ for (let i = 0; i < resolvedNames.length; i++) {
472
+ const result = results[i];
473
+ if (result.status === "rejected") {
474
+ logger.error(`"${resolvedNames[i]}" \u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${String(result.reason)}`);
475
+ hasFailure = true;
476
+ continue;
477
+ }
478
+ const helperFailed = await writeComponent(result.value, config, overwriteAll, overwritePolicy, installedHelpers, fetchOpts);
479
+ if (helperFailed) hasFailure = true;
480
+ }
481
+ if (hasFailure) {
482
+ logger.error("\u4E00\u90E8\u306E\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8 / helper \u306E\u8FFD\u52A0\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002");
483
+ process.exit(1);
484
+ }
485
+ logger.success("\u5B8C\u4E86\u3057\u307E\u3057\u305F\u3002");
486
+ }
487
+ async function askOverwritePolicy() {
488
+ return select2({
489
+ message: "\u65E2\u5B58\u30D5\u30A1\u30A4\u30EB\u306E\u4E0A\u66F8\u304D\u65B9\u91DD\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044:",
490
+ choices: [
491
+ { name: "\u5168\u3066\u4E0A\u66F8\u304D", value: "all" },
492
+ { name: "\u5168\u3066\u30B9\u30AD\u30C3\u30D7", value: "none" },
493
+ { name: "\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8\u3054\u3068\u306B\u78BA\u8A8D", value: "per-component" }
494
+ ]
495
+ });
496
+ }
497
+ function writeFile(filePath, content) {
498
+ fs3.mkdirSync(path4.dirname(filePath), { recursive: true });
499
+ fs3.writeFileSync(filePath, content);
500
+ logger.log(` \u4F5C\u6210: ${path4.relative(process.cwd(), filePath)}`);
501
+ }
502
+ function hasExistingFiles(files, baseDir) {
503
+ return files.some((f) => fs3.existsSync(path4.join(baseDir, f.path)));
504
+ }
505
+ async function writeComponent(component, config, overwriteAll, policy, installedHelpers, fetchOpts) {
506
+ logger.info(`${component.name} \u3092\u5C55\u958B\u4E2D...`);
507
+ const filesToWrite = [...component.files.shared, ...component.files[config.framework]];
508
+ const componentDirName = component.name;
509
+ const componentDir = path4.resolve(process.cwd(), config.componentsDir, componentDirName);
510
+ const helperDir = path4.resolve(process.cwd(), config.helperDir);
511
+ let shouldWrite;
512
+ const hasExisting = hasExistingFiles(filesToWrite, componentDir);
513
+ if (overwriteAll) {
514
+ shouldWrite = true;
515
+ } else if (!hasExisting) {
516
+ shouldWrite = true;
517
+ } else if (policy === "none") {
518
+ shouldWrite = false;
519
+ } else if (policy === "per-component") {
520
+ shouldWrite = await confirm({
521
+ message: `${componentDirName} \u306F\u65E2\u306B\u5B58\u5728\u3057\u307E\u3059\u3002\u4E0A\u66F8\u304D\u3057\u307E\u3059\u304B\uFF1F`,
522
+ default: false
523
+ });
524
+ } else {
525
+ shouldWrite = true;
526
+ }
527
+ if (!shouldWrite) {
528
+ logger.log(` \u30B9\u30AD\u30C3\u30D7: ${componentDirName}`);
529
+ } else {
530
+ for (const file of filesToWrite) {
531
+ const filePath = path4.join(componentDir, file.path);
532
+ const content = resolveHelperPlaceholder(file.content, file.path, componentDir, helperDir);
533
+ writeFile(filePath, content);
534
+ }
535
+ }
536
+ const helpersToInstall = component.helpers.filter((h) => !installedHelpers.has(h));
537
+ for (const h of helpersToInstall) installedHelpers.add(h);
538
+ let helperFailed = false;
539
+ if (helpersToInstall.length > 0) {
540
+ const helperResults = await Promise.allSettled(helpersToInstall.map((h) => fetchHelper(h, fetchOpts)));
541
+ for (let i = 0; i < helpersToInstall.length; i++) {
542
+ const result = helperResults[i];
543
+ if (result.status === "rejected") {
544
+ logger.error(` helper "${helpersToInstall[i]}" \u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${String(result.reason)}`);
545
+ helperFailed = true;
546
+ continue;
547
+ }
548
+ for (const file of result.value.files) {
549
+ const filePath = path4.join(helperDir, file.path);
550
+ if (fs3.existsSync(filePath) && !overwriteAll) {
551
+ if (policy === "none") {
552
+ logger.log(` \u30B9\u30AD\u30C3\u30D7: ${file.path}`);
553
+ continue;
554
+ }
555
+ if (policy === "per-component") {
556
+ const shouldOverwrite = await confirm({
557
+ message: `${path4.relative(process.cwd(), filePath)} \u306F\u65E2\u306B\u5B58\u5728\u3057\u307E\u3059\u3002\u4E0A\u66F8\u304D\u3057\u307E\u3059\u304B\uFF1F`,
558
+ default: false
559
+ });
560
+ if (!shouldOverwrite) {
561
+ logger.log(` \u30B9\u30AD\u30C3\u30D7: ${file.path}`);
562
+ continue;
563
+ }
564
+ }
565
+ }
566
+ writeFile(filePath, file.content);
567
+ }
568
+ }
569
+ }
570
+ return helperFailed;
571
+ }
572
+
573
+ // src/commands/ui/list.ts
574
+ async function listCommand(options = {}) {
575
+ logger.info("\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8\u4E00\u89A7\u3092\u53D6\u5F97\u4E2D...");
576
+ const fetchOpts = { ref: options.ref };
577
+ let catalog;
578
+ try {
579
+ catalog = await fetchCatalog(fetchOpts);
580
+ } catch (err) {
581
+ const refInfo = options.ref ? ` (ref: ${options.ref})` : "";
582
+ const reason = err instanceof Error ? err.message : String(err);
583
+ logger.error(`\u30AB\u30BF\u30ED\u30B0\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F${refInfo}: ${reason}`);
584
+ process.exit(1);
585
+ }
586
+ logger.log(`
587
+ Lism UI v${catalog.version}
588
+ `);
589
+ logger.log("\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8:");
590
+ for (const component of catalog.components) {
591
+ const helpers = component.helpers.length > 0 ? ` (helpers: ${component.helpers.join(", ")})` : "";
592
+ logger.log(` - ${component.name}${helpers}`);
593
+ }
594
+ logger.log(`
595
+ \u5408\u8A08: ${catalog.components.length} \u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8`);
596
+ }
597
+
598
+ // src/commands/ui/index.ts
599
+ function createUiCommand() {
600
+ const ui = new Command("ui").description("Lism UI \u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8\u306E\u8FFD\u52A0\u30FB\u7BA1\u7406");
601
+ ui.command("init").description("lism.config.js \u306E cli \u30BB\u30AF\u30B7\u30E7\u30F3\u3092\u751F\u6210\u3059\u308B").addOption(new Option("--framework <name>", "\u30D5\u30EC\u30FC\u30E0\u30EF\u30FC\u30AF").choices(["react", "astro"])).option("--components-dir <path>", "\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8\u306E\u51FA\u529B\u5148\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA").option("--helper-dir <path>", "helper \u306E\u51FA\u529B\u5148\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA").option("-f, --force", "\u65E2\u5B58\u306E cli \u30BB\u30AF\u30B7\u30E7\u30F3\u3092\u4E0A\u66F8\u304D", false).action(initCommand);
602
+ ui.command("add").description("\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8\u3092\u8FFD\u52A0\u3059\u308B").argument("[names...]", "\u8FFD\u52A0\u3059\u308B\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8\u540D").option("-o, --overwrite", "\u65E2\u5B58\u30D5\u30A1\u30A4\u30EB\u3092\u78BA\u8A8D\u306A\u3057\u3067\u4E0A\u66F8\u304D", false).option("-a, --all", "\u5168\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8\u3092\u8FFD\u52A0", false).option("--ref <ref>", "\u53D6\u5F97\u5143\u306E Git ref\uFF08\u30D6\u30E9\u30F3\u30C1 / \u30BF\u30B0 / \u30B3\u30DF\u30C3\u30C8\uFF09").action(addCommand);
603
+ ui.command("list").description("\u5229\u7528\u53EF\u80FD\u306A\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8\u4E00\u89A7\u3092\u8868\u793A\u3059\u308B").option("--ref <ref>", "\u53D6\u5F97\u5143\u306E Git ref\uFF08\u30D6\u30E9\u30F3\u30C1 / \u30BF\u30B0 / \u30B3\u30DF\u30C3\u30C8\uFF09").action(listCommand);
604
+ return ui;
605
+ }
606
+
607
+ // src/commands/skill/index.ts
608
+ import { Command as Command2 } from "commander";
609
+
610
+ // src/commands/skill/add.ts
611
+ import fs5 from "fs";
612
+ import path6 from "path";
613
+ import { checkbox, confirm as confirm2 } from "@inquirer/prompts";
614
+
615
+ // src/commands/skill/paths.ts
616
+ var SKILL_PATHS = {
617
+ claude: ".claude/skills/lism-css-guide",
618
+ codex: ".agents/skills/lism-css-guide",
619
+ cursor: ".cursor/skills/lism-css-guide",
620
+ windsurf: ".windsurf/skills/lism-css-guide",
621
+ cline: ".cline/skills/lism-css-guide",
622
+ copilot: ".github/skills/lism-css-guide",
623
+ gemini: ".gemini/skills/lism-css-guide",
624
+ junie: ".junie/skills/lism-css-guide"
625
+ };
626
+ var ALL_SKILL_TOOLS = Object.keys(SKILL_PATHS);
627
+ var TOOL_MARKERS = {
628
+ claude: [".claude"],
629
+ codex: [".agents", ".codex"],
630
+ cursor: [".cursor"],
631
+ windsurf: [".windsurf"],
632
+ cline: [".cline"],
633
+ copilot: [".github/copilot-instructions.md", ".github/copilot"],
634
+ gemini: [".gemini"],
635
+ junie: [".junie"]
636
+ };
637
+
638
+ // src/commands/skill/skillSource.ts
639
+ import crypto from "crypto";
640
+ import fs4 from "fs";
641
+ import os2 from "os";
642
+ import path5 from "path";
643
+ import { downloadTemplate as downloadTemplate2 } from "giget";
644
+ async function fetchSkillSource(ref = DEFAULT_SKILL_REF) {
645
+ const tmpDir = fs4.mkdtempSync(path5.join(os2.tmpdir(), "lism-skill-"));
646
+ await downloadTemplate2(`github:${SOURCE_REPO}/${SKILL_SOURCE_PATH}#${ref}`, {
647
+ dir: tmpDir,
648
+ force: true,
649
+ forceClean: true
650
+ });
651
+ return { dir: tmpDir, ref };
652
+ }
653
+ function cleanupTempDir(dir) {
654
+ try {
655
+ fs4.rmSync(dir, { recursive: true, force: true });
656
+ } catch {
657
+ }
658
+ }
659
+ function walkFiles2(root) {
660
+ const results = [];
661
+ const stack = [""];
662
+ while (stack.length > 0) {
663
+ const rel = stack.pop();
664
+ const abs = path5.join(root, rel);
665
+ for (const entry of fs4.readdirSync(abs, { withFileTypes: true })) {
666
+ const childRel = rel ? path5.join(rel, entry.name) : entry.name;
667
+ if (entry.isDirectory()) {
668
+ stack.push(childRel);
669
+ } else if (entry.isFile()) {
670
+ results.push(childRel);
671
+ }
672
+ }
673
+ }
674
+ return results;
675
+ }
676
+ function hashFile(filePath) {
677
+ const buf = fs4.readFileSync(filePath);
678
+ return crypto.createHash("sha256").update(buf).digest("hex");
679
+ }
680
+ function compareSkillDirs(localDir, remoteDir) {
681
+ const localExists = fs4.existsSync(localDir);
682
+ const remoteFiles = new Set(walkFiles2(remoteDir));
683
+ const localFiles = new Set(localExists ? walkFiles2(localDir) : []);
684
+ const diff = { unchanged: [], modified: [], added: [], localOnly: [] };
685
+ for (const rel of remoteFiles) {
686
+ if (!localFiles.has(rel)) {
687
+ diff.added.push(rel);
688
+ continue;
689
+ }
690
+ const localHash = hashFile(path5.join(localDir, rel));
691
+ const remoteHash = hashFile(path5.join(remoteDir, rel));
692
+ if (localHash === remoteHash) diff.unchanged.push(rel);
693
+ else diff.modified.push(rel);
694
+ }
695
+ for (const rel of localFiles) {
696
+ if (!remoteFiles.has(rel)) diff.localOnly.push(rel);
697
+ }
698
+ for (const list of Object.values(diff)) list.sort();
699
+ return diff;
700
+ }
701
+ function hasDiff(diff) {
702
+ return diff.modified.length > 0 || diff.added.length > 0 || diff.localOnly.length > 0;
703
+ }
704
+ function copyDirRecursive(src, dest) {
705
+ fs4.mkdirSync(dest, { recursive: true });
706
+ for (const entry of fs4.readdirSync(src, { withFileTypes: true })) {
707
+ const srcPath = path5.join(src, entry.name);
708
+ const destPath = path5.join(dest, entry.name);
709
+ if (entry.isDirectory()) {
710
+ copyDirRecursive(srcPath, destPath);
711
+ } else if (entry.isFile()) {
712
+ fs4.copyFileSync(srcPath, destPath);
713
+ }
714
+ }
715
+ }
716
+
717
+ // src/commands/skill/add.ts
718
+ function resolveExplicitTools(options) {
719
+ if (options.all) return [...ALL_SKILL_TOOLS];
720
+ const explicit = ALL_SKILL_TOOLS.filter((t) => options[t]);
721
+ return explicit;
722
+ }
723
+ function autoDetectTools(cwd) {
724
+ return ALL_SKILL_TOOLS.filter((tool) => TOOL_MARKERS[tool].some((marker) => fs5.existsSync(path6.resolve(cwd, marker))));
725
+ }
726
+ async function skillAddCommand(options) {
727
+ const cwd = process.cwd();
728
+ let targets = resolveExplicitTools(options);
729
+ if (targets.length === 0) {
730
+ const detected = autoDetectTools(cwd);
731
+ if (detected.length > 0) {
732
+ logger.info(`\u691C\u51FA\u3057\u305F AI \u30C4\u30FC\u30EB: ${detected.join(", ")}`);
733
+ }
734
+ targets = await checkbox({
735
+ message: "\u914D\u7F6E\u5148\u306E\u30C4\u30FC\u30EB\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u30B9\u30DA\u30FC\u30B9\u3067\u9078\u629E\u3001Enter \u3067\u78BA\u5B9A\uFF09:",
736
+ choices: ALL_SKILL_TOOLS.map((t) => ({
737
+ name: `${t} \u2192 ${SKILL_PATHS[t]}`,
738
+ value: t,
739
+ checked: detected.includes(t)
740
+ }))
741
+ });
742
+ }
743
+ if (targets.length === 0) {
744
+ logger.warn("\u914D\u7F6E\u5148\u304C\u9078\u629E\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F\u3002");
745
+ return;
746
+ }
747
+ const ref = options.ref ?? DEFAULT_SKILL_REF;
748
+ logger.info(`\u30B9\u30AD\u30EB\u3092\u53D6\u5F97\u4E2D\uFF08ref: ${ref}\uFF09...`);
749
+ const { dir: srcDir } = await fetchSkillSource(ref);
750
+ try {
751
+ for (const tool of targets) {
752
+ await deploySkillTo(srcDir, tool, options);
753
+ }
754
+ logger.success("\u5B8C\u4E86\u3057\u307E\u3057\u305F\u3002");
755
+ } finally {
756
+ cleanupTempDir(srcDir);
757
+ }
758
+ }
759
+ async function deploySkillTo(srcDir, tool, options) {
760
+ const destDir = path6.resolve(process.cwd(), SKILL_PATHS[tool]);
761
+ const existing = fs5.existsSync(destDir);
762
+ if (existing && !options.overwrite) {
763
+ const diff = compareSkillDirs(destDir, srcDir);
764
+ const label = `${tool} (${SKILL_PATHS[tool]})`;
765
+ if (!hasDiff(diff)) {
766
+ logger.log(` \u30B9\u30AD\u30C3\u30D7\uFF08\u5DEE\u5206\u306A\u3057\uFF09: ${label}`);
767
+ return;
768
+ }
769
+ logger.log("");
770
+ logger.log(` ${label} \u306B\u5DEE\u5206\u304C\u3042\u308A\u307E\u3059:`);
771
+ if (diff.modified.length > 0) logger.log(` \u5909\u66F4: ${diff.modified.length} \u30D5\u30A1\u30A4\u30EB`);
772
+ if (diff.added.length > 0) logger.log(` \u8FFD\u52A0: ${diff.added.length} \u30D5\u30A1\u30A4\u30EB`);
773
+ if (diff.localOnly.length > 0) {
774
+ logger.log(` \u524A\u9664: ${diff.localOnly.length} \u30D5\u30A1\u30A4\u30EB\uFF08\u30ED\u30FC\u30AB\u30EB\u306E\u307F\u306B\u5B58\u5728\u3001\u4E0A\u66F8\u304D\u6642\u306B\u524A\u9664\u3055\u308C\u307E\u3059\uFF09`);
775
+ for (const rel of diff.localOnly) logger.log(` - ${rel}`);
776
+ }
777
+ const go = await confirm2({
778
+ message: `${SKILL_PATHS[tool]} \u3092\u4E0A\u66F8\u304D\u3057\u307E\u3059\u304B\uFF1F`,
779
+ default: false
780
+ });
781
+ if (!go) {
782
+ logger.log(` \u30B9\u30AD\u30C3\u30D7: ${tool}`);
783
+ return;
784
+ }
785
+ }
786
+ if (existing) fs5.rmSync(destDir, { recursive: true, force: true });
787
+ copyDirRecursive(srcDir, destDir);
788
+ logger.log(` \u914D\u7F6E: ${tool} \u2192 ${path6.relative(process.cwd(), destDir)}`);
789
+ }
790
+
791
+ // src/commands/skill/check.ts
792
+ import fs6 from "fs";
793
+ import path7 from "path";
794
+ async function skillCheckCommand(options = {}) {
795
+ const cwd = process.cwd();
796
+ const installed = ALL_SKILL_TOOLS.filter((tool) => fs6.existsSync(path7.join(cwd, SKILL_PATHS[tool], "SKILL.md")));
797
+ if (installed.length === 0) {
798
+ logger.info("\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u6E08\u307F\u306E\u30B9\u30AD\u30EB\u306F\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F\u3002`lism skill add` \u3067\u914D\u7F6E\u3067\u304D\u307E\u3059\u3002");
799
+ return;
800
+ }
801
+ const ref = options.ref ?? DEFAULT_SKILL_REF;
802
+ logger.info(`\u30EA\u30E2\u30FC\u30C8\u30B9\u30AD\u30EB\u3092\u53D6\u5F97\u4E2D\uFF08ref: ${ref}\uFF09...`);
803
+ const { dir: remoteDir } = await fetchSkillSource(ref);
804
+ try {
805
+ let outdatedCount = 0;
806
+ for (const tool of installed) {
807
+ const localDir = path7.resolve(cwd, SKILL_PATHS[tool]);
808
+ const diff = compareSkillDirs(localDir, remoteDir);
809
+ const label = `${tool.padEnd(9)} ${SKILL_PATHS[tool]}`;
810
+ if (!hasDiff(diff)) {
811
+ logger.log(` \u2713 ${label} (\u6700\u65B0)`);
812
+ continue;
813
+ }
814
+ outdatedCount += 1;
815
+ logger.warn(` ! ${label}`);
816
+ logger.log(formatDiffSummary(diff));
817
+ if (options.verbose) logger.log(formatDiffDetails(diff));
818
+ }
819
+ logger.log("");
820
+ if (outdatedCount === 0) {
821
+ logger.success("\u3059\u3079\u3066\u306E\u30B9\u30AD\u30EB\u304C\u6700\u65B0\u3067\u3059\u3002");
822
+ } else {
823
+ logger.info(`${outdatedCount} \u4EF6\u306E\u30C4\u30FC\u30EB\u306B\u5DEE\u5206\u304C\u3042\u308A\u307E\u3059\u3002\`lism skill update\` \u3067\u6700\u65B0\u306B\u66F4\u65B0\u3067\u304D\u307E\u3059\u3002`);
824
+ }
825
+ } finally {
826
+ cleanupTempDir(remoteDir);
827
+ }
828
+ }
829
+ function formatDiffSummary(diff) {
830
+ const parts = [];
831
+ if (diff.modified.length > 0) parts.push(`\u5909\u66F4 ${diff.modified.length}`);
832
+ if (diff.added.length > 0) parts.push(`\u8FFD\u52A0 ${diff.added.length}`);
833
+ if (diff.localOnly.length > 0) parts.push(`\u524A\u9664 ${diff.localOnly.length}`);
834
+ return ` ${parts.join(" / ")}`;
835
+ }
836
+ function formatDiffDetails(diff) {
837
+ const lines = [];
838
+ for (const rel of diff.modified) lines.push(` ~ ${rel}`);
839
+ for (const rel of diff.added) lines.push(` + ${rel}`);
840
+ for (const rel of diff.localOnly) lines.push(` - ${rel}`);
841
+ return lines.join("\n");
842
+ }
843
+
844
+ // src/commands/skill/update.ts
845
+ async function skillUpdateCommand(options) {
846
+ await skillAddCommand({ ...options, overwrite: true });
847
+ }
848
+
849
+ // src/commands/skill/index.ts
850
+ function createSkillCommand() {
851
+ const skill = new Command2("skill").description("AI \u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u5411\u3051\u30B9\u30AD\u30EB\uFF08SKILL.md\uFF09\u306E\u914D\u7F6E\u3068\u7BA1\u7406");
852
+ const toolFlags = (cmd) => cmd.option("--all", "\u5168\u30C4\u30FC\u30EB\u306B\u914D\u7F6E").option("--claude", ".claude/skills/ \u306B\u914D\u7F6E").option("--codex", ".agents/skills/ \u306B\u914D\u7F6E").option("--cursor", ".cursor/skills/ \u306B\u914D\u7F6E").option("--windsurf", ".windsurf/skills/ \u306B\u914D\u7F6E").option("--cline", ".cline/skills/ \u306B\u914D\u7F6E").option("--copilot", ".github/skills/ \u306B\u914D\u7F6E").option("--gemini", ".gemini/skills/ \u306B\u914D\u7F6E").option("--junie", ".junie/skills/ \u306B\u914D\u7F6E");
853
+ toolFlags(
854
+ skill.command("add").description("\u30B9\u30AD\u30EB\u3092\u914D\u7F6E\u3059\u308B").option("-o, --overwrite", "\u78BA\u8A8D\u306A\u3057\u3067\u4E0A\u66F8\u304D", false).option("--ref <ref>", "GitHub \u306E\u53D6\u5F97\u5143 ref\uFF08\u30D6\u30E9\u30F3\u30C1\uFF0F\u30BF\u30B0\uFF0F\u30B3\u30DF\u30C3\u30C8\uFF09")
855
+ ).action(skillAddCommand);
856
+ skill.command("check").description("\u914D\u7F6E\u6E08\u307F\u30B9\u30AD\u30EB\u3068\u30EA\u30E2\u30FC\u30C8\u306E\u5DEE\u5206\u3092\u78BA\u8A8D\u3059\u308B").option("--ref <ref>", "GitHub \u306E\u53D6\u5F97\u5143 ref\uFF08\u30D6\u30E9\u30F3\u30C1\uFF0F\u30BF\u30B0\uFF0F\u30B3\u30DF\u30C3\u30C8\uFF09").option("-v, --verbose", "\u30D5\u30A1\u30A4\u30EB\u5358\u4F4D\u306E\u5DEE\u5206\u3092\u8868\u793A").action(skillCheckCommand);
857
+ toolFlags(
858
+ skill.command("update").description("\u914D\u7F6E\u6E08\u307F\u30B9\u30AD\u30EB\u3092\u6700\u65B0\u7248\u3067\u4E0A\u66F8\u304D\u3059\u308B\uFF08--overwrite \u540C\u7B49\uFF09").option("--ref <ref>", "GitHub \u306E\u53D6\u5F97\u5143 ref\uFF08\u30D6\u30E9\u30F3\u30C1\uFF0F\u30BF\u30B0\uFF0F\u30B3\u30DF\u30C3\u30C8\uFF09")
859
+ ).action(skillUpdateCommand);
860
+ return skill;
861
+ }
862
+
863
+ // src/createProgram.ts
864
+ function createLismProgram() {
865
+ const program2 = new Command3();
866
+ program2.name("lism").description("Lism CSS / UI \u5411\u3051 CLI").version(CLI_VERSION);
867
+ program2.command("create").description("examples \u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u304B\u3089\u65B0\u898F\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u751F\u6210\u3059\u308B").argument("[targetDir]", "\u51FA\u529B\u5148\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA").option("-t, --template <name>", "\u4F7F\u7528\u3059\u308B\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u540D\uFF08\u4F8B: astro-minimal\uFF09").option("-f, --force", "\u65E2\u5B58\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u5F37\u5236\u4E0A\u66F8\u304D", false).action(createCommand);
868
+ program2.addCommand(createUiCommand());
869
+ program2.addCommand(createSkillCommand());
870
+ return program2;
871
+ }
872
+
873
+ // src/index.ts
874
+ var program = createLismProgram();
875
+ program.parse();
package/dist/lib.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ interface RunCreateArgs {
2
+ template?: string;
3
+ targetDir?: string;
4
+ force?: boolean;
5
+ }
6
+ /** `lism create` / `create-lism` から共通で使える実体関数 */
7
+ declare function runCreate({ template, targetDir, force }: RunCreateArgs): Promise<void>;
8
+
9
+ export { type RunCreateArgs, runCreate };
package/dist/lib.js ADDED
@@ -0,0 +1,6 @@
1
+ import {
2
+ runCreate
3
+ } from "./chunk-4TQG5MGT.js";
4
+ export {
5
+ runCreate
6
+ };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "lism-cli",
3
+ "version": "0.0.1",
4
+ "description": "CLI for adding Lism UI components to your project",
5
+ "type": "module",
6
+ "bin": {
7
+ "lism": "./bin/lism.mjs"
8
+ },
9
+ "main": "./dist/lib.js",
10
+ "types": "./dist/lib.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/lib.d.ts",
14
+ "default": "./dist/lib.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "bin"
20
+ ],
21
+ "scripts": {
22
+ "format": "prettier --write . --ignore-path ../../.prettierignore",
23
+ "build": "tsup",
24
+ "dev": "tsup --watch",
25
+ "test": "vitest run",
26
+ "prepublishOnly": "pnpm build"
27
+ },
28
+ "dependencies": {
29
+ "@inquirer/prompts": "^7.0.0",
30
+ "commander": "^13.0.0",
31
+ "giget": "^3.2.0",
32
+ "jiti": "^2.6.1",
33
+ "picocolors": "^1.1.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^25.5.0",
37
+ "tsup": "^8.0.0",
38
+ "typescript": "^5.8.3"
39
+ },
40
+ "engines": {
41
+ "node": ">=18.0.0"
42
+ },
43
+ "license": "MIT"
44
+ }