@tinkcarlos/skillora 0.2.2 โ†’ 0.2.4

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.
Files changed (3) hide show
  1. package/CLAUDE.md +215 -0
  2. package/lib/init.js +240 -132
  3. package/package.json +2 -1
package/CLAUDE.md ADDED
@@ -0,0 +1,215 @@
1
+ # CLAUDE.md
2
+
3
+ ## ๐ŸŒ ่ฏญ่จ€่ฎพ็ฝฎ (Language Setting)
4
+
5
+ **ๅผบๅˆถ่ฆๆฑ‚**: ๆ‰€ๆœ‰่พ“ๅ‡บๅ†…ๅฎนๅฟ…้กปไฝฟ็”จ**็ฎ€ไฝ“ไธญๆ–‡**๏ผŒๅŒ…ๆ‹ฌไฝ†ไธ้™ไบŽ๏ผš
6
+ - ไปปๅŠกๅˆ†ๆžๅ’Œ่ฎกๅˆ’ (Task Analysis & Plan)
7
+ - Todoๅˆ—่กจ (Todo List)
8
+ - ๆŠ€่ƒฝๅˆ‡ๆข้€š็Ÿฅ (Skill Switch Notification)
9
+ - ไปปๅŠกๅฎŒๆˆๆŠฅๅ‘Š (Completion Report)
10
+ - ้—ฎ้ข˜ๆพ„ๆธ…ๅ’Œ็กฎ่ฎค (Clarification & Confirmation)
11
+
12
+ ## Core Decision Flow (Execute on EVERY user input)
13
+
14
+ ```
15
+ Step 1: Task Type Matching (Highest Priority)
16
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
17
+ โ”‚ Keywords โ†’ Force Skill (regardless of complexity) โ”‚
18
+ โ”‚ โ”œโ”€ fix/bug/error/exception/broken โ†’ @bug-fixing โ”‚
19
+ โ”‚ โ”œโ”€ develop/feature/implement/add โ†’ @fullstack-developer โ”‚
20
+ โ”‚ โ”œโ”€ optimize/refactor/improve โ†’ @fullstack-developer โ”‚
21
+ โ”‚ โ”œโ”€ review/check code/PR โ†’ @code-review โ”‚
22
+ โ”‚ โ”œโ”€ frontend/UI/interface/style โ†’ @frontend-design โ”‚
23
+ โ”‚ โ””โ”€ Other โ†’ Go to Step 2 โ”‚
24
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
25
+ โ†“
26
+ Step 2: Complexity Assessment (Only when Step 1 doesn't match)
27
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
28
+ โ”‚ Steps โ†’ Action โ”‚
29
+ โ”‚ โ”œโ”€ 1-2 steps โ†’ Simple โ†’ Execute directly โ”‚
30
+ โ”‚ โ”œโ”€ 3-5 steps โ†’ Medium โ†’ Must create Todo + Recommend Skill โ”‚
31
+ โ”‚ โ””โ”€ 5+ steps โ†’ Complex โ†’ Must Todo + Must Skill + Must MCP โ”‚
32
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
33
+ ```
34
+
35
+ ## Skill Mapping Table
36
+
37
+ ### Mandatory Skills (Must invoke when matched)
38
+
39
+ | Task Type | Skill | MCP Tools | Closed-loop |
40
+ |-----------|-------|-----------|-------------|
41
+ | Bug Fix | `@bug-fixing` | serena | โ†’ @code-review โ†’ loop |
42
+ | Feature Dev | `@fullstack-developer` | sequential-thinking + serena | โ†’ @code-review โ†’ loop |
43
+ | Code Review | `@code-review` | serena | Issues โ†’ @bug-fixing |
44
+ | Frontend UI | `@frontend-design` | context7 | โ†’ @fullstack-developer โ†’ @code-review |
45
+ | Test Design | `@test-architect-live` | sequential-thinking | - |
46
+ | Documentation | `@doc-coauthoring` | memory | - |
47
+ | Requirements | `@product-requirements` | sequential-thinking | - |
48
+ | Project Arch | `@project-development` | sequential-thinking + memory | - |
49
+
50
+ ### Implicit Intent Recognition
51
+
52
+ | User Pattern | Auto-invoke |
53
+ |--------------|-------------|
54
+ | Pastes error log / stack trace | `@bug-fixing` |
55
+ | Shares code + "help me look" | `@code-review` |
56
+ | "How to implement..." | `@fullstack-developer` |
57
+ | Mentions new component/page | `@fullstack-developer` + `@frontend-design` |
58
+ | Discusses API/endpoint | `@api-design-principles` |
59
+ | Mentions database/migration | `@database-migration` |
60
+
61
+ ### Sub-task Triggers (Auto-switch during main task)
62
+
63
+ | Scenario | Auto-invoke |
64
+ |----------|-------------|
65
+ | DB/table changes | `@database-migration` |
66
+ | New API endpoint | `@api-design-principles` |
67
+ | UI component work | `@frontend-design` |
68
+ | Auth/security | `@auth-implementation-patterns` |
69
+ | FastAPI | `@fastapi-templates` |
70
+ | LangChain/LLM | `@langchain-architecture` |
71
+ | React state | `@react-state-management` |
72
+
73
+ ### File Type Skills
74
+
75
+ | Type | Skill | Type | Skill |
76
+ |------|-------|------|-------|
77
+ | .docx | `@docx` | .pptx | `@pptx` |
78
+ | .xlsx/.csv | `@xlsx` | .pdf | `@pdf` |
79
+
80
+ ## MCP Tools Rules
81
+
82
+ ### Iron Rule: MCP = Read-Only
83
+
84
+ All MCP tools are for **reading and analysis only**. Code editing must use Claude Code native Edit/Write tools.
85
+
86
+ ### Tool Selection
87
+
88
+ | Scenario | Use Tool |
89
+ |----------|----------|
90
+ | Plan complex tasks (โ‰ฅ3 steps) | `sequential-thinking` |
91
+ | Query library/API docs | `context7` |
92
+ | Search best practices | `Exa Search` |
93
+ | Analyze code structure | `serena` (read-only) |
94
+ | Save/retrieve context | `memory` |
95
+
96
+ ### Forbidden serena Operations
97
+
98
+ ```
99
+ โŒ replace_symbol_body
100
+ โŒ insert_after_symbol
101
+ โŒ insert_before_symbol
102
+ โŒ rename_symbol
103
+ ```
104
+
105
+ ## Workflow Rules
106
+
107
+ ### Closed-loop (Mandatory for code changes)
108
+
109
+ ```
110
+ Main Skill โ†’ @code-review โ†’ (issues? โ†’ @bug-fixing โ†’ @code-review)* โ†’ Summary
111
+ ```
112
+
113
+ ### Skill Switch Notification (Required)
114
+
115
+ ```
116
+ ๐Ÿ”„ Skill Switch: [@current] โ†’ [@new]
117
+ Reason: [Brief explanation]
118
+ ```
119
+
120
+ ### Task Completion Report (Required)
121
+
122
+ ```
123
+ ๐Ÿ“Š Tool Report
124
+ - Skills: [@skill1], [@skill2]
125
+ - MCP: [tool1], [tool2]
126
+ - Chain: @main โ†’ @sub โ†’ @code-review
127
+ ```
128
+
129
+ ## Core Rules
130
+
131
+ | Rule | Description |
132
+ |------|-------------|
133
+ | **ไธญๆ–‡่พ“ๅ‡บ** | ๆ‰€ๆœ‰ๅฏน่ฏๅ†…ๅฎนๅฟ…้กปไฝฟ็”จไธญๆ–‡๏ผŒๅŒ…ๆ‹ฌไปปๅŠกๅˆ—่กจใ€่ฎกๅˆ’ใ€ๅˆ†ๆžใ€ๆŠฅๅ‘Š |
134
+ | Check Every Input | Re-evaluate task type on each user message |
135
+ | Task Type > Complexity | Even 1-step bug fix must use @bug-fixing |
136
+ | Analyze Before Action | Output analysis before coding |
137
+ | Ask Don't Guess | Clarify when uncertain |
138
+ | Transparent Output | Notify on skill switch, report on completion |
139
+
140
+ ## Skills System
141
+
142
+ ### How to Invoke Skills
143
+
144
+ Use `Skill` tool to load skills. Example: invoke skill "bug-fixing"
145
+
146
+ ### Core Skills (15)
147
+
148
+ | Category | Skill | Triggers |
149
+ |----------|-------|----------|
150
+ | Dev Core | `@bug-fixing` | bug, error, fix |
151
+ | | `@fullstack-developer` | feature, develop |
152
+ | | `@code-review` | review, PR |
153
+ | Frontend | `@frontend-design` | UI, component |
154
+ | | `@tailwind-design-system` | tailwind, CSS |
155
+ | Backend | `@api-design-principles` | API, REST |
156
+ | | `@database-migration` | DB, migration |
157
+ | Docs | `@doc-coauthoring` | documentation |
158
+ | | `@product-requirements` | PRD, requirements |
159
+ | | `@project-development` | architecture |
160
+ | Testing | `@test-architect-live` | test cases |
161
+ | | `@webapp-testing` | E2E, browser test |
162
+ | Files | `@docx` `@xlsx` `@pdf` `@pptx` | file types |
163
+
164
+ ### Extended Skills (85+)
165
+
166
+ Run `openskills list` or `openskills search <query>` to discover more skills.
167
+
168
+ Categories: Data Engineering, Cloud Infrastructure, CI/CD, Security, LLM/AI, Frontend, Backend, Observability, Blockchain
169
+
170
+ ## Python Development
171
+
172
+ Always use virtual environment for Python projects:
173
+ ```bash
174
+ # Windows
175
+ python -m venv venv
176
+ venv\Scripts\activate
177
+ pip install -r requirements.txt
178
+ venv\Scripts\python.exe script.py
179
+ ```
180
+
181
+ ## Hooks ่‡ชๅŠจ่งฆๅ‘่ง„ๅˆ™
182
+
183
+ ### Skill ่ฐƒ็”จๆ—ถ
184
+
185
+ | Hook | ่งฆๅ‘ๆ—ถๆœบ | ไฝœ็”จ |
186
+ |------|----------|------|
187
+ | PreToolUse:Skill | Skill ่ฐƒ็”จๅ‰ | ่ฎฐๅฝ•่ฐƒ็”จใ€ๆ้†’้”š็‚น |
188
+ | PostToolUse:Skill | Skill ่ฐƒ็”จๅŽ | ้ชŒ่ฏๅฎŒๆˆใ€ๆฃ€ๆŸฅๅ็ฆป |
189
+
190
+ ### Subagent ๆดพๅ‘ๆ—ถ
191
+
192
+ | Hook | ่งฆๅ‘ๆ—ถๆœบ | ไฝœ็”จ |
193
+ |------|----------|------|
194
+ | PreToolUse:Task | Subagent ๆดพๅ‘ๅ‰ | ๆฃ€ๆŸฅ้”š็‚นๅฎŒๆ•ดๆ€ง |
195
+ | PostToolUse:Task | Subagent ่ฟ”ๅ›žๅŽ | ้ชŒ่ฏๅ็ฆปใ€ๆ›ดๆ–ฐ่ฟ›ๅบฆ |
196
+ | SubagentStop | Subagent ๅœๆญขๆ—ถ | ๆขๅคไธŠไธ‹ๆ–‡ใ€่พ“ๅ‡บๆฃ€ๆŸฅๆธ…ๅ• |
197
+
198
+ ### Hook ่พ“ๅ‡บ่งฃ่ฏป
199
+
200
+ | ่พ“ๅ‡บ | ๅซไน‰ | ๅค„็† |
201
+ |------|------|------|
202
+ | `โš ๏ธ WARNING: missing context anchor` | Subagent ็ผบๅฐ‘้”š็‚น | ้‡ๆ–ฐๆดพๅ‘๏ผŒๆทปๅŠ ้”š็‚น |
203
+ | `๐Ÿ”„ Subagent completed` | Subagent ๆญฃๅธธๅฎŒๆˆ | ้ชŒ่ฏ่Œƒๅ›ด๏ผŒ็ปง็ปญไปปๅŠก |
204
+ | `๐Ÿ“‹ Checklist` | ๆฃ€ๆŸฅๆธ…ๅ• | ้€้กน้ชŒ่ฏ |
205
+
206
+ ### ๆ—ฅๅฟ—ๆ–‡ไปถ
207
+
208
+ | ๆ–‡ไปถ | ๅ†…ๅฎน |
209
+ |------|------|
210
+ | `~/.claude/skill-invocation-log.jsonl` | Skill ่ฐƒ็”จ่ฎฐๅฝ• |
211
+ | `~/.claude/subagent-dispatch-log.jsonl` | Subagent ๆดพๅ‘่ฎฐๅฝ• |
212
+
213
+ ### Subagent ่ฐƒ็”จๆจกๆฟ
214
+
215
+ ่ฏฆ่ง `.claude/skills/shared-references/subagent-dispatch-templates.md`
package/lib/init.js CHANGED
@@ -6,12 +6,12 @@ const os = require("os");
6
6
  const readline = require("readline");
7
7
 
8
8
  const PLATFORMS = [
9
- { name: "Claude Code", value: "claude-code", native: true },
10
- { name: "Cursor", value: "cursor" },
11
- { name: "Codex/GPT", value: "codex" },
12
- { name: "Gemini", value: "gemini" },
13
- { name: "Windsurf", value: "windsurf" },
14
- { name: "Aider", value: "aider" },
9
+ { name: "Claude Code", value: "claude-code", selected: true },
10
+ { name: "Cursor", value: "cursor", selected: false },
11
+ { name: "Codex/GPT", value: "codex", selected: false },
12
+ { name: "Gemini", value: "gemini", selected: false },
13
+ { name: "Windsurf", value: "windsurf", selected: false },
14
+ { name: "Aider", value: "aider", selected: false },
15
15
  ];
16
16
 
17
17
  const INSTALL_MODES = [
@@ -20,50 +20,126 @@ const INSTALL_MODES = [
20
20
  { name: "ๅ…จๅฑ€ๆจกๅผ (~/.claude/skills/ - ๆ‰€ๆœ‰้กน็›ฎๅ…ฑไบซ)", value: "global" },
21
21
  ];
22
22
 
23
- function createReadline() {
24
- return readline.createInterface({
25
- input: process.stdin,
26
- output: process.stdout,
27
- });
23
+ function clearLines(count) {
24
+ for (let i = 0; i < count; i++) {
25
+ process.stdout.write("\x1b[1A\x1b[2K");
26
+ }
28
27
  }
29
28
 
30
- async function question(rl, prompt) {
31
- return new Promise((resolve, reject) => {
32
- rl.question(prompt, resolve);
33
- rl.once("close", () => reject(new Error("User cancelled")));
29
+ function renderMultiSelect(title, options, cursor) {
30
+ const lines = [`\n${title}`];
31
+ options.forEach((opt, i) => {
32
+ const pointer = i === cursor ? ">" : " ";
33
+ const check = opt.selected ? "[โœ“]" : "[ ]";
34
+ lines.push(` ${pointer} ${check} ${opt.name}`);
34
35
  });
36
+ lines.push("\n โ†‘โ†“ ็งปๅŠจ ็ฉบๆ ผ ้€‰ๆ‹ฉ ๅ›ž่ฝฆ ็กฎ่ฎค");
37
+ return lines;
35
38
  }
36
39
 
37
- async function selectMultiple(rl, title, options) {
38
- console.log(`\n${title}`);
40
+ function renderSingleSelect(title, options, cursor) {
41
+ const lines = [`\n${title}`];
39
42
  options.forEach((opt, i) => {
40
- const marker = opt.native ? "โ—‰" : "โ—ฏ";
41
- console.log(` ${i + 1}. [${marker}] ${opt.name}`);
43
+ const pointer = i === cursor ? ">" : " ";
44
+ const radio = i === cursor ? "(โ—)" : "( )";
45
+ lines.push(` ${pointer} ${radio} ${opt.name}`);
42
46
  });
43
- console.log("\n่พ“ๅ…ฅๆ•ฐๅญ—้€‰ๆ‹ฉ (ๅคš้€‰็”จ้€—ๅทๅˆ†้š”, ๅฆ‚: 1,2), ๅ›ž่ฝฆ็กฎ่ฎค:");
47
+ lines.push("\n โ†‘โ†“ ็งปๅŠจ ๅ›ž่ฝฆ ็กฎ่ฎค");
48
+ return lines;
49
+ }
44
50
 
45
- const answer = await question(rl, "> ");
46
- if (!answer.trim()) {
47
- return options.filter((o) => o.native).map((o) => o.value);
48
- }
51
+ async function selectMultiple(title, options) {
52
+ return new Promise((resolve) => {
53
+ const opts = options.map((o) => ({ ...o }));
54
+ let cursor = 0;
55
+ let lines = renderMultiSelect(title, opts, cursor);
49
56
 
50
- const indices = answer.split(",").map((s) => parseInt(s.trim(), 10) - 1);
51
- return indices
52
- .filter((i) => i >= 0 && i < options.length)
53
- .map((i) => options[i].value);
54
- }
57
+ process.stdout.write(lines.join("\n") + "\n");
55
58
 
56
- async function selectSingle(rl, title, options) {
57
- console.log(`\n${title}`);
58
- options.forEach((opt, i) => {
59
- const marker = i === 0 ? "โ—‰" : "โ—ฏ";
60
- console.log(` ${i + 1}. [${marker}] ${opt.name}`);
59
+ if (!process.stdin.isTTY) {
60
+ resolve(opts.filter((o) => o.selected).map((o) => o.value));
61
+ return;
62
+ }
63
+
64
+ readline.emitKeypressEvents(process.stdin);
65
+ process.stdin.setRawMode(true);
66
+
67
+ const onKeypress = (str, key) => {
68
+ if (key.ctrl && key.name === "c") {
69
+ process.stdin.setRawMode(false);
70
+ process.stdin.removeListener("keypress", onKeypress);
71
+ process.exit(0);
72
+ }
73
+
74
+ if (key.name === "up") {
75
+ cursor = cursor > 0 ? cursor - 1 : opts.length - 1;
76
+ } else if (key.name === "down") {
77
+ cursor = cursor < opts.length - 1 ? cursor + 1 : 0;
78
+ } else if (key.name === "space") {
79
+ opts[cursor].selected = !opts[cursor].selected;
80
+ } else if (key.name === "return") {
81
+ process.stdin.setRawMode(false);
82
+ process.stdin.removeListener("keypress", onKeypress);
83
+ clearLines(lines.length);
84
+ const selected = opts.filter((o) => o.selected).map((o) => o.value);
85
+ console.log(`${title}`);
86
+ console.log(` ๅทฒ้€‰ๆ‹ฉ: ${selected.length > 0 ? selected.join(", ") : "(ๆ— )"}\n`);
87
+ resolve(selected);
88
+ return;
89
+ }
90
+
91
+ clearLines(lines.length);
92
+ lines = renderMultiSelect(title, opts, cursor);
93
+ process.stdout.write(lines.join("\n") + "\n");
94
+ };
95
+
96
+ process.stdin.on("keypress", onKeypress);
61
97
  });
62
- console.log("\n่พ“ๅ…ฅๆ•ฐๅญ—้€‰ๆ‹ฉ (้ป˜่ฎค: 1):");
98
+ }
99
+
100
+ async function selectSingle(title, options) {
101
+ return new Promise((resolve) => {
102
+ let cursor = 0;
103
+ let lines = renderSingleSelect(title, options, cursor);
104
+
105
+ process.stdout.write(lines.join("\n") + "\n");
106
+
107
+ if (!process.stdin.isTTY) {
108
+ resolve(options[0].value);
109
+ return;
110
+ }
111
+
112
+ readline.emitKeypressEvents(process.stdin);
113
+ process.stdin.setRawMode(true);
114
+
115
+ const onKeypress = (str, key) => {
116
+ if (key.ctrl && key.name === "c") {
117
+ process.stdin.setRawMode(false);
118
+ process.stdin.removeListener("keypress", onKeypress);
119
+ process.exit(0);
120
+ }
121
+
122
+ if (key.name === "up") {
123
+ cursor = cursor > 0 ? cursor - 1 : options.length - 1;
124
+ } else if (key.name === "down") {
125
+ cursor = cursor < options.length - 1 ? cursor + 1 : 0;
126
+ } else if (key.name === "return") {
127
+ process.stdin.setRawMode(false);
128
+ process.stdin.removeListener("keypress", onKeypress);
129
+ clearLines(lines.length);
130
+ console.log(`${title}`);
131
+ console.log(` ๅทฒ้€‰ๆ‹ฉ: ${options[cursor].name}\n`);
132
+ resolve(options[cursor].value);
133
+ return;
134
+ }
63
135
 
64
- const answer = await question(rl, "> ");
65
- const index = answer.trim() ? parseInt(answer.trim(), 10) - 1 : 0;
66
- return options[Math.max(0, Math.min(index, options.length - 1))].value;
136
+ clearLines(lines.length);
137
+ lines = renderSingleSelect(title, options, cursor);
138
+ process.stdout.write(lines.join("\n") + "\n");
139
+ };
140
+
141
+ process.stdin.on("keypress", onKeypress);
142
+ });
67
143
  }
68
144
 
69
145
  function getTargetDir(mode) {
@@ -97,9 +173,96 @@ function listSkillDirs(rootDir) {
97
173
  .filter((d) => fs.existsSync(path.join(d, "SKILL.md")));
98
174
  }
99
175
 
100
- function generateClaudeMd(skills) {
176
+ function parseFrontmatter(content) {
177
+ if (!content || !content.startsWith("---")) return {};
178
+ const endIndex = content.indexOf("\n---", 3);
179
+ if (endIndex === -1) return {};
180
+
181
+ const raw = content.slice(3, endIndex).trim();
182
+ const data = {};
183
+ let currentKey = null;
184
+ let multilineValue = [];
185
+
186
+ for (const line of raw.split(/\r?\n/)) {
187
+ const indented = line.startsWith(" ") || line.startsWith("\t");
188
+ if (indented && currentKey) {
189
+ multilineValue.push(line.trim());
190
+ continue;
191
+ }
192
+
193
+ if (currentKey && multilineValue.length > 0) {
194
+ data[currentKey] = multilineValue.join(" ").trim();
195
+ multilineValue = [];
196
+ }
197
+
198
+ const idx = line.indexOf(":");
199
+ if (idx === -1) continue;
200
+ const key = line.slice(0, idx).trim();
201
+ let value = line.slice(idx + 1).trim();
202
+
203
+ if (value === "|" || value === ">") {
204
+ currentKey = key;
205
+ multilineValue = [];
206
+ continue;
207
+ }
208
+
209
+ if (
210
+ (value.startsWith('"') && value.endsWith('"')) ||
211
+ (value.startsWith("'") && value.endsWith("'"))
212
+ ) {
213
+ value = value.slice(1, -1);
214
+ }
215
+ if (key && value) data[key] = value;
216
+ currentKey = null;
217
+ }
218
+
219
+ if (currentKey && multilineValue.length > 0) {
220
+ data[currentKey] = multilineValue.join(" ").trim();
221
+ }
222
+
223
+ return data;
224
+ }
225
+
226
+ function readSkillMeta(skillDir) {
227
+ try {
228
+ const content = fs.readFileSync(path.join(skillDir, "SKILL.md"), "utf8");
229
+ const fm = parseFrontmatter(content);
230
+ return {
231
+ name: fm.name || path.basename(skillDir),
232
+ description: fm.description || "",
233
+ userInvocable: fm["user-invocable"] !== "false",
234
+ };
235
+ } catch (err) {
236
+ console.error(`Warning: Failed to read ${skillDir}: ${err.message}`);
237
+ return {
238
+ name: path.basename(skillDir),
239
+ description: "",
240
+ userInvocable: true,
241
+ };
242
+ }
243
+ }
244
+
245
+ function generateClaudeMdFromTemplate(templatePath, skills) {
101
246
  const invocableSkills = skills.filter((s) => s.userInvocable);
102
- const slashCommands = invocableSkills.map((s) => `/ora/${s.name}`).join(", ");
247
+ const slashCommands = invocableSkills.map((s) => `/ora/${s.name}`);
248
+
249
+ if (fs.existsSync(templatePath)) {
250
+ let content = fs.readFileSync(templatePath, "utf8");
251
+ // ๆ›ฟๆข openskills ไธบ skillora
252
+ content = content.replace(/openskills/g, "skillora");
253
+ // ๆ›ดๆ–ฐๆŠ€่ƒฝๆ•ฐ้‡
254
+ content = content.replace(
255
+ /Core Skills \(\d+\)/,
256
+ `Core Skills (${skills.length})`
257
+ );
258
+ content = content.replace(
259
+ /Extended Skills \(\d+\+\)/,
260
+ `Extended Skills (${skills.length}+)`
261
+ );
262
+ return content;
263
+ }
264
+
265
+ // ๅฆ‚ๆžœๆจกๆฟไธๅญ˜ๅœจ๏ผŒ็”Ÿๆˆ็ฎ€ๅŒ–็‰ˆๆœฌ
103
266
  return `# CLAUDE.md
104
267
 
105
268
  ## Skills System
@@ -108,7 +271,7 @@ function generateClaudeMd(skills) {
108
271
 
109
272
  ### ๆ–œๆ ๅ‘ฝไปค๏ผˆๆŽจ่๏ผ‰
110
273
 
111
- ${slashCommands || "(ๆ— ๅฏ่ฐƒ็”จๆŠ€่ƒฝ)"}
274
+ ${slashCommands.join(", ")}
112
275
 
113
276
  ### CLI ๅ‘ฝไปค
114
277
 
@@ -118,6 +281,8 @@ ${slashCommands || "(ๆ— ๅฏ่ฐƒ็”จๆŠ€่ƒฝ)"}
118
281
  }
119
282
 
120
283
  function generateCursorRules(skills) {
284
+ const invocableSkills = skills.filter((s) => s.userInvocable);
285
+ const slashCommands = invocableSkills.map((s) => `/ora/${s.name}`).join(", ");
121
286
  const skillList = skills
122
287
  .map((s) => `- ${s.name}: ${s.description}`)
123
288
  .join("\n");
@@ -127,6 +292,10 @@ function generateCursorRules(skills) {
127
292
 
128
293
  ${skillList}
129
294
 
295
+ ## Slash Commands (Claude Code)
296
+
297
+ ${slashCommands}
298
+
130
299
  ## Usage
131
300
 
132
301
  ไฝฟ็”จ \`skillora read <skill-name>\` ๅŠ ่ฝฝๆŠ€่ƒฝ่ฏฆๆƒ…ใ€‚
@@ -171,75 +340,6 @@ ${skillTable}
171
340
  `;
172
341
  }
173
342
 
174
- function parseFrontmatter(content) {
175
- if (!content || !content.startsWith("---")) return {};
176
- const endIndex = content.indexOf("\n---", 3);
177
- if (endIndex === -1) return {};
178
-
179
- const raw = content.slice(3, endIndex).trim();
180
- const data = {};
181
- let currentKey = null;
182
- let multilineValue = [];
183
-
184
- for (const line of raw.split(/\r?\n/)) {
185
- const indented = line.startsWith(" ") || line.startsWith("\t");
186
- if (indented && currentKey) {
187
- multilineValue.push(line.trim());
188
- continue;
189
- }
190
-
191
- if (currentKey && multilineValue.length > 0) {
192
- data[currentKey] = multilineValue.join(" ").trim();
193
- multilineValue = [];
194
- }
195
-
196
- const idx = line.indexOf(":");
197
- if (idx === -1) continue;
198
- const key = line.slice(0, idx).trim();
199
- let value = line.slice(idx + 1).trim();
200
-
201
- if (value === "|" || value === ">") {
202
- currentKey = key;
203
- multilineValue = [];
204
- continue;
205
- }
206
-
207
- if (
208
- (value.startsWith('"') && value.endsWith('"')) ||
209
- (value.startsWith("'") && value.endsWith("'"))
210
- ) {
211
- value = value.slice(1, -1);
212
- }
213
- if (key && value) data[key] = value;
214
- currentKey = null;
215
- }
216
-
217
- if (currentKey && multilineValue.length > 0) {
218
- data[currentKey] = multilineValue.join(" ").trim();
219
- }
220
-
221
- return data;
222
- }
223
-
224
- function readSkillMeta(skillDir) {
225
- try {
226
- const content = fs.readFileSync(path.join(skillDir, "SKILL.md"), "utf8");
227
- const fm = parseFrontmatter(content);
228
- return {
229
- name: fm.name || path.basename(skillDir),
230
- description: fm.description || "",
231
- userInvocable: fm["user-invocable"] !== "false",
232
- };
233
- } catch (err) {
234
- console.error(`Warning: Failed to read ${skillDir}: ${err.message}`);
235
- return {
236
- name: path.basename(skillDir),
237
- description: "",
238
- userInvocable: true,
239
- };
240
- }
241
- }
242
-
243
343
  async function runInit(options, bundledSkillsDir) {
244
344
  let platforms = options.platform
245
345
  ? options.platform.split(",").map((s) => s.trim())
@@ -258,22 +358,20 @@ async function runInit(options, bundledSkillsDir) {
258
358
  }
259
359
 
260
360
  // ไบคไบ’ๅผ้€‰ๆ‹ฉ
261
- let rl = null;
262
361
  if (!platforms || !mode) {
263
- rl = createReadline();
264
- try {
265
- if (!platforms) {
266
- platforms = await selectMultiple(rl, "้€‰ๆ‹ฉ่ฆ้…็ฝฎ็š„ๅนณๅฐ:", PLATFORMS);
267
- }
268
- if (!mode) {
269
- mode = await selectSingle(rl, "้€‰ๆ‹ฉๅฎ‰่ฃ…ๆจกๅผ:", INSTALL_MODES);
270
- }
271
- } finally {
272
- rl.close();
362
+ if (!platforms) {
363
+ platforms = await selectMultiple("้€‰ๆ‹ฉ่ฆ้…็ฝฎ็š„ๅนณๅฐ:", PLATFORMS);
364
+ }
365
+ if (!mode) {
366
+ mode = await selectSingle("้€‰ๆ‹ฉๅฎ‰่ฃ…ๆจกๅผ:", INSTALL_MODES);
273
367
  }
274
368
  }
275
369
 
276
- console.log("\nๆญฃๅœจ้…็ฝฎ...\n");
370
+ if (platforms.length === 0) {
371
+ platforms = ["claude-code"];
372
+ }
373
+
374
+ console.log("ๆญฃๅœจ้…็ฝฎ...\n");
277
375
 
278
376
  // ็กฎๅฎš็›ฎๆ ‡็›ฎๅฝ•
279
377
  const targetDir = getTargetDir(mode);
@@ -299,21 +397,29 @@ async function runInit(options, bundledSkillsDir) {
299
397
  const skills = installedSkillDirs.map(readSkillMeta);
300
398
 
301
399
  const cwd = process.cwd();
400
+ const packageRoot = path.resolve(__dirname, "..");
302
401
  const created = [];
303
402
 
304
403
  // ็”Ÿๆˆๅนณๅฐ้…็ฝฎๆ–‡ไปถ
305
404
  if (platforms.includes("claude-code")) {
306
405
  const claudeMdPath = path.join(cwd, "CLAUDE.md");
307
- if (!fs.existsSync(claudeMdPath)) {
308
- fs.writeFileSync(claudeMdPath, generateClaudeMd(skills), "utf8");
406
+ if (!fs.existsSync(claudeMdPath) || options.force) {
407
+ const templatePath = path.join(packageRoot, "CLAUDE.md");
408
+ fs.writeFileSync(
409
+ claudeMdPath,
410
+ generateClaudeMdFromTemplate(templatePath, skills),
411
+ "utf8"
412
+ );
309
413
  created.push("CLAUDE.md");
310
414
  }
311
415
  }
312
416
 
313
417
  if (platforms.includes("cursor")) {
314
418
  const cursorPath = path.join(cwd, ".cursorrules");
315
- fs.writeFileSync(cursorPath, generateCursorRules(skills), "utf8");
316
- created.push(".cursorrules");
419
+ if (!fs.existsSync(cursorPath) || options.force) {
420
+ fs.writeFileSync(cursorPath, generateCursorRules(skills), "utf8");
421
+ created.push(".cursorrules");
422
+ }
317
423
  }
318
424
 
319
425
  const needsAgentsMd = platforms.some((p) =>
@@ -322,12 +428,14 @@ async function runInit(options, bundledSkillsDir) {
322
428
 
323
429
  if (needsAgentsMd) {
324
430
  const agentsPath = path.join(cwd, "AGENTS.md");
325
- fs.writeFileSync(
326
- agentsPath,
327
- generateAgentsMd(skills, "skillora"),
328
- "utf8",
329
- );
330
- created.push("AGENTS.md");
431
+ if (!fs.existsSync(agentsPath) || options.force) {
432
+ fs.writeFileSync(
433
+ agentsPath,
434
+ generateAgentsMd(skills, "skillora"),
435
+ "utf8",
436
+ );
437
+ created.push("AGENTS.md");
438
+ }
331
439
  }
332
440
 
333
441
  // ่พ“ๅ‡บ็ป“ๆžœ
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinkcarlos/skillora",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "CLI installer for bundled Claude Code skills (SKILL.md format)",
5
5
  "bin": {
6
6
  "skillora": "bin/cli.js"
@@ -9,6 +9,7 @@
9
9
  "bin",
10
10
  "lib",
11
11
  ".claude/skills",
12
+ "CLAUDE.md",
12
13
  "README.md"
13
14
  ],
14
15
  "keywords": [