@getskillmd/cli 0.1.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/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/cli.js +430 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tekcify
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# @getskillmd/cli
|
|
2
|
+
|
|
3
|
+
Install `skill.md` files into Claude Code, Cursor, and Windsurf with one command.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
No install needed — use `npx`:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @getskillmd/cli add stripe
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or install globally:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm i -g @getskillmd/cli
|
|
17
|
+
getskillmd add stripe
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Add a skill by slug
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx @getskillmd/cli add stripe
|
|
26
|
+
npx @getskillmd/cli add tailwind --ide cursor
|
|
27
|
+
npx @getskillmd/cli add nextjs --ide all
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Generate from a URL
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx @getskillmd/cli add https://stripe.com/docs/api
|
|
34
|
+
npx @getskillmd/cli add https://nextjs.org/docs --mode library
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Modes: `design` (default), `api`, `library`, `generic`.
|
|
38
|
+
|
|
39
|
+
### List installed skills
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npx @getskillmd/cli list
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## IDE behavior
|
|
46
|
+
|
|
47
|
+
| IDE | Path written | Notes |
|
|
48
|
+
| ------------ | ------------------------------------- | ---------------------------------- |
|
|
49
|
+
| Claude Code | `.claude/skills/<slug>/SKILL.md` | Directory created if missing |
|
|
50
|
+
| Cursor | `.cursor/rules/<slug>.mdc` | Wrapped with Cursor frontmatter |
|
|
51
|
+
| Windsurf | `.windsurfrules` | Appended in a labelled section |
|
|
52
|
+
|
|
53
|
+
If `--ide` is not provided, the CLI detects which config dirs already exist
|
|
54
|
+
in your project. If multiple are found (or none), you are prompted.
|
|
55
|
+
|
|
56
|
+
## Environment
|
|
57
|
+
|
|
58
|
+
- `GETSKILLMD_API_URL` — override the API base URL. Defaults to `https://getskillmd.com`.
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
MIT
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import kleur from "kleur";
|
|
6
|
+
import prompts from "prompts";
|
|
7
|
+
|
|
8
|
+
// src/constants.ts
|
|
9
|
+
var DEFAULT_API_URL = "https://getskillmd.com";
|
|
10
|
+
var POLL_INTERVAL_MS = 2e3;
|
|
11
|
+
var POLL_TIMEOUT_MS = 24e4;
|
|
12
|
+
var REQUEST_TIMEOUT_MS = 3e4;
|
|
13
|
+
var IDE_LABELS = {
|
|
14
|
+
claude: "Claude Code",
|
|
15
|
+
cursor: "Cursor",
|
|
16
|
+
windsurf: "Windsurf"
|
|
17
|
+
};
|
|
18
|
+
var IDE_PATHS = {
|
|
19
|
+
claude: ".claude/skills",
|
|
20
|
+
cursor: ".cursor/rules",
|
|
21
|
+
windsurf: ".windsurfrules"
|
|
22
|
+
};
|
|
23
|
+
var WINDSURF_SECTION_HEADER_PREFIX = "# getskillmd:";
|
|
24
|
+
var WINDSURF_SECTION_FOOTER_PREFIX = "# end getskillmd:";
|
|
25
|
+
var VALID_MODES = ["design", "api", "library", "generic"];
|
|
26
|
+
var VALID_IDES = ["claude", "cursor", "windsurf", "all"];
|
|
27
|
+
|
|
28
|
+
// src/api.ts
|
|
29
|
+
function getBaseUrl() {
|
|
30
|
+
const fromEnv = process.env.GETSKILLMD_API_URL;
|
|
31
|
+
if (fromEnv && fromEnv.trim().length > 0) {
|
|
32
|
+
return fromEnv.replace(/\/$/, "");
|
|
33
|
+
}
|
|
34
|
+
return DEFAULT_API_URL;
|
|
35
|
+
}
|
|
36
|
+
async function request(path, init = {}, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
37
|
+
const controller = new AbortController();
|
|
38
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetch(`${getBaseUrl()}${path}`, {
|
|
41
|
+
...init,
|
|
42
|
+
signal: controller.signal,
|
|
43
|
+
headers: {
|
|
44
|
+
"Content-Type": "application/json",
|
|
45
|
+
Accept: "application/json",
|
|
46
|
+
"User-Agent": "getskillmd-cli",
|
|
47
|
+
...init.headers ?? {}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
const text = await response.text().catch(() => "");
|
|
52
|
+
let message = `Request failed (${response.status})`;
|
|
53
|
+
try {
|
|
54
|
+
const parsed = JSON.parse(text);
|
|
55
|
+
message = parsed.error ?? parsed.message ?? message;
|
|
56
|
+
} catch {
|
|
57
|
+
if (text) message = text.slice(0, 200);
|
|
58
|
+
}
|
|
59
|
+
throw new ApiError(message, response.status);
|
|
60
|
+
}
|
|
61
|
+
return await response.json();
|
|
62
|
+
} finally {
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
var ApiError = class extends Error {
|
|
67
|
+
status;
|
|
68
|
+
constructor(message, status) {
|
|
69
|
+
super(message);
|
|
70
|
+
this.name = "ApiError";
|
|
71
|
+
this.status = status;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
async function fetchSkillBySlug(slug) {
|
|
75
|
+
return request(
|
|
76
|
+
`/api/cli/skills/${encodeURIComponent(slug)}`,
|
|
77
|
+
{ method: "GET" }
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
async function startGenerationFromUrl(url, mode) {
|
|
81
|
+
return request("/api/cli/generate", {
|
|
82
|
+
method: "POST",
|
|
83
|
+
body: JSON.stringify({ url, mode })
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
async function pollGeneration(id) {
|
|
87
|
+
return request(`/api/cli/poll/${encodeURIComponent(id)}`, {
|
|
88
|
+
method: "GET"
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
async function waitForGeneration(id, onTick) {
|
|
92
|
+
const start = Date.now();
|
|
93
|
+
while (Date.now() - start < POLL_TIMEOUT_MS) {
|
|
94
|
+
const result = await pollGeneration(id);
|
|
95
|
+
onTick(result.status);
|
|
96
|
+
if (result.status === "done" || result.status === "failed") {
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
await sleep(POLL_INTERVAL_MS);
|
|
100
|
+
}
|
|
101
|
+
throw new Error("Timed out waiting for generation");
|
|
102
|
+
}
|
|
103
|
+
function sleep(ms) {
|
|
104
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
105
|
+
}
|
|
106
|
+
function isUrlInput(input) {
|
|
107
|
+
if (input.startsWith("http://") || input.startsWith("https://")) return true;
|
|
108
|
+
if (input.includes("://")) return true;
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/install.ts
|
|
113
|
+
import { existsSync } from "fs";
|
|
114
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
115
|
+
import { dirname, join, resolve } from "path";
|
|
116
|
+
async function installSkill(input) {
|
|
117
|
+
const results = [];
|
|
118
|
+
for (const ide of input.ides) {
|
|
119
|
+
if (ide === "claude") {
|
|
120
|
+
results.push(await writeClaudeSkill(input.cwd, input.slug, input.skillMd));
|
|
121
|
+
} else if (ide === "cursor") {
|
|
122
|
+
results.push(await writeCursorRule(input.cwd, input.slug, input.skillMd));
|
|
123
|
+
} else if (ide === "windsurf") {
|
|
124
|
+
results.push(await writeWindsurfSection(input.cwd, input.slug, input.skillMd));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return results;
|
|
128
|
+
}
|
|
129
|
+
async function writeClaudeSkill(cwd, slug, skillMd) {
|
|
130
|
+
const dir = resolve(cwd, IDE_PATHS.claude, slug);
|
|
131
|
+
const path = join(dir, "SKILL.md");
|
|
132
|
+
await mkdir(dir, { recursive: true });
|
|
133
|
+
await writeFile(path, skillMd, "utf8");
|
|
134
|
+
return { ide: "claude", path, written: true };
|
|
135
|
+
}
|
|
136
|
+
async function writeCursorRule(cwd, slug, skillMd) {
|
|
137
|
+
const dir = resolve(cwd, IDE_PATHS.cursor);
|
|
138
|
+
const path = join(dir, `${slug}.mdc`);
|
|
139
|
+
await mkdir(dir, { recursive: true });
|
|
140
|
+
const body = stripFrontmatter(skillMd);
|
|
141
|
+
const description = extractDescription(skillMd) ?? slug;
|
|
142
|
+
const content = [
|
|
143
|
+
"---",
|
|
144
|
+
`description: ${escapeYaml(description)}`,
|
|
145
|
+
"globs:",
|
|
146
|
+
' - "**/*"',
|
|
147
|
+
"alwaysApply: false",
|
|
148
|
+
"---",
|
|
149
|
+
"",
|
|
150
|
+
body.trimStart()
|
|
151
|
+
].join("\n");
|
|
152
|
+
await writeFile(path, content, "utf8");
|
|
153
|
+
return { ide: "cursor", path, written: true };
|
|
154
|
+
}
|
|
155
|
+
async function writeWindsurfSection(cwd, slug, skillMd) {
|
|
156
|
+
const path = resolve(cwd, IDE_PATHS.windsurf);
|
|
157
|
+
await mkdir(dirname(path), { recursive: true });
|
|
158
|
+
let existing = "";
|
|
159
|
+
if (existsSync(path)) {
|
|
160
|
+
existing = await readFile(path, "utf8");
|
|
161
|
+
}
|
|
162
|
+
const header = `${WINDSURF_SECTION_HEADER_PREFIX}${slug}`;
|
|
163
|
+
const footer = `${WINDSURF_SECTION_FOOTER_PREFIX}${slug}`;
|
|
164
|
+
const block = [header, "", skillMd.trimEnd(), "", footer, ""].join("\n");
|
|
165
|
+
const sectionRegex = new RegExp(
|
|
166
|
+
`(^|\\n)${escapeRegex(header)}[\\s\\S]*?${escapeRegex(footer)}\\n?`,
|
|
167
|
+
"g"
|
|
168
|
+
);
|
|
169
|
+
const stripped = existing.replace(sectionRegex, "$1");
|
|
170
|
+
const next = (stripped.trimEnd() + "\n\n" + block).trimStart();
|
|
171
|
+
await writeFile(path, next, "utf8");
|
|
172
|
+
return { ide: "windsurf", path, written: true };
|
|
173
|
+
}
|
|
174
|
+
function detectIdes(cwd) {
|
|
175
|
+
const detected = [];
|
|
176
|
+
if (existsSync(resolve(cwd, ".claude"))) detected.push("claude");
|
|
177
|
+
if (existsSync(resolve(cwd, ".cursor"))) detected.push("cursor");
|
|
178
|
+
if (existsSync(resolve(cwd, ".windsurfrules"))) detected.push("windsurf");
|
|
179
|
+
return detected;
|
|
180
|
+
}
|
|
181
|
+
function expandIdeSelection(selection) {
|
|
182
|
+
if (selection === "all") return ["claude", "cursor", "windsurf"];
|
|
183
|
+
return [selection];
|
|
184
|
+
}
|
|
185
|
+
function stripFrontmatter(raw) {
|
|
186
|
+
return raw.replace(/^---\n[\s\S]*?\n---\n*/, "");
|
|
187
|
+
}
|
|
188
|
+
function extractDescription(raw) {
|
|
189
|
+
const match = raw.match(/^description:\s*(.+)$/m);
|
|
190
|
+
if (!match) return null;
|
|
191
|
+
return match[1].trim().replace(/^"(.*)"$/, "$1");
|
|
192
|
+
}
|
|
193
|
+
function escapeYaml(value) {
|
|
194
|
+
if (/[":\n]/.test(value)) {
|
|
195
|
+
return `"${value.replace(/"/g, '\\"').replace(/\n/g, " ")}"`;
|
|
196
|
+
}
|
|
197
|
+
return value;
|
|
198
|
+
}
|
|
199
|
+
function escapeRegex(value) {
|
|
200
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/list.ts
|
|
204
|
+
import { existsSync as existsSync2 } from "fs";
|
|
205
|
+
import { readdir, readFile as readFile2, stat } from "fs/promises";
|
|
206
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
207
|
+
async function listInstalledSkills(cwd) {
|
|
208
|
+
const skills = [];
|
|
209
|
+
skills.push(...await listClaudeSkills(cwd));
|
|
210
|
+
skills.push(...await listCursorSkills(cwd));
|
|
211
|
+
skills.push(...await listWindsurfSkills(cwd));
|
|
212
|
+
return skills;
|
|
213
|
+
}
|
|
214
|
+
async function listClaudeSkills(cwd) {
|
|
215
|
+
const root = resolve2(cwd, IDE_PATHS.claude);
|
|
216
|
+
if (!existsSync2(root)) return [];
|
|
217
|
+
const entries = await readdir(root);
|
|
218
|
+
const out = [];
|
|
219
|
+
for (const entry of entries) {
|
|
220
|
+
const dir = join2(root, entry);
|
|
221
|
+
const info = await stat(dir).catch(() => null);
|
|
222
|
+
if (!info?.isDirectory()) continue;
|
|
223
|
+
const path = join2(dir, "SKILL.md");
|
|
224
|
+
if (existsSync2(path)) {
|
|
225
|
+
out.push({ ide: "claude", slug: entry, path });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return out;
|
|
229
|
+
}
|
|
230
|
+
async function listCursorSkills(cwd) {
|
|
231
|
+
const root = resolve2(cwd, IDE_PATHS.cursor);
|
|
232
|
+
if (!existsSync2(root)) return [];
|
|
233
|
+
const entries = await readdir(root);
|
|
234
|
+
return entries.filter((entry) => entry.endsWith(".mdc")).map((entry) => ({
|
|
235
|
+
ide: "cursor",
|
|
236
|
+
slug: entry.replace(/\.mdc$/, ""),
|
|
237
|
+
path: join2(root, entry)
|
|
238
|
+
}));
|
|
239
|
+
}
|
|
240
|
+
async function listWindsurfSkills(cwd) {
|
|
241
|
+
const path = resolve2(cwd, IDE_PATHS.windsurf);
|
|
242
|
+
if (!existsSync2(path)) return [];
|
|
243
|
+
const content = await readFile2(path, "utf8");
|
|
244
|
+
const matches = content.matchAll(
|
|
245
|
+
new RegExp(`^${escapeRegex2(WINDSURF_SECTION_HEADER_PREFIX)}(.+)$`, "gm")
|
|
246
|
+
);
|
|
247
|
+
const out = [];
|
|
248
|
+
for (const match of matches) {
|
|
249
|
+
out.push({ ide: "windsurf", slug: match[1].trim(), path });
|
|
250
|
+
}
|
|
251
|
+
return out;
|
|
252
|
+
}
|
|
253
|
+
function escapeRegex2(value) {
|
|
254
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/cli.ts
|
|
258
|
+
var VERSION = "0.1.0";
|
|
259
|
+
var program = new Command();
|
|
260
|
+
program.name("getskillmd").description("Install skill.md files into Claude Code, Cursor, and Windsurf").version(VERSION, "-v, --version", "Print the version");
|
|
261
|
+
program.command("add").argument("<slug-or-url>", "skill slug or source URL").option(
|
|
262
|
+
"--ide <ide>",
|
|
263
|
+
`target IDE: ${VALID_IDES.join(" | ")}`
|
|
264
|
+
).option(
|
|
265
|
+
"--mode <mode>",
|
|
266
|
+
`generation mode (URL only): ${VALID_MODES.join(" | ")}`,
|
|
267
|
+
"design"
|
|
268
|
+
).option("--cwd <path>", "working directory", process.cwd()).description("Install a skill into your IDE").action(async (input, options) => {
|
|
269
|
+
try {
|
|
270
|
+
await runAdd(input, options);
|
|
271
|
+
} catch (error) {
|
|
272
|
+
handleError(error);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
program.command("list").option("--cwd <path>", "working directory", process.cwd()).description("List skills installed in this project").action(async (options) => {
|
|
276
|
+
try {
|
|
277
|
+
await runList(options);
|
|
278
|
+
} catch (error) {
|
|
279
|
+
handleError(error);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
program.parseAsync(process.argv).catch(handleError);
|
|
283
|
+
async function runAdd(input, options) {
|
|
284
|
+
const cwd = options.cwd;
|
|
285
|
+
const ideSelection = await resolveIdeSelection(options.ide, cwd);
|
|
286
|
+
const ides = expandIdeSelection(ideSelection);
|
|
287
|
+
const mode = parseMode(options.mode);
|
|
288
|
+
const skill = isUrlInput(input) ? await generateFromUrl(input, mode) : await loadFromSlug(input);
|
|
289
|
+
console.log(
|
|
290
|
+
kleur.dim("Installing"),
|
|
291
|
+
kleur.cyan(skill.slug),
|
|
292
|
+
kleur.dim("into"),
|
|
293
|
+
kleur.bold(ides.map((i) => IDE_LABELS[i]).join(", "))
|
|
294
|
+
);
|
|
295
|
+
const results = await installSkill({
|
|
296
|
+
cwd,
|
|
297
|
+
slug: skill.slug,
|
|
298
|
+
skillMd: skill.skillMd,
|
|
299
|
+
ides
|
|
300
|
+
});
|
|
301
|
+
for (const result of results) {
|
|
302
|
+
console.log(
|
|
303
|
+
kleur.green(" +"),
|
|
304
|
+
kleur.bold(IDE_LABELS[result.ide]),
|
|
305
|
+
kleur.dim(`(${result.path.replace(cwd, ".")})`)
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
console.log();
|
|
309
|
+
console.log(kleur.green("Done."), kleur.dim(`${skill.slug} ready to use.`));
|
|
310
|
+
}
|
|
311
|
+
async function runList(options) {
|
|
312
|
+
const skills = await listInstalledSkills(options.cwd);
|
|
313
|
+
if (skills.length === 0) {
|
|
314
|
+
console.log(kleur.dim("No skills installed in this directory."));
|
|
315
|
+
console.log(
|
|
316
|
+
kleur.dim("Looked in:"),
|
|
317
|
+
Object.values(IDE_PATHS).join(", ")
|
|
318
|
+
);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
322
|
+
for (const skill of skills) {
|
|
323
|
+
const list = grouped.get(skill.ide) ?? [];
|
|
324
|
+
list.push(skill);
|
|
325
|
+
grouped.set(skill.ide, list);
|
|
326
|
+
}
|
|
327
|
+
for (const [ide, list] of grouped) {
|
|
328
|
+
console.log(kleur.bold(IDE_LABELS[ide]));
|
|
329
|
+
for (const skill of list) {
|
|
330
|
+
console.log(" ", kleur.cyan(skill.slug), kleur.dim(skill.path));
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async function loadFromSlug(slug) {
|
|
335
|
+
console.log(kleur.dim("Fetching"), kleur.cyan(slug), kleur.dim("..."));
|
|
336
|
+
const response = await fetchSkillBySlug(slug);
|
|
337
|
+
return { slug: response.slug, skillMd: response.skill_md };
|
|
338
|
+
}
|
|
339
|
+
async function generateFromUrl(url, mode) {
|
|
340
|
+
console.log(
|
|
341
|
+
kleur.dim("Generating skill from"),
|
|
342
|
+
kleur.cyan(url),
|
|
343
|
+
kleur.dim(`(${mode})`)
|
|
344
|
+
);
|
|
345
|
+
const start = await startGenerationFromUrl(url, mode);
|
|
346
|
+
let lastStatus = null;
|
|
347
|
+
const result = await waitForGeneration(start.id, (status) => {
|
|
348
|
+
if (status !== lastStatus) {
|
|
349
|
+
lastStatus = status;
|
|
350
|
+
console.log(kleur.dim(" status:"), status);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
if (result.status === "failed") {
|
|
354
|
+
throw new Error(result.error ?? "Generation failed");
|
|
355
|
+
}
|
|
356
|
+
if (!result.skill_md || !result.slug) {
|
|
357
|
+
throw new Error("Generation completed without a skill payload");
|
|
358
|
+
}
|
|
359
|
+
return { slug: result.slug, skillMd: result.skill_md };
|
|
360
|
+
}
|
|
361
|
+
async function resolveIdeSelection(raw, cwd) {
|
|
362
|
+
if (raw) {
|
|
363
|
+
if (!isValidIde(raw)) {
|
|
364
|
+
throw new Error(
|
|
365
|
+
`Invalid --ide value "${raw}". Use one of: ${VALID_IDES.join(", ")}`
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
return raw;
|
|
369
|
+
}
|
|
370
|
+
const detected = detectIdes(cwd);
|
|
371
|
+
if (detected.length === 1) return detected[0];
|
|
372
|
+
if (detected.length === 0) {
|
|
373
|
+
const response2 = await prompts({
|
|
374
|
+
type: "select",
|
|
375
|
+
name: "ide",
|
|
376
|
+
message: "Which IDE?",
|
|
377
|
+
choices: [
|
|
378
|
+
{ title: IDE_LABELS.claude, value: "claude" },
|
|
379
|
+
{ title: IDE_LABELS.cursor, value: "cursor" },
|
|
380
|
+
{ title: IDE_LABELS.windsurf, value: "windsurf" },
|
|
381
|
+
{ title: "All of them", value: "all" }
|
|
382
|
+
],
|
|
383
|
+
initial: 0
|
|
384
|
+
});
|
|
385
|
+
if (!response2.ide) throw new Error("Aborted");
|
|
386
|
+
return response2.ide;
|
|
387
|
+
}
|
|
388
|
+
const response = await prompts({
|
|
389
|
+
type: "select",
|
|
390
|
+
name: "ide",
|
|
391
|
+
message: "Multiple IDE configs detected. Which one?",
|
|
392
|
+
choices: [
|
|
393
|
+
...detected.map((ide) => ({ title: IDE_LABELS[ide], value: ide })),
|
|
394
|
+
{ title: "All detected", value: "all" }
|
|
395
|
+
],
|
|
396
|
+
initial: 0
|
|
397
|
+
});
|
|
398
|
+
if (!response.ide) throw new Error("Aborted");
|
|
399
|
+
return response.ide;
|
|
400
|
+
}
|
|
401
|
+
function parseMode(raw) {
|
|
402
|
+
if (!raw) return "design";
|
|
403
|
+
if (!isValidMode(raw)) {
|
|
404
|
+
throw new Error(
|
|
405
|
+
`Invalid --mode value "${raw}". Use one of: ${VALID_MODES.join(", ")}`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
return raw;
|
|
409
|
+
}
|
|
410
|
+
function isValidIde(value) {
|
|
411
|
+
return VALID_IDES.includes(value);
|
|
412
|
+
}
|
|
413
|
+
function isValidMode(value) {
|
|
414
|
+
return VALID_MODES.includes(value);
|
|
415
|
+
}
|
|
416
|
+
function handleError(error) {
|
|
417
|
+
if (error instanceof ApiError) {
|
|
418
|
+
console.error(kleur.red("Error:"), error.message);
|
|
419
|
+
if (error.status === 404) {
|
|
420
|
+
console.error(
|
|
421
|
+
kleur.dim("Hint: pass a full URL to generate a new skill from scratch.")
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
} else if (error instanceof Error) {
|
|
425
|
+
console.error(kleur.red("Error:"), error.message);
|
|
426
|
+
} else {
|
|
427
|
+
console.error(kleur.red("Error:"), String(error));
|
|
428
|
+
}
|
|
429
|
+
process.exit(1);
|
|
430
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@getskillmd/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI to install skill.md files into Claude Code, Cursor, and Windsurf",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "tekcify",
|
|
9
|
+
"url": "https://tekcify.com"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://getskillmd.com",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/tekcify/getskillmd.git",
|
|
15
|
+
"directory": "packages/cli"
|
|
16
|
+
},
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/tekcify/getskillmd/issues"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"ai",
|
|
22
|
+
"claude",
|
|
23
|
+
"cursor",
|
|
24
|
+
"windsurf",
|
|
25
|
+
"skills",
|
|
26
|
+
"skill.md",
|
|
27
|
+
"agent"
|
|
28
|
+
],
|
|
29
|
+
"bin": {
|
|
30
|
+
"getskillmd": "./dist/cli.js"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"README.md",
|
|
38
|
+
"LICENSE"
|
|
39
|
+
],
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"commander": "^12.1.0",
|
|
45
|
+
"kleur": "^4.1.5",
|
|
46
|
+
"nanoid": "^5.1.11",
|
|
47
|
+
"prompts": "^2.4.2"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^20",
|
|
51
|
+
"@types/prompts": "^2.4.9",
|
|
52
|
+
"tsup": "^8.3.5",
|
|
53
|
+
"typescript": "^5"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsup",
|
|
57
|
+
"dev": "tsup --watch",
|
|
58
|
+
"type-check": "tsc --noEmit"
|
|
59
|
+
}
|
|
60
|
+
}
|