@skillkit/cli 1.3.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 +190 -0
- package/dist/index.d.ts +249 -0
- package/dist/index.js +2247 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2247 @@
|
|
|
1
|
+
// src/commands/list.ts
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { Command, Option } from "clipanion";
|
|
4
|
+
import { findAllSkills } from "@skillkit/core";
|
|
5
|
+
|
|
6
|
+
// src/helpers.ts
|
|
7
|
+
import {
|
|
8
|
+
loadConfig,
|
|
9
|
+
getSearchDirs as coreGetSearchDirs,
|
|
10
|
+
getInstallDir as coreGetInstallDir,
|
|
11
|
+
getAgentConfigPath as coreGetAgentConfigPath,
|
|
12
|
+
initProject as coreInitProject,
|
|
13
|
+
loadSkillMetadata as coreLoadSkillMetadata,
|
|
14
|
+
saveSkillMetadata as coreSaveSkillMetadata
|
|
15
|
+
} from "@skillkit/core";
|
|
16
|
+
import { getAdapter, detectAgent } from "@skillkit/agents";
|
|
17
|
+
var loadSkillMetadata = coreLoadSkillMetadata;
|
|
18
|
+
var saveSkillMetadata = coreSaveSkillMetadata;
|
|
19
|
+
function getSearchDirs(agentType) {
|
|
20
|
+
const type = agentType || loadConfig().agent;
|
|
21
|
+
const adapter = getAdapter(type);
|
|
22
|
+
const adapterInfo = {
|
|
23
|
+
type: adapter.type,
|
|
24
|
+
name: adapter.name,
|
|
25
|
+
skillsDir: adapter.skillsDir,
|
|
26
|
+
configFile: adapter.configFile
|
|
27
|
+
};
|
|
28
|
+
return coreGetSearchDirs(adapterInfo);
|
|
29
|
+
}
|
|
30
|
+
function getInstallDir(global = false, agentType) {
|
|
31
|
+
const type = agentType || loadConfig().agent;
|
|
32
|
+
const adapter = getAdapter(type);
|
|
33
|
+
const adapterInfo = {
|
|
34
|
+
type: adapter.type,
|
|
35
|
+
name: adapter.name,
|
|
36
|
+
skillsDir: adapter.skillsDir,
|
|
37
|
+
configFile: adapter.configFile
|
|
38
|
+
};
|
|
39
|
+
return coreGetInstallDir(adapterInfo, global);
|
|
40
|
+
}
|
|
41
|
+
function getAgentConfigPath(agentType) {
|
|
42
|
+
const type = agentType || loadConfig().agent;
|
|
43
|
+
const adapter = getAdapter(type);
|
|
44
|
+
const adapterInfo = {
|
|
45
|
+
type: adapter.type,
|
|
46
|
+
name: adapter.name,
|
|
47
|
+
skillsDir: adapter.skillsDir,
|
|
48
|
+
configFile: adapter.configFile
|
|
49
|
+
};
|
|
50
|
+
return coreGetAgentConfigPath(adapterInfo);
|
|
51
|
+
}
|
|
52
|
+
async function initProject(agentType) {
|
|
53
|
+
const type = agentType || await detectAgent();
|
|
54
|
+
const adapter = getAdapter(type);
|
|
55
|
+
const adapterInfo = {
|
|
56
|
+
type: adapter.type,
|
|
57
|
+
name: adapter.name,
|
|
58
|
+
skillsDir: adapter.skillsDir,
|
|
59
|
+
configFile: adapter.configFile
|
|
60
|
+
};
|
|
61
|
+
return coreInitProject(type, adapterInfo);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/commands/list.ts
|
|
65
|
+
var ListCommand = class extends Command {
|
|
66
|
+
static paths = [["list"], ["ls"], ["l"]];
|
|
67
|
+
static usage = Command.Usage({
|
|
68
|
+
description: "List all installed skills",
|
|
69
|
+
examples: [
|
|
70
|
+
["List all skills", "$0 list"],
|
|
71
|
+
["Show only enabled skills", "$0 list --enabled"],
|
|
72
|
+
["Show JSON output", "$0 list --json"]
|
|
73
|
+
]
|
|
74
|
+
});
|
|
75
|
+
enabled = Option.Boolean("--enabled,-e", false, {
|
|
76
|
+
description: "Show only enabled skills"
|
|
77
|
+
});
|
|
78
|
+
disabled = Option.Boolean("--disabled,-d", false, {
|
|
79
|
+
description: "Show only disabled skills"
|
|
80
|
+
});
|
|
81
|
+
json = Option.Boolean("--json,-j", false, {
|
|
82
|
+
description: "Output as JSON"
|
|
83
|
+
});
|
|
84
|
+
async execute() {
|
|
85
|
+
const searchDirs = getSearchDirs();
|
|
86
|
+
let skills = findAllSkills(searchDirs);
|
|
87
|
+
if (this.enabled) {
|
|
88
|
+
skills = skills.filter((s) => s.enabled);
|
|
89
|
+
} else if (this.disabled) {
|
|
90
|
+
skills = skills.filter((s) => !s.enabled);
|
|
91
|
+
}
|
|
92
|
+
skills.sort((a, b) => {
|
|
93
|
+
if (a.location !== b.location) {
|
|
94
|
+
return a.location === "project" ? -1 : 1;
|
|
95
|
+
}
|
|
96
|
+
return a.name.localeCompare(b.name);
|
|
97
|
+
});
|
|
98
|
+
if (this.json) {
|
|
99
|
+
console.log(JSON.stringify(skills, null, 2));
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
if (skills.length === 0) {
|
|
103
|
+
console.log(chalk.yellow("No skills installed"));
|
|
104
|
+
console.log(chalk.dim("Install skills with: skillkit install <source>"));
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
console.log(chalk.cyan(`Installed skills (${skills.length}):
|
|
108
|
+
`));
|
|
109
|
+
const projectSkills = skills.filter((s) => s.location === "project");
|
|
110
|
+
const globalSkills = skills.filter((s) => s.location === "global");
|
|
111
|
+
if (projectSkills.length > 0) {
|
|
112
|
+
console.log(chalk.blue("Project skills:"));
|
|
113
|
+
for (const skill of projectSkills) {
|
|
114
|
+
printSkill(skill);
|
|
115
|
+
}
|
|
116
|
+
console.log();
|
|
117
|
+
}
|
|
118
|
+
if (globalSkills.length > 0) {
|
|
119
|
+
console.log(chalk.dim("Global skills:"));
|
|
120
|
+
for (const skill of globalSkills) {
|
|
121
|
+
printSkill(skill);
|
|
122
|
+
}
|
|
123
|
+
console.log();
|
|
124
|
+
}
|
|
125
|
+
const enabledCount = skills.filter((s) => s.enabled).length;
|
|
126
|
+
const disabledCount = skills.length - enabledCount;
|
|
127
|
+
console.log(
|
|
128
|
+
chalk.dim(
|
|
129
|
+
`${projectSkills.length} project, ${globalSkills.length} global` + (disabledCount > 0 ? `, ${disabledCount} disabled` : "")
|
|
130
|
+
)
|
|
131
|
+
);
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
function printSkill(skill) {
|
|
136
|
+
const status = skill.enabled ? chalk.green("\u2713") : chalk.red("\u25CB");
|
|
137
|
+
const name = skill.enabled ? skill.name : chalk.dim(skill.name);
|
|
138
|
+
const desc = chalk.dim(truncate(skill.description, 50));
|
|
139
|
+
console.log(` ${status} ${name}`);
|
|
140
|
+
if (skill.description) {
|
|
141
|
+
console.log(` ${desc}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function truncate(str, maxLen) {
|
|
145
|
+
if (str.length <= maxLen) return str;
|
|
146
|
+
return str.slice(0, maxLen - 3) + "...";
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/commands/read.ts
|
|
150
|
+
import chalk2 from "chalk";
|
|
151
|
+
import { Command as Command2, Option as Option2 } from "clipanion";
|
|
152
|
+
import { findSkill, readSkillContent } from "@skillkit/core";
|
|
153
|
+
var ReadCommand = class extends Command2 {
|
|
154
|
+
static paths = [["read"], ["r"]];
|
|
155
|
+
static usage = Command2.Usage({
|
|
156
|
+
description: "Read skill content for AI agent consumption",
|
|
157
|
+
examples: [
|
|
158
|
+
["Read a single skill", "$0 read pdf"],
|
|
159
|
+
["Read multiple skills", "$0 read pdf,xlsx,docx"],
|
|
160
|
+
["Read with verbose output", "$0 read pdf --verbose"]
|
|
161
|
+
]
|
|
162
|
+
});
|
|
163
|
+
skills = Option2.String({ required: true });
|
|
164
|
+
verbose = Option2.Boolean("--verbose,-v", false, {
|
|
165
|
+
description: "Show additional information"
|
|
166
|
+
});
|
|
167
|
+
async execute() {
|
|
168
|
+
const searchDirs = getSearchDirs();
|
|
169
|
+
const skillNames = this.skills.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
170
|
+
if (skillNames.length === 0) {
|
|
171
|
+
console.error(chalk2.red("No skill names provided"));
|
|
172
|
+
return 1;
|
|
173
|
+
}
|
|
174
|
+
let exitCode = 0;
|
|
175
|
+
for (const skillName of skillNames) {
|
|
176
|
+
const skill = findSkill(skillName, searchDirs);
|
|
177
|
+
if (!skill) {
|
|
178
|
+
console.error(chalk2.red(`Skill not found: ${skillName}`));
|
|
179
|
+
console.error(chalk2.dim("Available directories:"));
|
|
180
|
+
searchDirs.forEach((d) => console.error(chalk2.dim(` - ${d}`)));
|
|
181
|
+
exitCode = 1;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (!skill.enabled) {
|
|
185
|
+
console.error(chalk2.yellow(`Skill disabled: ${skillName}`));
|
|
186
|
+
console.error(chalk2.dim("Enable with: skillkit enable " + skillName));
|
|
187
|
+
exitCode = 1;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const content = readSkillContent(skill.path);
|
|
191
|
+
if (!content) {
|
|
192
|
+
console.error(chalk2.red(`Could not read SKILL.md for: ${skillName}`));
|
|
193
|
+
exitCode = 1;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
console.log(`Reading: ${skillName}`);
|
|
197
|
+
console.log(`Base directory: ${skill.path}`);
|
|
198
|
+
console.log();
|
|
199
|
+
console.log(content);
|
|
200
|
+
console.log();
|
|
201
|
+
console.log(`Skill read: ${skillName}`);
|
|
202
|
+
if (skillNames.length > 1 && skillName !== skillNames[skillNames.length - 1]) {
|
|
203
|
+
console.log("\n---\n");
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return exitCode;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// src/commands/sync.ts
|
|
211
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
212
|
+
import { dirname } from "path";
|
|
213
|
+
import chalk3 from "chalk";
|
|
214
|
+
import { Command as Command3, Option as Option3 } from "clipanion";
|
|
215
|
+
import { loadConfig as loadConfig2, findAllSkills as findAllSkills2 } from "@skillkit/core";
|
|
216
|
+
import { getAdapter as getAdapter2, detectAgent as detectAgent2 } from "@skillkit/agents";
|
|
217
|
+
var SyncCommand = class extends Command3 {
|
|
218
|
+
static paths = [["sync"], ["s"]];
|
|
219
|
+
static usage = Command3.Usage({
|
|
220
|
+
description: "Sync skills to agent configuration file",
|
|
221
|
+
examples: [
|
|
222
|
+
["Sync all enabled skills", "$0 sync"],
|
|
223
|
+
["Sync to specific file", "$0 sync --output AGENTS.md"],
|
|
224
|
+
["Sync for specific agent", "$0 sync --agent cursor"],
|
|
225
|
+
["Only sync enabled skills", "$0 sync --enabled-only"]
|
|
226
|
+
]
|
|
227
|
+
});
|
|
228
|
+
output = Option3.String("--output,-o", {
|
|
229
|
+
description: "Output file path (default: agent-specific config file)"
|
|
230
|
+
});
|
|
231
|
+
agent = Option3.String("--agent,-a", {
|
|
232
|
+
description: "Target agent type (claude-code, cursor, codex, etc.)"
|
|
233
|
+
});
|
|
234
|
+
enabledOnly = Option3.Boolean("--enabled-only,-e", true, {
|
|
235
|
+
description: "Only include enabled skills (default: true)"
|
|
236
|
+
});
|
|
237
|
+
yes = Option3.Boolean("--yes,-y", false, {
|
|
238
|
+
description: "Skip confirmation prompts"
|
|
239
|
+
});
|
|
240
|
+
async execute() {
|
|
241
|
+
try {
|
|
242
|
+
let agentType;
|
|
243
|
+
if (this.agent) {
|
|
244
|
+
agentType = this.agent;
|
|
245
|
+
} else {
|
|
246
|
+
const config2 = loadConfig2();
|
|
247
|
+
agentType = config2.agent || await detectAgent2();
|
|
248
|
+
}
|
|
249
|
+
const adapter = getAdapter2(agentType);
|
|
250
|
+
const outputPath = this.output || getAgentConfigPath(agentType);
|
|
251
|
+
const searchDirs = getSearchDirs(agentType);
|
|
252
|
+
let skills = findAllSkills2(searchDirs);
|
|
253
|
+
if (this.enabledOnly) {
|
|
254
|
+
skills = skills.filter((s) => s.enabled);
|
|
255
|
+
}
|
|
256
|
+
if (skills.length === 0) {
|
|
257
|
+
console.log(chalk3.yellow("No skills found to sync"));
|
|
258
|
+
console.log(chalk3.dim("Install skills with: skillkit install <source>"));
|
|
259
|
+
return 0;
|
|
260
|
+
}
|
|
261
|
+
console.log(chalk3.cyan(`Syncing ${skills.length} skill(s) for ${adapter.name}:`));
|
|
262
|
+
skills.forEach((s) => {
|
|
263
|
+
const status = s.enabled ? chalk3.green("\u2713") : chalk3.dim("\u25CB");
|
|
264
|
+
const location = s.location === "project" ? chalk3.blue("[project]") : chalk3.dim("[global]");
|
|
265
|
+
console.log(` ${status} ${s.name} ${location}`);
|
|
266
|
+
});
|
|
267
|
+
console.log();
|
|
268
|
+
const config = adapter.generateConfig(skills);
|
|
269
|
+
if (!config) {
|
|
270
|
+
console.log(chalk3.yellow("No configuration generated"));
|
|
271
|
+
return 0;
|
|
272
|
+
}
|
|
273
|
+
let existingContent = "";
|
|
274
|
+
if (existsSync(outputPath)) {
|
|
275
|
+
existingContent = readFileSync(outputPath, "utf-8");
|
|
276
|
+
}
|
|
277
|
+
const newContent = updateConfigContent(existingContent, config, agentType);
|
|
278
|
+
const dir = dirname(outputPath);
|
|
279
|
+
if (!existsSync(dir)) {
|
|
280
|
+
mkdirSync(dir, { recursive: true });
|
|
281
|
+
}
|
|
282
|
+
writeFileSync(outputPath, newContent, "utf-8");
|
|
283
|
+
console.log(chalk3.green(`Synced to ${outputPath}`));
|
|
284
|
+
console.log(chalk3.dim(`Agent: ${adapter.name}`));
|
|
285
|
+
return 0;
|
|
286
|
+
} catch (error) {
|
|
287
|
+
console.error(chalk3.red("Sync failed"));
|
|
288
|
+
console.error(chalk3.dim(error instanceof Error ? error.message : String(error)));
|
|
289
|
+
return 1;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
function updateConfigContent(existing, newConfig, agentType) {
|
|
294
|
+
const markers = {
|
|
295
|
+
"claude-code": {
|
|
296
|
+
start: "<!-- SKILLS_TABLE_START -->",
|
|
297
|
+
end: "<!-- SKILLS_TABLE_END -->"
|
|
298
|
+
},
|
|
299
|
+
cursor: {
|
|
300
|
+
start: "<!-- SKILLS_DATA_START -->",
|
|
301
|
+
end: "<!-- SKILLS_DATA_END -->"
|
|
302
|
+
},
|
|
303
|
+
universal: {
|
|
304
|
+
start: "<!-- SKILLKIT_SKILLS_START -->",
|
|
305
|
+
end: "<!-- SKILLKIT_SKILLS_END -->"
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
const agentMarkers = markers[agentType] || markers.universal;
|
|
309
|
+
const startIdx = existing.indexOf(agentMarkers.start);
|
|
310
|
+
const endIdx = existing.indexOf(agentMarkers.end);
|
|
311
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
312
|
+
return existing.slice(0, startIdx) + newConfig.slice(newConfig.indexOf(agentMarkers.start)) + existing.slice(endIdx + agentMarkers.end.length);
|
|
313
|
+
}
|
|
314
|
+
const genericStart = "<!-- SKILLKIT_SKILLS_START -->";
|
|
315
|
+
const genericEnd = "<!-- SKILLKIT_SKILLS_END -->";
|
|
316
|
+
const gStartIdx = existing.indexOf(genericStart);
|
|
317
|
+
const gEndIdx = existing.indexOf(genericEnd);
|
|
318
|
+
if (gStartIdx !== -1 && gEndIdx !== -1) {
|
|
319
|
+
return existing.slice(0, gStartIdx) + newConfig + existing.slice(gEndIdx + genericEnd.length);
|
|
320
|
+
}
|
|
321
|
+
if (existing.trim()) {
|
|
322
|
+
return existing + "\n\n" + newConfig;
|
|
323
|
+
}
|
|
324
|
+
return newConfig;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/commands/init.ts
|
|
328
|
+
import chalk4 from "chalk";
|
|
329
|
+
import { Command as Command4, Option as Option4 } from "clipanion";
|
|
330
|
+
import { detectAgent as detectAgent3, getAdapter as getAdapter3, getAllAdapters } from "@skillkit/agents";
|
|
331
|
+
var InitCommand = class extends Command4 {
|
|
332
|
+
static paths = [["init"]];
|
|
333
|
+
static usage = Command4.Usage({
|
|
334
|
+
description: "Initialize skillkit in a project",
|
|
335
|
+
examples: [
|
|
336
|
+
["Auto-detect agent and initialize", "$0 init"],
|
|
337
|
+
["Initialize for specific agent", "$0 init --agent cursor"],
|
|
338
|
+
["List supported agents", "$0 init --list"]
|
|
339
|
+
]
|
|
340
|
+
});
|
|
341
|
+
agent = Option4.String("--agent,-a", {
|
|
342
|
+
description: "Target agent type"
|
|
343
|
+
});
|
|
344
|
+
list = Option4.Boolean("--list,-l", false, {
|
|
345
|
+
description: "List supported agents"
|
|
346
|
+
});
|
|
347
|
+
async execute() {
|
|
348
|
+
if (this.list) {
|
|
349
|
+
console.log(chalk4.cyan("Supported agents:\n"));
|
|
350
|
+
const adapters = getAllAdapters();
|
|
351
|
+
for (const adapter of adapters) {
|
|
352
|
+
console.log(` ${chalk4.green(adapter.type)}`);
|
|
353
|
+
console.log(` Name: ${adapter.name}`);
|
|
354
|
+
console.log(` Skills dir: ${adapter.skillsDir}`);
|
|
355
|
+
console.log(` Config file: ${adapter.configFile}`);
|
|
356
|
+
console.log();
|
|
357
|
+
}
|
|
358
|
+
return 0;
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
let agentType;
|
|
362
|
+
if (this.agent) {
|
|
363
|
+
agentType = this.agent;
|
|
364
|
+
} else {
|
|
365
|
+
console.log(chalk4.dim("Auto-detecting agent..."));
|
|
366
|
+
agentType = await detectAgent3();
|
|
367
|
+
}
|
|
368
|
+
const adapter = getAdapter3(agentType);
|
|
369
|
+
console.log(chalk4.cyan(`Initializing for ${adapter.name}...`));
|
|
370
|
+
await initProject(agentType);
|
|
371
|
+
console.log();
|
|
372
|
+
console.log(chalk4.green("Initialized successfully!"));
|
|
373
|
+
console.log();
|
|
374
|
+
console.log(chalk4.dim("Created:"));
|
|
375
|
+
console.log(chalk4.dim(` - ${adapter.skillsDir}/ (skills directory)`));
|
|
376
|
+
console.log(chalk4.dim(` - skillkit.yaml (config file)`));
|
|
377
|
+
console.log(chalk4.dim(` - ${adapter.configFile} (agent config)`));
|
|
378
|
+
console.log();
|
|
379
|
+
console.log(chalk4.cyan("Next steps:"));
|
|
380
|
+
console.log(chalk4.dim(" 1. Install skills: skillkit install owner/repo"));
|
|
381
|
+
console.log(chalk4.dim(" 2. Sync config: skillkit sync"));
|
|
382
|
+
console.log(chalk4.dim(" 3. Use skills: skillkit read <skill-name>"));
|
|
383
|
+
return 0;
|
|
384
|
+
} catch (error) {
|
|
385
|
+
console.error(chalk4.red("Initialization failed"));
|
|
386
|
+
console.error(chalk4.dim(error instanceof Error ? error.message : String(error)));
|
|
387
|
+
return 1;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// src/commands/enable.ts
|
|
393
|
+
import chalk5 from "chalk";
|
|
394
|
+
import { Command as Command5, Option as Option5 } from "clipanion";
|
|
395
|
+
import { setSkillEnabled, findSkill as findSkill2 } from "@skillkit/core";
|
|
396
|
+
var EnableCommand = class extends Command5 {
|
|
397
|
+
static paths = [["enable"]];
|
|
398
|
+
static usage = Command5.Usage({
|
|
399
|
+
description: "Enable one or more skills",
|
|
400
|
+
examples: [
|
|
401
|
+
["Enable a skill", "$0 enable pdf"],
|
|
402
|
+
["Enable multiple skills", "$0 enable pdf xlsx docx"]
|
|
403
|
+
]
|
|
404
|
+
});
|
|
405
|
+
skills = Option5.Rest({ required: 1 });
|
|
406
|
+
async execute() {
|
|
407
|
+
const searchDirs = getSearchDirs();
|
|
408
|
+
let success = 0;
|
|
409
|
+
let failed = 0;
|
|
410
|
+
for (const skillName of this.skills) {
|
|
411
|
+
const skill = findSkill2(skillName, searchDirs);
|
|
412
|
+
if (!skill) {
|
|
413
|
+
console.log(chalk5.red(`Skill not found: ${skillName}`));
|
|
414
|
+
failed++;
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
if (skill.enabled) {
|
|
418
|
+
console.log(chalk5.dim(`Already enabled: ${skillName}`));
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
const result = setSkillEnabled(skill.path, true);
|
|
422
|
+
if (result) {
|
|
423
|
+
console.log(chalk5.green(`Enabled: ${skillName}`));
|
|
424
|
+
success++;
|
|
425
|
+
} else {
|
|
426
|
+
console.log(chalk5.red(`Failed to enable: ${skillName}`));
|
|
427
|
+
failed++;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (success > 0) {
|
|
431
|
+
console.log(chalk5.dim("\nRun `skillkit sync` to update your agent config"));
|
|
432
|
+
}
|
|
433
|
+
return failed > 0 ? 1 : 0;
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
var DisableCommand = class extends Command5 {
|
|
437
|
+
static paths = [["disable"]];
|
|
438
|
+
static usage = Command5.Usage({
|
|
439
|
+
description: "Disable one or more skills",
|
|
440
|
+
examples: [
|
|
441
|
+
["Disable a skill", "$0 disable pdf"],
|
|
442
|
+
["Disable multiple skills", "$0 disable pdf xlsx docx"]
|
|
443
|
+
]
|
|
444
|
+
});
|
|
445
|
+
skills = Option5.Rest({ required: 1 });
|
|
446
|
+
async execute() {
|
|
447
|
+
const searchDirs = getSearchDirs();
|
|
448
|
+
let success = 0;
|
|
449
|
+
let failed = 0;
|
|
450
|
+
for (const skillName of this.skills) {
|
|
451
|
+
const skill = findSkill2(skillName, searchDirs);
|
|
452
|
+
if (!skill) {
|
|
453
|
+
console.log(chalk5.red(`Skill not found: ${skillName}`));
|
|
454
|
+
failed++;
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (!skill.enabled) {
|
|
458
|
+
console.log(chalk5.dim(`Already disabled: ${skillName}`));
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
const result = setSkillEnabled(skill.path, false);
|
|
462
|
+
if (result) {
|
|
463
|
+
console.log(chalk5.yellow(`Disabled: ${skillName}`));
|
|
464
|
+
success++;
|
|
465
|
+
} else {
|
|
466
|
+
console.log(chalk5.red(`Failed to disable: ${skillName}`));
|
|
467
|
+
failed++;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (success > 0) {
|
|
471
|
+
console.log(chalk5.dim("\nRun `skillkit sync` to update your agent config"));
|
|
472
|
+
}
|
|
473
|
+
return failed > 0 ? 1 : 0;
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
// src/commands/remove.ts
|
|
478
|
+
import { existsSync as existsSync2, rmSync } from "fs";
|
|
479
|
+
import chalk6 from "chalk";
|
|
480
|
+
import { Command as Command6, Option as Option6 } from "clipanion";
|
|
481
|
+
import { findSkill as findSkill3 } from "@skillkit/core";
|
|
482
|
+
var RemoveCommand = class extends Command6 {
|
|
483
|
+
static paths = [["remove"], ["rm"], ["uninstall"]];
|
|
484
|
+
static usage = Command6.Usage({
|
|
485
|
+
description: "Remove installed skills",
|
|
486
|
+
examples: [
|
|
487
|
+
["Remove a skill", "$0 remove pdf"],
|
|
488
|
+
["Remove multiple skills", "$0 remove pdf xlsx docx"],
|
|
489
|
+
["Force removal without confirmation", "$0 remove pdf --force"]
|
|
490
|
+
]
|
|
491
|
+
});
|
|
492
|
+
skills = Option6.Rest({ required: 1 });
|
|
493
|
+
force = Option6.Boolean("--force,-f", false, {
|
|
494
|
+
description: "Skip confirmation"
|
|
495
|
+
});
|
|
496
|
+
async execute() {
|
|
497
|
+
const searchDirs = getSearchDirs();
|
|
498
|
+
let removed = 0;
|
|
499
|
+
let failed = 0;
|
|
500
|
+
for (const skillName of this.skills) {
|
|
501
|
+
const skill = findSkill3(skillName, searchDirs);
|
|
502
|
+
if (!skill) {
|
|
503
|
+
console.log(chalk6.yellow(`Skill not found: ${skillName}`));
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
if (!existsSync2(skill.path)) {
|
|
507
|
+
console.log(chalk6.yellow(`Path not found: ${skill.path}`));
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
try {
|
|
511
|
+
rmSync(skill.path, { recursive: true, force: true });
|
|
512
|
+
console.log(chalk6.green(`Removed: ${skillName}`));
|
|
513
|
+
removed++;
|
|
514
|
+
} catch (error) {
|
|
515
|
+
console.log(chalk6.red(`Failed to remove: ${skillName}`));
|
|
516
|
+
console.error(chalk6.dim(error instanceof Error ? error.message : String(error)));
|
|
517
|
+
failed++;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (removed > 0) {
|
|
521
|
+
console.log(chalk6.dim("\nRun `skillkit sync` to update your agent config"));
|
|
522
|
+
}
|
|
523
|
+
return failed > 0 ? 1 : 0;
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
// src/commands/install.ts
|
|
528
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, cpSync, rmSync as rmSync2 } from "fs";
|
|
529
|
+
import { join } from "path";
|
|
530
|
+
import chalk7 from "chalk";
|
|
531
|
+
import ora from "ora";
|
|
532
|
+
import { Command as Command7, Option as Option7 } from "clipanion";
|
|
533
|
+
import { detectProvider, isLocalPath, getProvider } from "@skillkit/core";
|
|
534
|
+
import { isPathInside } from "@skillkit/core";
|
|
535
|
+
import { getAdapter as getAdapter4, detectAgent as detectAgent4 } from "@skillkit/agents";
|
|
536
|
+
var InstallCommand = class extends Command7 {
|
|
537
|
+
static paths = [["install"], ["i"]];
|
|
538
|
+
static usage = Command7.Usage({
|
|
539
|
+
description: "Install skills from GitHub, GitLab, Bitbucket, or local path",
|
|
540
|
+
examples: [
|
|
541
|
+
["Install from GitHub", "$0 install owner/repo"],
|
|
542
|
+
["Install from GitLab", "$0 install gitlab:owner/repo"],
|
|
543
|
+
["Install from Bitbucket", "$0 install bitbucket:owner/repo"],
|
|
544
|
+
["Install specific skills (CI/CD)", "$0 install owner/repo --skills=pdf,xlsx"],
|
|
545
|
+
["Install all skills non-interactively", "$0 install owner/repo --all"],
|
|
546
|
+
["Install from local path", "$0 install ./my-skills"],
|
|
547
|
+
["Install globally", "$0 install owner/repo --global"],
|
|
548
|
+
["List available skills", "$0 install owner/repo --list"],
|
|
549
|
+
["Install to specific agents", "$0 install owner/repo --agent claude-code --agent cursor"]
|
|
550
|
+
]
|
|
551
|
+
});
|
|
552
|
+
source = Option7.String({ required: true });
|
|
553
|
+
skills = Option7.String("--skills,-s", {
|
|
554
|
+
description: "Comma-separated list of skills to install (non-interactive)"
|
|
555
|
+
});
|
|
556
|
+
all = Option7.Boolean("--all,-a", false, {
|
|
557
|
+
description: "Install all discovered skills (non-interactive)"
|
|
558
|
+
});
|
|
559
|
+
yes = Option7.Boolean("--yes,-y", false, {
|
|
560
|
+
description: "Skip confirmation prompts"
|
|
561
|
+
});
|
|
562
|
+
global = Option7.Boolean("--global,-g", false, {
|
|
563
|
+
description: "Install to global skills directory"
|
|
564
|
+
});
|
|
565
|
+
force = Option7.Boolean("--force,-f", false, {
|
|
566
|
+
description: "Overwrite existing skills"
|
|
567
|
+
});
|
|
568
|
+
provider = Option7.String("--provider,-p", {
|
|
569
|
+
description: "Force specific provider (github, gitlab, bitbucket)"
|
|
570
|
+
});
|
|
571
|
+
list = Option7.Boolean("--list,-l", false, {
|
|
572
|
+
description: "List available skills without installing"
|
|
573
|
+
});
|
|
574
|
+
agent = Option7.Array("--agent", {
|
|
575
|
+
description: "Target specific agents (can specify multiple)"
|
|
576
|
+
});
|
|
577
|
+
async execute() {
|
|
578
|
+
const spinner = ora();
|
|
579
|
+
try {
|
|
580
|
+
let providerAdapter = detectProvider(this.source);
|
|
581
|
+
if (this.provider) {
|
|
582
|
+
providerAdapter = getProvider(this.provider);
|
|
583
|
+
}
|
|
584
|
+
if (!providerAdapter) {
|
|
585
|
+
console.error(chalk7.red(`Could not detect provider for: ${this.source}`));
|
|
586
|
+
console.error(chalk7.dim("Use --provider flag or specify source as:"));
|
|
587
|
+
console.error(chalk7.dim(" GitHub: owner/repo or https://github.com/owner/repo"));
|
|
588
|
+
console.error(chalk7.dim(" GitLab: gitlab:owner/repo or https://gitlab.com/owner/repo"));
|
|
589
|
+
console.error(chalk7.dim(" Bitbucket: bitbucket:owner/repo"));
|
|
590
|
+
console.error(chalk7.dim(" Local: ./path or ~/path"));
|
|
591
|
+
return 1;
|
|
592
|
+
}
|
|
593
|
+
spinner.start(`Fetching from ${providerAdapter.name}...`);
|
|
594
|
+
const result = await providerAdapter.clone(this.source, "", { depth: 1 });
|
|
595
|
+
if (!result.success || !result.path) {
|
|
596
|
+
spinner.fail(chalk7.red(result.error || "Failed to fetch source"));
|
|
597
|
+
return 1;
|
|
598
|
+
}
|
|
599
|
+
spinner.succeed(`Found ${result.skills?.length || 0} skill(s)`);
|
|
600
|
+
const discoveredSkills = result.discoveredSkills || [];
|
|
601
|
+
if (this.list) {
|
|
602
|
+
if (discoveredSkills.length === 0) {
|
|
603
|
+
console.log(chalk7.yellow("\nNo skills found in this repository"));
|
|
604
|
+
} else {
|
|
605
|
+
console.log(chalk7.cyan("\nAvailable skills:\n"));
|
|
606
|
+
for (const skill of discoveredSkills) {
|
|
607
|
+
console.log(` ${chalk7.green(skill.name)}`);
|
|
608
|
+
}
|
|
609
|
+
console.log();
|
|
610
|
+
console.log(chalk7.dim(`Total: ${discoveredSkills.length} skill(s)`));
|
|
611
|
+
console.log(chalk7.dim("\nTo install specific skills: skillkit install <source> --skills=skill1,skill2"));
|
|
612
|
+
console.log(chalk7.dim("To install all skills: skillkit install <source> --all"));
|
|
613
|
+
}
|
|
614
|
+
const cleanupPath2 = result.tempRoot || result.path;
|
|
615
|
+
if (!isLocalPath(this.source) && cleanupPath2 && existsSync3(cleanupPath2)) {
|
|
616
|
+
rmSync2(cleanupPath2, { recursive: true, force: true });
|
|
617
|
+
}
|
|
618
|
+
return 0;
|
|
619
|
+
}
|
|
620
|
+
let skillsToInstall = discoveredSkills;
|
|
621
|
+
if (this.skills) {
|
|
622
|
+
const requestedSkills = this.skills.split(",").map((s) => s.trim());
|
|
623
|
+
const available = discoveredSkills.map((s) => s.name);
|
|
624
|
+
const notFound = requestedSkills.filter((s) => !available.includes(s));
|
|
625
|
+
if (notFound.length > 0) {
|
|
626
|
+
console.error(chalk7.red(`Skills not found: ${notFound.join(", ")}`));
|
|
627
|
+
console.error(chalk7.dim(`Available: ${available.join(", ")}`));
|
|
628
|
+
return 1;
|
|
629
|
+
}
|
|
630
|
+
skillsToInstall = discoveredSkills.filter((s) => requestedSkills.includes(s.name));
|
|
631
|
+
} else if (this.all || this.yes) {
|
|
632
|
+
skillsToInstall = discoveredSkills;
|
|
633
|
+
} else {
|
|
634
|
+
skillsToInstall = discoveredSkills;
|
|
635
|
+
if (skillsToInstall.length > 0) {
|
|
636
|
+
console.log(chalk7.cyan("\nSkills to install:"));
|
|
637
|
+
skillsToInstall.forEach((s) => console.log(chalk7.dim(` - ${s.name}`)));
|
|
638
|
+
console.log();
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (skillsToInstall.length === 0) {
|
|
642
|
+
console.log(chalk7.yellow("No skills to install"));
|
|
643
|
+
return 0;
|
|
644
|
+
}
|
|
645
|
+
let targetAgents;
|
|
646
|
+
if (this.agent && this.agent.length > 0) {
|
|
647
|
+
targetAgents = this.agent;
|
|
648
|
+
} else {
|
|
649
|
+
const detectedAgent = await detectAgent4();
|
|
650
|
+
targetAgents = [detectedAgent];
|
|
651
|
+
}
|
|
652
|
+
let totalInstalled = 0;
|
|
653
|
+
const installResults = [];
|
|
654
|
+
for (const agentType of targetAgents) {
|
|
655
|
+
const adapter = getAdapter4(agentType);
|
|
656
|
+
const installDir = getInstallDir(this.global, agentType);
|
|
657
|
+
if (!existsSync3(installDir)) {
|
|
658
|
+
mkdirSync2(installDir, { recursive: true });
|
|
659
|
+
}
|
|
660
|
+
if (targetAgents.length > 1) {
|
|
661
|
+
console.log(chalk7.cyan(`
|
|
662
|
+
Installing to ${adapter.name}...`));
|
|
663
|
+
}
|
|
664
|
+
let installed = 0;
|
|
665
|
+
for (const skill of skillsToInstall) {
|
|
666
|
+
const skillName = skill.name;
|
|
667
|
+
const sourcePath = skill.path;
|
|
668
|
+
const targetPath = join(installDir, skillName);
|
|
669
|
+
if (existsSync3(targetPath) && !this.force) {
|
|
670
|
+
console.log(chalk7.yellow(` Skipping ${skillName} (already exists, use --force to overwrite)`));
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
const securityRoot = result.tempRoot || result.path;
|
|
674
|
+
if (!isPathInside(sourcePath, securityRoot)) {
|
|
675
|
+
console.log(chalk7.red(` Skipping ${skillName} (path traversal detected)`));
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
spinner.start(`Installing ${skillName}...`);
|
|
679
|
+
try {
|
|
680
|
+
if (existsSync3(targetPath)) {
|
|
681
|
+
rmSync2(targetPath, { recursive: true, force: true });
|
|
682
|
+
}
|
|
683
|
+
cpSync(sourcePath, targetPath, { recursive: true, dereference: true });
|
|
684
|
+
const metadata = {
|
|
685
|
+
name: skillName,
|
|
686
|
+
description: "",
|
|
687
|
+
source: this.source,
|
|
688
|
+
sourceType: providerAdapter.type,
|
|
689
|
+
subpath: skillName,
|
|
690
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
691
|
+
enabled: true
|
|
692
|
+
};
|
|
693
|
+
saveSkillMetadata(targetPath, metadata);
|
|
694
|
+
spinner.succeed(chalk7.green(`Installed ${skillName}`));
|
|
695
|
+
installed++;
|
|
696
|
+
} catch (error) {
|
|
697
|
+
spinner.fail(chalk7.red(`Failed to install ${skillName}`));
|
|
698
|
+
console.error(chalk7.dim(error instanceof Error ? error.message : String(error)));
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
totalInstalled += installed;
|
|
702
|
+
installResults.push({ agent: adapter.name, dir: installDir, count: installed });
|
|
703
|
+
}
|
|
704
|
+
const cleanupPath = result.tempRoot || result.path;
|
|
705
|
+
if (!isLocalPath(this.source) && cleanupPath && existsSync3(cleanupPath)) {
|
|
706
|
+
rmSync2(cleanupPath, { recursive: true, force: true });
|
|
707
|
+
}
|
|
708
|
+
console.log();
|
|
709
|
+
if (targetAgents.length > 1) {
|
|
710
|
+
console.log(chalk7.green(`Installed ${totalInstalled} skill(s) across ${targetAgents.length} agents:`));
|
|
711
|
+
for (const r of installResults) {
|
|
712
|
+
console.log(chalk7.dim(` - ${r.agent}: ${r.count} skill(s) to ${r.dir}`));
|
|
713
|
+
}
|
|
714
|
+
} else {
|
|
715
|
+
console.log(chalk7.green(`Installed ${totalInstalled} skill(s) to ${installResults[0]?.dir}`));
|
|
716
|
+
}
|
|
717
|
+
if (!this.yes) {
|
|
718
|
+
console.log(chalk7.dim("\nRun `skillkit sync` to update your agent config"));
|
|
719
|
+
}
|
|
720
|
+
return 0;
|
|
721
|
+
} catch (error) {
|
|
722
|
+
spinner.fail(chalk7.red("Installation failed"));
|
|
723
|
+
console.error(chalk7.dim(error instanceof Error ? error.message : String(error)));
|
|
724
|
+
return 1;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
// src/commands/update.ts
|
|
730
|
+
import { existsSync as existsSync4, rmSync as rmSync3, cpSync as cpSync2 } from "fs";
|
|
731
|
+
import { join as join2 } from "path";
|
|
732
|
+
import chalk8 from "chalk";
|
|
733
|
+
import ora2 from "ora";
|
|
734
|
+
import { Command as Command8, Option as Option8 } from "clipanion";
|
|
735
|
+
import { findAllSkills as findAllSkills3, findSkill as findSkill4, detectProvider as detectProvider2, isLocalPath as isLocalPath2 } from "@skillkit/core";
|
|
736
|
+
var UpdateCommand = class extends Command8 {
|
|
737
|
+
static paths = [["update"], ["u"]];
|
|
738
|
+
static usage = Command8.Usage({
|
|
739
|
+
description: "Update skills from their original sources",
|
|
740
|
+
examples: [
|
|
741
|
+
["Update all skills", "$0 update"],
|
|
742
|
+
["Update specific skills", "$0 update pdf xlsx"],
|
|
743
|
+
["Force update (overwrite local changes)", "$0 update --force"]
|
|
744
|
+
]
|
|
745
|
+
});
|
|
746
|
+
skills = Option8.Rest();
|
|
747
|
+
force = Option8.Boolean("--force,-f", false, {
|
|
748
|
+
description: "Force update even if local changes exist"
|
|
749
|
+
});
|
|
750
|
+
async execute() {
|
|
751
|
+
const spinner = ora2();
|
|
752
|
+
const searchDirs = getSearchDirs();
|
|
753
|
+
let skillsToUpdate;
|
|
754
|
+
if (this.skills.length > 0) {
|
|
755
|
+
skillsToUpdate = this.skills.map((name) => findSkill4(name, searchDirs)).filter((s) => s !== null);
|
|
756
|
+
const notFound = this.skills.filter((name) => !findSkill4(name, searchDirs));
|
|
757
|
+
if (notFound.length > 0) {
|
|
758
|
+
console.log(chalk8.yellow(`Skills not found: ${notFound.join(", ")}`));
|
|
759
|
+
}
|
|
760
|
+
} else {
|
|
761
|
+
skillsToUpdate = findAllSkills3(searchDirs);
|
|
762
|
+
}
|
|
763
|
+
if (skillsToUpdate.length === 0) {
|
|
764
|
+
console.log(chalk8.yellow("No skills to update"));
|
|
765
|
+
return 0;
|
|
766
|
+
}
|
|
767
|
+
console.log(chalk8.cyan(`Updating ${skillsToUpdate.length} skill(s)...
|
|
768
|
+
`));
|
|
769
|
+
let updated = 0;
|
|
770
|
+
let skipped = 0;
|
|
771
|
+
let failed = 0;
|
|
772
|
+
for (const skill of skillsToUpdate) {
|
|
773
|
+
const metadata = loadSkillMetadata(skill.path);
|
|
774
|
+
if (!metadata) {
|
|
775
|
+
console.log(chalk8.dim(`Skipping ${skill.name} (no metadata, reinstall needed)`));
|
|
776
|
+
skipped++;
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
spinner.start(`Updating ${skill.name}...`);
|
|
780
|
+
try {
|
|
781
|
+
if (isLocalPath2(metadata.source)) {
|
|
782
|
+
const localPath = metadata.subpath ? join2(metadata.source, metadata.subpath) : metadata.source;
|
|
783
|
+
if (!existsSync4(localPath)) {
|
|
784
|
+
spinner.warn(chalk8.yellow(`${skill.name}: local source missing`));
|
|
785
|
+
skipped++;
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
const skillMdPath = join2(localPath, "SKILL.md");
|
|
789
|
+
if (!existsSync4(skillMdPath)) {
|
|
790
|
+
spinner.warn(chalk8.yellow(`${skill.name}: no SKILL.md at source`));
|
|
791
|
+
skipped++;
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
rmSync3(skill.path, { recursive: true, force: true });
|
|
795
|
+
cpSync2(localPath, skill.path, { recursive: true, dereference: true });
|
|
796
|
+
metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
797
|
+
saveSkillMetadata(skill.path, metadata);
|
|
798
|
+
spinner.succeed(chalk8.green(`Updated ${skill.name}`));
|
|
799
|
+
updated++;
|
|
800
|
+
} else {
|
|
801
|
+
const provider = detectProvider2(metadata.source);
|
|
802
|
+
if (!provider) {
|
|
803
|
+
spinner.warn(chalk8.yellow(`${skill.name}: unknown provider`));
|
|
804
|
+
skipped++;
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
const result = await provider.clone(metadata.source, "", { depth: 1 });
|
|
808
|
+
if (!result.success || !result.path) {
|
|
809
|
+
spinner.fail(chalk8.red(`${skill.name}: ${result.error || "clone failed"}`));
|
|
810
|
+
failed++;
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
const sourcePath = metadata.subpath ? join2(result.path, metadata.subpath) : result.path;
|
|
814
|
+
const skillMdPath = join2(sourcePath, "SKILL.md");
|
|
815
|
+
if (!existsSync4(skillMdPath)) {
|
|
816
|
+
spinner.warn(chalk8.yellow(`${skill.name}: no SKILL.md in source`));
|
|
817
|
+
rmSync3(result.path, { recursive: true, force: true });
|
|
818
|
+
skipped++;
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
rmSync3(skill.path, { recursive: true, force: true });
|
|
822
|
+
cpSync2(sourcePath, skill.path, { recursive: true, dereference: true });
|
|
823
|
+
rmSync3(result.path, { recursive: true, force: true });
|
|
824
|
+
metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
825
|
+
saveSkillMetadata(skill.path, metadata);
|
|
826
|
+
spinner.succeed(chalk8.green(`Updated ${skill.name}`));
|
|
827
|
+
updated++;
|
|
828
|
+
}
|
|
829
|
+
} catch (error) {
|
|
830
|
+
spinner.fail(chalk8.red(`Failed to update ${skill.name}`));
|
|
831
|
+
console.error(chalk8.dim(error instanceof Error ? error.message : String(error)));
|
|
832
|
+
failed++;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
console.log();
|
|
836
|
+
console.log(
|
|
837
|
+
chalk8.cyan(
|
|
838
|
+
`Updated: ${updated}, Skipped: ${skipped}, Failed: ${failed}`
|
|
839
|
+
)
|
|
840
|
+
);
|
|
841
|
+
return failed > 0 ? 1 : 0;
|
|
842
|
+
}
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
// src/commands/validate.ts
|
|
846
|
+
import { existsSync as existsSync5, readdirSync } from "fs";
|
|
847
|
+
import { join as join3, basename } from "path";
|
|
848
|
+
import chalk9 from "chalk";
|
|
849
|
+
import { Command as Command9, Option as Option9 } from "clipanion";
|
|
850
|
+
import { validateSkill } from "@skillkit/core";
|
|
851
|
+
var ValidateCommand = class extends Command9 {
|
|
852
|
+
static paths = [["validate"], ["v"]];
|
|
853
|
+
static usage = Command9.Usage({
|
|
854
|
+
description: "Validate skill(s) against the Agent Skills specification (agentskills.io)",
|
|
855
|
+
examples: [
|
|
856
|
+
["Validate a skill directory", "$0 validate ./my-skill"],
|
|
857
|
+
["Validate all skills in a directory", "$0 validate ./skills --all"]
|
|
858
|
+
]
|
|
859
|
+
});
|
|
860
|
+
skillPath = Option9.String({ required: true });
|
|
861
|
+
all = Option9.Boolean("--all,-a", false, {
|
|
862
|
+
description: "Validate all skills in the directory"
|
|
863
|
+
});
|
|
864
|
+
async execute() {
|
|
865
|
+
const targetPath = this.skillPath;
|
|
866
|
+
if (!existsSync5(targetPath)) {
|
|
867
|
+
console.error(chalk9.red(`Path does not exist: ${targetPath}`));
|
|
868
|
+
return 1;
|
|
869
|
+
}
|
|
870
|
+
const skillPaths = [];
|
|
871
|
+
if (this.all) {
|
|
872
|
+
const entries = readdirSync(targetPath, { withFileTypes: true });
|
|
873
|
+
for (const entry of entries) {
|
|
874
|
+
if (entry.isDirectory()) {
|
|
875
|
+
const skillPath = join3(targetPath, entry.name);
|
|
876
|
+
if (existsSync5(join3(skillPath, "SKILL.md"))) {
|
|
877
|
+
skillPaths.push(skillPath);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
if (skillPaths.length === 0) {
|
|
882
|
+
console.error(chalk9.yellow("No skills found in directory"));
|
|
883
|
+
return 1;
|
|
884
|
+
}
|
|
885
|
+
} else {
|
|
886
|
+
skillPaths.push(targetPath);
|
|
887
|
+
}
|
|
888
|
+
let hasErrors = false;
|
|
889
|
+
for (const skillPath of skillPaths) {
|
|
890
|
+
const skillName = basename(skillPath);
|
|
891
|
+
const result = validateSkill(skillPath);
|
|
892
|
+
if (result.valid) {
|
|
893
|
+
console.log(chalk9.green(`\u2713 ${skillName}`));
|
|
894
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
895
|
+
result.warnings.forEach((w) => {
|
|
896
|
+
console.log(chalk9.yellow(` \u26A0 ${w}`));
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
} else {
|
|
900
|
+
console.log(chalk9.red(`\u2717 ${skillName}`));
|
|
901
|
+
result.errors.forEach((e) => {
|
|
902
|
+
console.log(chalk9.red(` \u2022 ${e}`));
|
|
903
|
+
});
|
|
904
|
+
hasErrors = true;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
console.log();
|
|
908
|
+
if (hasErrors) {
|
|
909
|
+
console.log(chalk9.red("Validation failed"));
|
|
910
|
+
console.log(chalk9.dim("See https://agentskills.io/specification for the complete format"));
|
|
911
|
+
return 1;
|
|
912
|
+
}
|
|
913
|
+
console.log(chalk9.green(`Validated ${skillPaths.length} skill(s) successfully`));
|
|
914
|
+
return 0;
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
// src/commands/create.ts
|
|
919
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
920
|
+
import { join as join4 } from "path";
|
|
921
|
+
import chalk10 from "chalk";
|
|
922
|
+
import { Command as Command10, Option as Option10 } from "clipanion";
|
|
923
|
+
var CreateCommand = class extends Command10 {
|
|
924
|
+
static paths = [["create"], ["new"]];
|
|
925
|
+
static usage = Command10.Usage({
|
|
926
|
+
description: "Create a new skill with proper structure",
|
|
927
|
+
examples: [
|
|
928
|
+
["Create a new skill", "$0 create my-skill"],
|
|
929
|
+
["Create with all optional directories", "$0 create my-skill --full"],
|
|
930
|
+
["Create with scripts directory", "$0 create my-skill --scripts"]
|
|
931
|
+
]
|
|
932
|
+
});
|
|
933
|
+
name = Option10.String({ required: true, name: "skill-name" });
|
|
934
|
+
full = Option10.Boolean("--full,-f", false, {
|
|
935
|
+
description: "Include all optional directories (references, scripts, assets)"
|
|
936
|
+
});
|
|
937
|
+
scripts = Option10.Boolean("--scripts", false, {
|
|
938
|
+
description: "Include scripts directory"
|
|
939
|
+
});
|
|
940
|
+
references = Option10.Boolean("--references", false, {
|
|
941
|
+
description: "Include references directory"
|
|
942
|
+
});
|
|
943
|
+
assets = Option10.Boolean("--assets", false, {
|
|
944
|
+
description: "Include assets directory"
|
|
945
|
+
});
|
|
946
|
+
directory = Option10.String("--dir,-d", {
|
|
947
|
+
description: "Parent directory to create skill in (default: current directory)"
|
|
948
|
+
});
|
|
949
|
+
async execute() {
|
|
950
|
+
const skillName = this.name.toLowerCase();
|
|
951
|
+
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(skillName)) {
|
|
952
|
+
console.error(chalk10.red("Invalid skill name"));
|
|
953
|
+
console.error(chalk10.dim("Must be lowercase alphanumeric with hyphens (e.g., my-skill)"));
|
|
954
|
+
return 1;
|
|
955
|
+
}
|
|
956
|
+
const parentDir = this.directory || process.cwd();
|
|
957
|
+
const skillDir = join4(parentDir, skillName);
|
|
958
|
+
if (existsSync6(skillDir)) {
|
|
959
|
+
console.error(chalk10.red(`Directory already exists: ${skillDir}`));
|
|
960
|
+
return 1;
|
|
961
|
+
}
|
|
962
|
+
try {
|
|
963
|
+
mkdirSync3(skillDir, { recursive: true });
|
|
964
|
+
const skillMd = generateSkillMd(skillName);
|
|
965
|
+
writeFileSync2(join4(skillDir, "SKILL.md"), skillMd);
|
|
966
|
+
if (this.full || this.references) {
|
|
967
|
+
const refsDir = join4(skillDir, "references");
|
|
968
|
+
mkdirSync3(refsDir);
|
|
969
|
+
writeFileSync2(join4(refsDir, ".gitkeep"), "");
|
|
970
|
+
}
|
|
971
|
+
if (this.full || this.scripts) {
|
|
972
|
+
const scriptsDir = join4(skillDir, "scripts");
|
|
973
|
+
mkdirSync3(scriptsDir);
|
|
974
|
+
writeFileSync2(join4(scriptsDir, ".gitkeep"), "");
|
|
975
|
+
}
|
|
976
|
+
if (this.full || this.assets) {
|
|
977
|
+
const assetsDir = join4(skillDir, "assets");
|
|
978
|
+
mkdirSync3(assetsDir);
|
|
979
|
+
writeFileSync2(join4(assetsDir, ".gitkeep"), "");
|
|
980
|
+
}
|
|
981
|
+
console.log(chalk10.green(`Created skill: ${skillName}`));
|
|
982
|
+
console.log();
|
|
983
|
+
console.log(chalk10.dim("Structure:"));
|
|
984
|
+
console.log(chalk10.dim(` ${skillDir}/`));
|
|
985
|
+
console.log(chalk10.dim(" \u251C\u2500\u2500 SKILL.md"));
|
|
986
|
+
if (this.full || this.references) console.log(chalk10.dim(" \u251C\u2500\u2500 references/"));
|
|
987
|
+
if (this.full || this.scripts) console.log(chalk10.dim(" \u251C\u2500\u2500 scripts/"));
|
|
988
|
+
if (this.full || this.assets) console.log(chalk10.dim(" \u2514\u2500\u2500 assets/"));
|
|
989
|
+
console.log();
|
|
990
|
+
console.log(chalk10.cyan("Next steps:"));
|
|
991
|
+
console.log(chalk10.dim(" 1. Edit SKILL.md with your instructions"));
|
|
992
|
+
console.log(chalk10.dim(" 2. Validate: skillkit validate " + skillDir));
|
|
993
|
+
console.log(chalk10.dim(" 3. Test: skillkit read " + skillName));
|
|
994
|
+
return 0;
|
|
995
|
+
} catch (error) {
|
|
996
|
+
console.error(chalk10.red("Failed to create skill"));
|
|
997
|
+
console.error(chalk10.dim(error instanceof Error ? error.message : String(error)));
|
|
998
|
+
return 1;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
function generateSkillMd(name) {
|
|
1003
|
+
const title = name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
1004
|
+
return `---
|
|
1005
|
+
name: ${name}
|
|
1006
|
+
description: Describe what this skill does and when to use it. Include trigger keywords.
|
|
1007
|
+
---
|
|
1008
|
+
|
|
1009
|
+
# ${title}
|
|
1010
|
+
|
|
1011
|
+
Instructions for the AI agent on how to use this skill.
|
|
1012
|
+
|
|
1013
|
+
## When to Use
|
|
1014
|
+
|
|
1015
|
+
- Scenario 1
|
|
1016
|
+
- Scenario 2
|
|
1017
|
+
|
|
1018
|
+
## Steps
|
|
1019
|
+
|
|
1020
|
+
1. First step
|
|
1021
|
+
2. Second step
|
|
1022
|
+
3. Third step
|
|
1023
|
+
`;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// src/commands/ui.ts
|
|
1027
|
+
import { Command as Command11 } from "clipanion";
|
|
1028
|
+
var UICommand = class extends Command11 {
|
|
1029
|
+
static paths = [["ui"], ["tui"]];
|
|
1030
|
+
static usage = Command11.Usage({
|
|
1031
|
+
description: "Launch the interactive TUI (Terminal User Interface)",
|
|
1032
|
+
examples: [
|
|
1033
|
+
["Open interactive TUI", "$0 ui"],
|
|
1034
|
+
["Alias for TUI", "$0 tui"]
|
|
1035
|
+
]
|
|
1036
|
+
});
|
|
1037
|
+
async execute() {
|
|
1038
|
+
const { startTUI } = await import("@skillkit/tui");
|
|
1039
|
+
await startTUI();
|
|
1040
|
+
return 0;
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1043
|
+
|
|
1044
|
+
// src/commands/translate.ts
|
|
1045
|
+
import { Command as Command12, Option as Option11 } from "clipanion";
|
|
1046
|
+
import { existsSync as existsSync7, readFileSync as readFileSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
|
|
1047
|
+
import { join as join5, basename as basename2, dirname as dirname2 } from "path";
|
|
1048
|
+
import chalk11 from "chalk";
|
|
1049
|
+
import {
|
|
1050
|
+
translateSkill,
|
|
1051
|
+
translateSkillFile,
|
|
1052
|
+
getSupportedTranslationAgents,
|
|
1053
|
+
translatorRegistry,
|
|
1054
|
+
findAllSkills as findAllSkills4
|
|
1055
|
+
} from "@skillkit/core";
|
|
1056
|
+
import { getAdapter as getAdapter5, getAllAdapters as getAllAdapters2 } from "@skillkit/agents";
|
|
1057
|
+
var TranslateCommand = class extends Command12 {
|
|
1058
|
+
static paths = [["translate"]];
|
|
1059
|
+
static usage = Command12.Usage({
|
|
1060
|
+
description: "Translate skills between different AI agent formats",
|
|
1061
|
+
details: `
|
|
1062
|
+
This command translates skills from one AI agent format to another.
|
|
1063
|
+
|
|
1064
|
+
Supported formats:
|
|
1065
|
+
- SKILL.md (Claude Code, Codex, Gemini CLI, and 10+ more)
|
|
1066
|
+
- Cursor MDC (.mdc files with globs)
|
|
1067
|
+
- Windsurf rules (.windsurfrules)
|
|
1068
|
+
- GitHub Copilot instructions (copilot-instructions.md)
|
|
1069
|
+
|
|
1070
|
+
Translation is bidirectional - you can translate from any format to any other.
|
|
1071
|
+
`,
|
|
1072
|
+
examples: [
|
|
1073
|
+
["Translate a skill to Cursor format", "$0 translate my-skill --to cursor"],
|
|
1074
|
+
["Translate all skills to Windsurf", "$0 translate --all --to windsurf"],
|
|
1075
|
+
["Translate a file directly", "$0 translate ./SKILL.md --to cursor --output ./my-skill.mdc"],
|
|
1076
|
+
["List all supported agents", "$0 translate --list"],
|
|
1077
|
+
["Preview translation without writing", "$0 translate my-skill --to cursor --dry-run"]
|
|
1078
|
+
]
|
|
1079
|
+
});
|
|
1080
|
+
// Skill name or path
|
|
1081
|
+
source = Option11.String({ required: false });
|
|
1082
|
+
// Target agent
|
|
1083
|
+
to = Option11.String("--to,-t", {
|
|
1084
|
+
description: "Target agent to translate to"
|
|
1085
|
+
});
|
|
1086
|
+
// Source agent (auto-detected if not specified)
|
|
1087
|
+
from = Option11.String("--from,-f", {
|
|
1088
|
+
description: "Source agent format (auto-detected if not specified)"
|
|
1089
|
+
});
|
|
1090
|
+
// Output path
|
|
1091
|
+
output = Option11.String("--output,-o", {
|
|
1092
|
+
description: "Output file path (default: agent skills directory)"
|
|
1093
|
+
});
|
|
1094
|
+
// Translate all installed skills
|
|
1095
|
+
all = Option11.Boolean("--all,-a", false, {
|
|
1096
|
+
description: "Translate all installed skills"
|
|
1097
|
+
});
|
|
1098
|
+
// Dry run (preview without writing)
|
|
1099
|
+
dryRun = Option11.Boolean("--dry-run,-n", false, {
|
|
1100
|
+
description: "Preview translation without writing files"
|
|
1101
|
+
});
|
|
1102
|
+
// Add metadata comments
|
|
1103
|
+
metadata = Option11.Boolean("--metadata,-m", false, {
|
|
1104
|
+
description: "Add translation metadata to output"
|
|
1105
|
+
});
|
|
1106
|
+
// Force overwrite
|
|
1107
|
+
force = Option11.Boolean("--force", false, {
|
|
1108
|
+
description: "Overwrite existing files"
|
|
1109
|
+
});
|
|
1110
|
+
// List supported agents
|
|
1111
|
+
list = Option11.Boolean("--list,-l", false, {
|
|
1112
|
+
description: "List all supported agents and formats"
|
|
1113
|
+
});
|
|
1114
|
+
// Show compatibility info
|
|
1115
|
+
compat = Option11.Boolean("--compat,-c", false, {
|
|
1116
|
+
description: "Show compatibility info between agents"
|
|
1117
|
+
});
|
|
1118
|
+
async execute() {
|
|
1119
|
+
if (this.list) {
|
|
1120
|
+
return this.listAgents();
|
|
1121
|
+
}
|
|
1122
|
+
if (this.compat) {
|
|
1123
|
+
return this.showCompatibility();
|
|
1124
|
+
}
|
|
1125
|
+
if (!this.to) {
|
|
1126
|
+
console.error(chalk11.red("Error: --to/-t target agent is required"));
|
|
1127
|
+
console.log(chalk11.gray("Use --list to see all supported agents"));
|
|
1128
|
+
return 1;
|
|
1129
|
+
}
|
|
1130
|
+
const targetAgent = this.to;
|
|
1131
|
+
if (!getSupportedTranslationAgents().includes(targetAgent)) {
|
|
1132
|
+
console.error(chalk11.red(`Error: Unknown target agent "${this.to}"`));
|
|
1133
|
+
console.log(chalk11.gray("Use --list to see all supported agents"));
|
|
1134
|
+
return 1;
|
|
1135
|
+
}
|
|
1136
|
+
if (this.all) {
|
|
1137
|
+
return this.translateAll(targetAgent);
|
|
1138
|
+
}
|
|
1139
|
+
if (!this.source) {
|
|
1140
|
+
console.error(chalk11.red("Error: Please specify a skill name or path, or use --all"));
|
|
1141
|
+
return 1;
|
|
1142
|
+
}
|
|
1143
|
+
return this.translateSingle(this.source, targetAgent);
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* List all supported agents and their formats
|
|
1147
|
+
*/
|
|
1148
|
+
listAgents() {
|
|
1149
|
+
console.log(chalk11.bold("\nSupported Agents for Translation:\n"));
|
|
1150
|
+
const agents = getSupportedTranslationAgents();
|
|
1151
|
+
const adapters = getAllAdapters2();
|
|
1152
|
+
const byFormat = {};
|
|
1153
|
+
for (const agent of agents) {
|
|
1154
|
+
const format = translatorRegistry.getFormatForAgent(agent);
|
|
1155
|
+
if (!byFormat[format]) byFormat[format] = [];
|
|
1156
|
+
byFormat[format].push(agent);
|
|
1157
|
+
}
|
|
1158
|
+
const formatNames = {
|
|
1159
|
+
"skill-md": "SKILL.md Format (Standard)",
|
|
1160
|
+
"cursor-mdc": "Cursor MDC Format",
|
|
1161
|
+
"markdown-rules": "Markdown Rules Format",
|
|
1162
|
+
"external": "External Systems"
|
|
1163
|
+
};
|
|
1164
|
+
for (const [format, formatAgents] of Object.entries(byFormat)) {
|
|
1165
|
+
console.log(chalk11.cyan(` ${formatNames[format] || format}:`));
|
|
1166
|
+
for (const agent of formatAgents) {
|
|
1167
|
+
const adapter = adapters.find((a) => a.type === agent);
|
|
1168
|
+
const name = adapter?.name || agent;
|
|
1169
|
+
console.log(` ${chalk11.green(agent.padEnd(16))} ${chalk11.gray(name)}`);
|
|
1170
|
+
}
|
|
1171
|
+
console.log();
|
|
1172
|
+
}
|
|
1173
|
+
console.log(chalk11.gray("All formats can translate to any other format."));
|
|
1174
|
+
console.log(chalk11.gray("Use --compat to see compatibility details.\n"));
|
|
1175
|
+
return 0;
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Show compatibility between agents
|
|
1179
|
+
*/
|
|
1180
|
+
showCompatibility() {
|
|
1181
|
+
if (!this.from || !this.to) {
|
|
1182
|
+
console.error(chalk11.red("Error: Both --from and --to are required for compatibility check"));
|
|
1183
|
+
return 1;
|
|
1184
|
+
}
|
|
1185
|
+
const fromAgent = this.from;
|
|
1186
|
+
const toAgent = this.to;
|
|
1187
|
+
const info = translatorRegistry.getCompatibilityInfo(fromAgent, toAgent);
|
|
1188
|
+
console.log(chalk11.bold(`
|
|
1189
|
+
Translation: ${fromAgent} \u2192 ${toAgent}
|
|
1190
|
+
`));
|
|
1191
|
+
if (info.supported) {
|
|
1192
|
+
console.log(chalk11.green(" \u2713 Translation supported"));
|
|
1193
|
+
} else {
|
|
1194
|
+
console.log(chalk11.red(" \u2717 Translation not supported"));
|
|
1195
|
+
return 1;
|
|
1196
|
+
}
|
|
1197
|
+
if (info.warnings.length > 0) {
|
|
1198
|
+
console.log(chalk11.yellow("\n Warnings:"));
|
|
1199
|
+
for (const warning of info.warnings) {
|
|
1200
|
+
console.log(chalk11.yellow(` \u2022 ${warning}`));
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
if (info.lossyFields.length > 0) {
|
|
1204
|
+
console.log(chalk11.gray("\n Fields with reduced functionality:"));
|
|
1205
|
+
for (const field of info.lossyFields) {
|
|
1206
|
+
console.log(chalk11.gray(` \u2022 ${field}`));
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
console.log();
|
|
1210
|
+
return 0;
|
|
1211
|
+
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Translate all installed skills
|
|
1214
|
+
*/
|
|
1215
|
+
async translateAll(targetAgent) {
|
|
1216
|
+
const searchDirs = getSearchDirs();
|
|
1217
|
+
const skills = findAllSkills4(searchDirs);
|
|
1218
|
+
if (skills.length === 0) {
|
|
1219
|
+
console.log(chalk11.yellow("No skills found to translate"));
|
|
1220
|
+
return 0;
|
|
1221
|
+
}
|
|
1222
|
+
console.log(chalk11.bold(`
|
|
1223
|
+
Translating ${skills.length} skill(s) to ${targetAgent}...
|
|
1224
|
+
`));
|
|
1225
|
+
let success = 0;
|
|
1226
|
+
let failed = 0;
|
|
1227
|
+
for (const skill of skills) {
|
|
1228
|
+
const skillMdPath = join5(skill.path, "SKILL.md");
|
|
1229
|
+
if (!existsSync7(skillMdPath)) {
|
|
1230
|
+
console.log(chalk11.yellow(` \u26A0 ${skill.name}: No SKILL.md found`));
|
|
1231
|
+
failed++;
|
|
1232
|
+
continue;
|
|
1233
|
+
}
|
|
1234
|
+
const result = translateSkillFile(skillMdPath, targetAgent, {
|
|
1235
|
+
addMetadata: this.metadata
|
|
1236
|
+
});
|
|
1237
|
+
if (result.success) {
|
|
1238
|
+
if (this.dryRun) {
|
|
1239
|
+
console.log(chalk11.green(` \u2713 ${skill.name} \u2192 ${result.filename} (dry run)`));
|
|
1240
|
+
if (result.warnings.length > 0) {
|
|
1241
|
+
for (const warning of result.warnings) {
|
|
1242
|
+
console.log(chalk11.gray(` ${warning}`));
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
} else {
|
|
1246
|
+
const targetAdapter = getAdapter5(targetAgent);
|
|
1247
|
+
const outputDir = this.output || join5(process.cwd(), targetAdapter.skillsDir, skill.name);
|
|
1248
|
+
if (!existsSync7(outputDir)) {
|
|
1249
|
+
mkdirSync4(outputDir, { recursive: true });
|
|
1250
|
+
}
|
|
1251
|
+
const outputPath = join5(outputDir, result.filename);
|
|
1252
|
+
if (existsSync7(outputPath) && !this.force) {
|
|
1253
|
+
console.log(chalk11.yellow(` \u26A0 ${skill.name}: ${outputPath} exists (use --force)`));
|
|
1254
|
+
failed++;
|
|
1255
|
+
continue;
|
|
1256
|
+
}
|
|
1257
|
+
writeFileSync3(outputPath, result.content, "utf-8");
|
|
1258
|
+
console.log(chalk11.green(` \u2713 ${skill.name} \u2192 ${outputPath}`));
|
|
1259
|
+
}
|
|
1260
|
+
if (result.incompatible.length > 0) {
|
|
1261
|
+
for (const item of result.incompatible) {
|
|
1262
|
+
console.log(chalk11.gray(` \u26A0 ${item}`));
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
success++;
|
|
1266
|
+
} else {
|
|
1267
|
+
console.log(chalk11.red(` \u2717 ${skill.name}: Translation failed`));
|
|
1268
|
+
for (const item of result.incompatible) {
|
|
1269
|
+
console.log(chalk11.red(` ${item}`));
|
|
1270
|
+
}
|
|
1271
|
+
failed++;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
console.log();
|
|
1275
|
+
console.log(chalk11.bold(`Translated: ${success}, Failed: ${failed}`));
|
|
1276
|
+
return failed > 0 ? 1 : 0;
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Translate a single skill
|
|
1280
|
+
*/
|
|
1281
|
+
async translateSingle(source, targetAgent) {
|
|
1282
|
+
let sourcePath;
|
|
1283
|
+
let skillName;
|
|
1284
|
+
if (existsSync7(source)) {
|
|
1285
|
+
sourcePath = source;
|
|
1286
|
+
skillName = basename2(dirname2(source));
|
|
1287
|
+
if (skillName === ".") {
|
|
1288
|
+
skillName = basename2(source).replace(/\.(md|mdc)$/i, "");
|
|
1289
|
+
}
|
|
1290
|
+
} else {
|
|
1291
|
+
const searchDirs = getSearchDirs();
|
|
1292
|
+
let found = false;
|
|
1293
|
+
for (const dir of searchDirs) {
|
|
1294
|
+
const skillPath = join5(dir, source);
|
|
1295
|
+
const skillMdPath = join5(skillPath, "SKILL.md");
|
|
1296
|
+
if (existsSync7(skillMdPath)) {
|
|
1297
|
+
sourcePath = skillMdPath;
|
|
1298
|
+
skillName = source;
|
|
1299
|
+
found = true;
|
|
1300
|
+
break;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
if (!found) {
|
|
1304
|
+
console.error(chalk11.red(`Error: Skill "${source}" not found`));
|
|
1305
|
+
console.log(chalk11.gray("Searched in:"));
|
|
1306
|
+
for (const dir of searchDirs) {
|
|
1307
|
+
console.log(chalk11.gray(` ${dir}`));
|
|
1308
|
+
}
|
|
1309
|
+
return 1;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
const content = readFileSync2(sourcePath, "utf-8");
|
|
1313
|
+
const result = translateSkill(content, targetAgent, {
|
|
1314
|
+
addMetadata: this.metadata,
|
|
1315
|
+
sourceFilename: basename2(sourcePath)
|
|
1316
|
+
});
|
|
1317
|
+
if (!result.success) {
|
|
1318
|
+
console.error(chalk11.red("Translation failed:"));
|
|
1319
|
+
for (const item of result.incompatible) {
|
|
1320
|
+
console.error(chalk11.red(` ${item}`));
|
|
1321
|
+
}
|
|
1322
|
+
return 1;
|
|
1323
|
+
}
|
|
1324
|
+
if (result.warnings.length > 0) {
|
|
1325
|
+
console.log(chalk11.yellow("\nWarnings:"));
|
|
1326
|
+
for (const warning of result.warnings) {
|
|
1327
|
+
console.log(chalk11.yellow(` \u2022 ${warning}`));
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
if (result.incompatible.length > 0) {
|
|
1331
|
+
console.log(chalk11.gray("\nIncompatible features:"));
|
|
1332
|
+
for (const item of result.incompatible) {
|
|
1333
|
+
console.log(chalk11.gray(` \u2022 ${item}`));
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
if (this.dryRun) {
|
|
1337
|
+
console.log(chalk11.bold(`
|
|
1338
|
+
Translated content (${result.filename}):
|
|
1339
|
+
`));
|
|
1340
|
+
console.log(chalk11.gray("\u2500".repeat(60)));
|
|
1341
|
+
console.log(result.content);
|
|
1342
|
+
console.log(chalk11.gray("\u2500".repeat(60)));
|
|
1343
|
+
return 0;
|
|
1344
|
+
}
|
|
1345
|
+
let outputPath;
|
|
1346
|
+
if (this.output) {
|
|
1347
|
+
outputPath = this.output;
|
|
1348
|
+
} else {
|
|
1349
|
+
const targetAdapter = getAdapter5(targetAgent);
|
|
1350
|
+
const outputDir2 = join5(process.cwd(), targetAdapter.skillsDir, skillName);
|
|
1351
|
+
if (!existsSync7(outputDir2)) {
|
|
1352
|
+
mkdirSync4(outputDir2, { recursive: true });
|
|
1353
|
+
}
|
|
1354
|
+
outputPath = join5(outputDir2, result.filename);
|
|
1355
|
+
}
|
|
1356
|
+
if (existsSync7(outputPath) && !this.force) {
|
|
1357
|
+
console.error(chalk11.red(`Error: ${outputPath} already exists`));
|
|
1358
|
+
console.log(chalk11.gray("Use --force to overwrite"));
|
|
1359
|
+
return 1;
|
|
1360
|
+
}
|
|
1361
|
+
const outputDir = dirname2(outputPath);
|
|
1362
|
+
if (!existsSync7(outputDir)) {
|
|
1363
|
+
mkdirSync4(outputDir, { recursive: true });
|
|
1364
|
+
}
|
|
1365
|
+
writeFileSync3(outputPath, result.content, "utf-8");
|
|
1366
|
+
console.log(chalk11.green(`
|
|
1367
|
+
\u2713 Translated to ${outputPath}`));
|
|
1368
|
+
console.log(chalk11.gray(` Format: ${result.targetFormat}`));
|
|
1369
|
+
console.log(chalk11.gray(` Agent: ${result.targetAgent}`));
|
|
1370
|
+
return 0;
|
|
1371
|
+
}
|
|
1372
|
+
};
|
|
1373
|
+
|
|
1374
|
+
// src/commands/context.ts
|
|
1375
|
+
import { Command as Command13, Option as Option12 } from "clipanion";
|
|
1376
|
+
import { existsSync as existsSync8, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
1377
|
+
import { resolve } from "path";
|
|
1378
|
+
import chalk12 from "chalk";
|
|
1379
|
+
import {
|
|
1380
|
+
ContextManager,
|
|
1381
|
+
createContextSync,
|
|
1382
|
+
analyzeProject,
|
|
1383
|
+
getStackTags
|
|
1384
|
+
} from "@skillkit/core";
|
|
1385
|
+
import { getAllAdapters as getAllAdapters3 } from "@skillkit/agents";
|
|
1386
|
+
var ContextCommand = class extends Command13 {
|
|
1387
|
+
static paths = [["context"]];
|
|
1388
|
+
static usage = Command13.Usage({
|
|
1389
|
+
description: "Manage project context for multi-agent skill synchronization",
|
|
1390
|
+
details: `
|
|
1391
|
+
The context command helps you configure your project once and sync skills
|
|
1392
|
+
across all AI coding agents.
|
|
1393
|
+
|
|
1394
|
+
Subcommands:
|
|
1395
|
+
- init: Initialize project context with auto-detection
|
|
1396
|
+
- show: Display current project context
|
|
1397
|
+
- export: Export context to a file
|
|
1398
|
+
- import: Import context from a file
|
|
1399
|
+
- sync: Sync skills to all configured agents
|
|
1400
|
+
- detect: Run project detection
|
|
1401
|
+
`,
|
|
1402
|
+
examples: [
|
|
1403
|
+
["Initialize project context", "$0 context init"],
|
|
1404
|
+
["Show current context", "$0 context show"],
|
|
1405
|
+
["Sync skills to all agents", "$0 context sync"],
|
|
1406
|
+
["Sync to specific agent", "$0 context sync --agent cursor"],
|
|
1407
|
+
["Export context", "$0 context export --output context.yaml"]
|
|
1408
|
+
]
|
|
1409
|
+
});
|
|
1410
|
+
// Subcommand (init, show, export, import, sync, detect)
|
|
1411
|
+
action = Option12.String({ required: false });
|
|
1412
|
+
// Agent filter
|
|
1413
|
+
agent = Option12.String("--agent,-a", {
|
|
1414
|
+
description: "Target agent for sync"
|
|
1415
|
+
});
|
|
1416
|
+
// Output file for export
|
|
1417
|
+
output = Option12.String("--output,-o", {
|
|
1418
|
+
description: "Output file path"
|
|
1419
|
+
});
|
|
1420
|
+
// Input file for import
|
|
1421
|
+
input = Option12.String("--input,-i", {
|
|
1422
|
+
description: "Input file path"
|
|
1423
|
+
});
|
|
1424
|
+
// Force overwrite
|
|
1425
|
+
force = Option12.Boolean("--force,-f", false, {
|
|
1426
|
+
description: "Force overwrite existing files"
|
|
1427
|
+
});
|
|
1428
|
+
// Dry run
|
|
1429
|
+
dryRun = Option12.Boolean("--dry-run,-n", false, {
|
|
1430
|
+
description: "Preview without making changes"
|
|
1431
|
+
});
|
|
1432
|
+
// Merge on import
|
|
1433
|
+
merge = Option12.Boolean("--merge,-m", false, {
|
|
1434
|
+
description: "Merge with existing context on import"
|
|
1435
|
+
});
|
|
1436
|
+
// JSON output
|
|
1437
|
+
json = Option12.Boolean("--json,-j", false, {
|
|
1438
|
+
description: "Output in JSON format"
|
|
1439
|
+
});
|
|
1440
|
+
// Verbose output
|
|
1441
|
+
verbose = Option12.Boolean("--verbose,-v", false, {
|
|
1442
|
+
description: "Show detailed output"
|
|
1443
|
+
});
|
|
1444
|
+
async execute() {
|
|
1445
|
+
const action = this.action || "show";
|
|
1446
|
+
switch (action) {
|
|
1447
|
+
case "init":
|
|
1448
|
+
return this.initContext();
|
|
1449
|
+
case "show":
|
|
1450
|
+
return this.showContext();
|
|
1451
|
+
case "export":
|
|
1452
|
+
return this.exportContext();
|
|
1453
|
+
case "import":
|
|
1454
|
+
return this.importContext();
|
|
1455
|
+
case "sync":
|
|
1456
|
+
return this.syncContext();
|
|
1457
|
+
case "detect":
|
|
1458
|
+
return this.detectProject();
|
|
1459
|
+
case "agents":
|
|
1460
|
+
return this.listAgents();
|
|
1461
|
+
default:
|
|
1462
|
+
console.error(chalk12.red(`Unknown action: ${action}`));
|
|
1463
|
+
console.log(chalk12.gray("Available actions: init, show, export, import, sync, detect, agents"));
|
|
1464
|
+
return 1;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Initialize project context
|
|
1469
|
+
*/
|
|
1470
|
+
async initContext() {
|
|
1471
|
+
const manager = new ContextManager(process.cwd());
|
|
1472
|
+
if (manager.exists() && !this.force) {
|
|
1473
|
+
console.log(chalk12.yellow("Context already exists. Use --force to reinitialize."));
|
|
1474
|
+
return this.showContext();
|
|
1475
|
+
}
|
|
1476
|
+
console.log(chalk12.cyan("Initializing project context...\n"));
|
|
1477
|
+
const context = manager.init({ force: this.force });
|
|
1478
|
+
console.log(chalk12.green("\u2713 Context initialized\n"));
|
|
1479
|
+
console.log(chalk12.gray(` Location: .skillkit/context.yaml
|
|
1480
|
+
`));
|
|
1481
|
+
this.printContextSummary(context);
|
|
1482
|
+
const sync = createContextSync(process.cwd());
|
|
1483
|
+
const detected = sync.detectAgents();
|
|
1484
|
+
if (detected.length > 0) {
|
|
1485
|
+
console.log(chalk12.cyan("\nDetected agents:"));
|
|
1486
|
+
for (const agent of detected) {
|
|
1487
|
+
console.log(` ${chalk12.green("\u2022")} ${agent}`);
|
|
1488
|
+
}
|
|
1489
|
+
manager.updateAgents({
|
|
1490
|
+
detected,
|
|
1491
|
+
synced: detected
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
console.log(chalk12.gray("\nRun `skillkit context sync` to sync skills to all agents."));
|
|
1495
|
+
return 0;
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* Show current context
|
|
1499
|
+
*/
|
|
1500
|
+
async showContext() {
|
|
1501
|
+
const manager = new ContextManager(process.cwd());
|
|
1502
|
+
const context = manager.load();
|
|
1503
|
+
if (!context) {
|
|
1504
|
+
console.log(chalk12.yellow("No context found. Run `skillkit context init` first."));
|
|
1505
|
+
return 1;
|
|
1506
|
+
}
|
|
1507
|
+
if (this.json) {
|
|
1508
|
+
console.log(JSON.stringify(context, null, 2));
|
|
1509
|
+
return 0;
|
|
1510
|
+
}
|
|
1511
|
+
this.printContextSummary(context);
|
|
1512
|
+
if (this.verbose) {
|
|
1513
|
+
console.log(chalk12.gray("\nFull context:"));
|
|
1514
|
+
console.log(chalk12.gray(JSON.stringify(context, null, 2)));
|
|
1515
|
+
}
|
|
1516
|
+
return 0;
|
|
1517
|
+
}
|
|
1518
|
+
/**
|
|
1519
|
+
* Export context to file
|
|
1520
|
+
*/
|
|
1521
|
+
async exportContext() {
|
|
1522
|
+
const manager = new ContextManager(process.cwd());
|
|
1523
|
+
const context = manager.get();
|
|
1524
|
+
if (!context) {
|
|
1525
|
+
console.error(chalk12.red("No context found. Run `skillkit context init` first."));
|
|
1526
|
+
return 1;
|
|
1527
|
+
}
|
|
1528
|
+
const format = this.json ? "json" : "yaml";
|
|
1529
|
+
const content = manager.export({
|
|
1530
|
+
format,
|
|
1531
|
+
includeSkills: true,
|
|
1532
|
+
includeAgents: true
|
|
1533
|
+
});
|
|
1534
|
+
if (this.output) {
|
|
1535
|
+
const outputPath = resolve(this.output);
|
|
1536
|
+
if (existsSync8(outputPath) && !this.force) {
|
|
1537
|
+
console.error(chalk12.red(`File exists: ${outputPath}. Use --force to overwrite.`));
|
|
1538
|
+
return 1;
|
|
1539
|
+
}
|
|
1540
|
+
writeFileSync4(outputPath, content, "utf-8");
|
|
1541
|
+
console.log(chalk12.green(`\u2713 Context exported to ${outputPath}`));
|
|
1542
|
+
} else {
|
|
1543
|
+
console.log(content);
|
|
1544
|
+
}
|
|
1545
|
+
return 0;
|
|
1546
|
+
}
|
|
1547
|
+
/**
|
|
1548
|
+
* Import context from file
|
|
1549
|
+
*/
|
|
1550
|
+
async importContext() {
|
|
1551
|
+
if (!this.input) {
|
|
1552
|
+
console.error(chalk12.red("Error: --input/-i file path is required"));
|
|
1553
|
+
return 1;
|
|
1554
|
+
}
|
|
1555
|
+
const inputPath = resolve(this.input);
|
|
1556
|
+
if (!existsSync8(inputPath)) {
|
|
1557
|
+
console.error(chalk12.red(`File not found: ${inputPath}`));
|
|
1558
|
+
return 1;
|
|
1559
|
+
}
|
|
1560
|
+
const manager = new ContextManager(process.cwd());
|
|
1561
|
+
const content = readFileSync3(inputPath, "utf-8");
|
|
1562
|
+
try {
|
|
1563
|
+
const context = manager.import(content, {
|
|
1564
|
+
merge: this.merge,
|
|
1565
|
+
overwrite: this.force
|
|
1566
|
+
});
|
|
1567
|
+
console.log(chalk12.green("\u2713 Context imported successfully"));
|
|
1568
|
+
this.printContextSummary(context);
|
|
1569
|
+
return 0;
|
|
1570
|
+
} catch (error) {
|
|
1571
|
+
console.error(chalk12.red(`Import failed: ${error}`));
|
|
1572
|
+
return 1;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1576
|
+
* Sync skills to all configured agents
|
|
1577
|
+
*/
|
|
1578
|
+
async syncContext() {
|
|
1579
|
+
const manager = new ContextManager(process.cwd());
|
|
1580
|
+
const context = manager.get();
|
|
1581
|
+
if (!context) {
|
|
1582
|
+
console.log(chalk12.yellow("No context found. Initializing..."));
|
|
1583
|
+
manager.init();
|
|
1584
|
+
}
|
|
1585
|
+
const sync = createContextSync(process.cwd());
|
|
1586
|
+
console.log(chalk12.cyan("Syncing skills across agents...\n"));
|
|
1587
|
+
const agents = this.agent ? [this.agent] : void 0;
|
|
1588
|
+
const report = await sync.syncAll({
|
|
1589
|
+
agents,
|
|
1590
|
+
force: this.force,
|
|
1591
|
+
dryRun: this.dryRun
|
|
1592
|
+
});
|
|
1593
|
+
for (const result of report.results) {
|
|
1594
|
+
const status = result.success ? chalk12.green("\u2713") : chalk12.red("\u2717");
|
|
1595
|
+
console.log(`${status} ${result.agent}: ${result.skillsSynced} synced, ${result.skillsSkipped} skipped`);
|
|
1596
|
+
if (this.verbose && result.files.length > 0) {
|
|
1597
|
+
for (const file of result.files) {
|
|
1598
|
+
console.log(chalk12.gray(` \u2192 ${file}`));
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
if (result.warnings.length > 0) {
|
|
1602
|
+
for (const warning of result.warnings) {
|
|
1603
|
+
console.log(chalk12.yellow(` \u26A0 ${warning}`));
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
if (result.errors.length > 0) {
|
|
1607
|
+
for (const error of result.errors) {
|
|
1608
|
+
console.log(chalk12.red(` \u2717 ${error}`));
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
console.log();
|
|
1613
|
+
console.log(chalk12.bold(`Summary: ${report.successfulAgents}/${report.totalAgents} agents, ${report.totalSkills} skills`));
|
|
1614
|
+
if (this.dryRun) {
|
|
1615
|
+
console.log(chalk12.gray("\n(Dry run - no files were written)"));
|
|
1616
|
+
}
|
|
1617
|
+
return report.successfulAgents === report.totalAgents ? 0 : 1;
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* Detect project stack
|
|
1621
|
+
*/
|
|
1622
|
+
async detectProject() {
|
|
1623
|
+
console.log(chalk12.cyan("Analyzing project...\n"));
|
|
1624
|
+
const stack = analyzeProject(process.cwd());
|
|
1625
|
+
const tags = getStackTags(stack);
|
|
1626
|
+
if (stack.languages.length > 0) {
|
|
1627
|
+
console.log(chalk12.bold("Languages:"));
|
|
1628
|
+
for (const lang of stack.languages) {
|
|
1629
|
+
const version = lang.version ? ` (${lang.version})` : "";
|
|
1630
|
+
console.log(` ${chalk12.green("\u2022")} ${lang.name}${version}`);
|
|
1631
|
+
}
|
|
1632
|
+
console.log();
|
|
1633
|
+
}
|
|
1634
|
+
if (stack.frameworks.length > 0) {
|
|
1635
|
+
console.log(chalk12.bold("Frameworks:"));
|
|
1636
|
+
for (const fw of stack.frameworks) {
|
|
1637
|
+
const version = fw.version ? ` (${fw.version})` : "";
|
|
1638
|
+
console.log(` ${chalk12.green("\u2022")} ${fw.name}${version}`);
|
|
1639
|
+
}
|
|
1640
|
+
console.log();
|
|
1641
|
+
}
|
|
1642
|
+
if (stack.libraries.length > 0) {
|
|
1643
|
+
console.log(chalk12.bold("Libraries:"));
|
|
1644
|
+
for (const lib of stack.libraries) {
|
|
1645
|
+
const version = lib.version ? ` (${lib.version})` : "";
|
|
1646
|
+
console.log(` ${chalk12.green("\u2022")} ${lib.name}${version}`);
|
|
1647
|
+
}
|
|
1648
|
+
console.log();
|
|
1649
|
+
}
|
|
1650
|
+
if (stack.styling.length > 0) {
|
|
1651
|
+
console.log(chalk12.bold("Styling:"));
|
|
1652
|
+
for (const style of stack.styling) {
|
|
1653
|
+
console.log(` ${chalk12.green("\u2022")} ${style.name}`);
|
|
1654
|
+
}
|
|
1655
|
+
console.log();
|
|
1656
|
+
}
|
|
1657
|
+
if (stack.testing.length > 0) {
|
|
1658
|
+
console.log(chalk12.bold("Testing:"));
|
|
1659
|
+
for (const test of stack.testing) {
|
|
1660
|
+
console.log(` ${chalk12.green("\u2022")} ${test.name}`);
|
|
1661
|
+
}
|
|
1662
|
+
console.log();
|
|
1663
|
+
}
|
|
1664
|
+
if (stack.databases.length > 0) {
|
|
1665
|
+
console.log(chalk12.bold("Databases:"));
|
|
1666
|
+
for (const db of stack.databases) {
|
|
1667
|
+
console.log(` ${chalk12.green("\u2022")} ${db.name}`);
|
|
1668
|
+
}
|
|
1669
|
+
console.log();
|
|
1670
|
+
}
|
|
1671
|
+
if (stack.tools.length > 0) {
|
|
1672
|
+
console.log(chalk12.bold("Tools:"));
|
|
1673
|
+
for (const tool of stack.tools) {
|
|
1674
|
+
console.log(` ${chalk12.green("\u2022")} ${tool.name}`);
|
|
1675
|
+
}
|
|
1676
|
+
console.log();
|
|
1677
|
+
}
|
|
1678
|
+
if (tags.length > 0) {
|
|
1679
|
+
console.log(chalk12.bold("Recommended skill tags:"));
|
|
1680
|
+
console.log(` ${chalk12.cyan(tags.join(", "))}`);
|
|
1681
|
+
console.log();
|
|
1682
|
+
}
|
|
1683
|
+
if (this.json) {
|
|
1684
|
+
console.log(chalk12.gray("\nJSON:"));
|
|
1685
|
+
console.log(JSON.stringify(stack, null, 2));
|
|
1686
|
+
}
|
|
1687
|
+
return 0;
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* List detected agents
|
|
1691
|
+
*/
|
|
1692
|
+
async listAgents() {
|
|
1693
|
+
const sync = createContextSync(process.cwd());
|
|
1694
|
+
const detected = sync.detectAgents();
|
|
1695
|
+
const status = sync.checkStatus();
|
|
1696
|
+
const adapters = getAllAdapters3();
|
|
1697
|
+
console.log(chalk12.bold("\nAgent Status:\n"));
|
|
1698
|
+
for (const [agent, info] of Object.entries(status)) {
|
|
1699
|
+
const adapter = adapters.find((a) => a.type === agent);
|
|
1700
|
+
const name = adapter?.name || agent;
|
|
1701
|
+
const isDetected = detected.includes(agent);
|
|
1702
|
+
const statusIcon = info.hasSkills ? chalk12.green("\u25CF") : isDetected ? chalk12.yellow("\u25CB") : chalk12.gray("\u25CB");
|
|
1703
|
+
const skillInfo = info.skillCount > 0 ? chalk12.gray(` (${info.skillCount} skills)`) : "";
|
|
1704
|
+
console.log(` ${statusIcon} ${name.padEnd(20)} ${chalk12.gray(agent)}${skillInfo}`);
|
|
1705
|
+
if (this.verbose && info.skills.length > 0) {
|
|
1706
|
+
for (const skill of info.skills) {
|
|
1707
|
+
console.log(chalk12.gray(` \u2514\u2500 ${skill}`));
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
console.log();
|
|
1712
|
+
console.log(chalk12.gray("Legend: \u25CF has skills, \u25CB detected/configured, \u25CB not detected"));
|
|
1713
|
+
console.log();
|
|
1714
|
+
return 0;
|
|
1715
|
+
}
|
|
1716
|
+
/**
|
|
1717
|
+
* Print context summary
|
|
1718
|
+
*/
|
|
1719
|
+
printContextSummary(context) {
|
|
1720
|
+
console.log(chalk12.bold("Project:"));
|
|
1721
|
+
console.log(` Name: ${chalk12.cyan(context.project.name)}`);
|
|
1722
|
+
if (context.project.type) {
|
|
1723
|
+
console.log(` Type: ${context.project.type}`);
|
|
1724
|
+
}
|
|
1725
|
+
if (context.project.description) {
|
|
1726
|
+
console.log(` Description: ${context.project.description}`);
|
|
1727
|
+
}
|
|
1728
|
+
const stackItems = [];
|
|
1729
|
+
if (context.stack.languages.length > 0) {
|
|
1730
|
+
stackItems.push(`${context.stack.languages.length} languages`);
|
|
1731
|
+
}
|
|
1732
|
+
if (context.stack.frameworks.length > 0) {
|
|
1733
|
+
stackItems.push(`${context.stack.frameworks.length} frameworks`);
|
|
1734
|
+
}
|
|
1735
|
+
if (context.stack.libraries.length > 0) {
|
|
1736
|
+
stackItems.push(`${context.stack.libraries.length} libraries`);
|
|
1737
|
+
}
|
|
1738
|
+
if (context.stack.databases.length > 0) {
|
|
1739
|
+
stackItems.push(`${context.stack.databases.length} databases`);
|
|
1740
|
+
}
|
|
1741
|
+
if (stackItems.length > 0) {
|
|
1742
|
+
console.log(`
|
|
1743
|
+
${chalk12.bold("Stack:")} ${stackItems.join(", ")}`);
|
|
1744
|
+
const topFrameworks = context.stack.frameworks.slice(0, 3).map((f) => f.name);
|
|
1745
|
+
if (topFrameworks.length > 0) {
|
|
1746
|
+
console.log(` Frameworks: ${chalk12.cyan(topFrameworks.join(", "))}`);
|
|
1747
|
+
}
|
|
1748
|
+
const topLibs = context.stack.libraries.slice(0, 3).map((l) => l.name);
|
|
1749
|
+
if (topLibs.length > 0) {
|
|
1750
|
+
console.log(` Libraries: ${chalk12.cyan(topLibs.join(", "))}`);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
if (context.skills) {
|
|
1754
|
+
console.log(`
|
|
1755
|
+
${chalk12.bold("Skills:")}`);
|
|
1756
|
+
console.log(` Installed: ${context.skills.installed?.length || 0}`);
|
|
1757
|
+
console.log(` Auto-sync: ${context.skills.autoSync ? "enabled" : "disabled"}`);
|
|
1758
|
+
}
|
|
1759
|
+
if (context.agents) {
|
|
1760
|
+
console.log(`
|
|
1761
|
+
${chalk12.bold("Agents:")}`);
|
|
1762
|
+
if (context.agents.primary) {
|
|
1763
|
+
console.log(` Primary: ${chalk12.cyan(context.agents.primary)}`);
|
|
1764
|
+
}
|
|
1765
|
+
if (context.agents.synced?.length) {
|
|
1766
|
+
console.log(` Synced: ${context.agents.synced.join(", ")}`);
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
};
|
|
1771
|
+
|
|
1772
|
+
// src/commands/recommend.ts
|
|
1773
|
+
import { Command as Command14, Option as Option13 } from "clipanion";
|
|
1774
|
+
import { existsSync as existsSync9, readFileSync as readFileSync4, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5 } from "fs";
|
|
1775
|
+
import { resolve as resolve2, join as join6 } from "path";
|
|
1776
|
+
import chalk13 from "chalk";
|
|
1777
|
+
import {
|
|
1778
|
+
ContextManager as ContextManager2,
|
|
1779
|
+
RecommendationEngine
|
|
1780
|
+
} from "@skillkit/core";
|
|
1781
|
+
var INDEX_PATH = join6(process.env.HOME || "~", ".skillkit", "index.json");
|
|
1782
|
+
var INDEX_CACHE_HOURS = 24;
|
|
1783
|
+
var RecommendCommand = class extends Command14 {
|
|
1784
|
+
static paths = [["recommend"], ["rec"]];
|
|
1785
|
+
static usage = Command14.Usage({
|
|
1786
|
+
description: "Get skill recommendations based on your project",
|
|
1787
|
+
details: `
|
|
1788
|
+
The recommend command analyzes your project and suggests skills that match
|
|
1789
|
+
your technology stack, frameworks, and patterns.
|
|
1790
|
+
|
|
1791
|
+
It scores skills based on:
|
|
1792
|
+
- Framework compatibility (React, Vue, Next.js, etc.)
|
|
1793
|
+
- Language match (TypeScript, Python, etc.)
|
|
1794
|
+
- Library alignment (Tailwind, Prisma, etc.)
|
|
1795
|
+
- Tag relevance
|
|
1796
|
+
- Popularity and quality metrics
|
|
1797
|
+
|
|
1798
|
+
Run "skillkit recommend --update" to refresh the skill index from known sources.
|
|
1799
|
+
`,
|
|
1800
|
+
examples: [
|
|
1801
|
+
["Get recommendations", "$0 recommend"],
|
|
1802
|
+
["Show top 5 recommendations", "$0 recommend --limit 5"],
|
|
1803
|
+
["Filter by category", "$0 recommend --category security"],
|
|
1804
|
+
["Show detailed reasons", "$0 recommend --verbose"],
|
|
1805
|
+
["Update skill index", "$0 recommend --update"],
|
|
1806
|
+
["Search for skills by task", '$0 recommend --search "authentication"']
|
|
1807
|
+
]
|
|
1808
|
+
});
|
|
1809
|
+
// Limit number of results
|
|
1810
|
+
limit = Option13.String("--limit,-l", {
|
|
1811
|
+
description: "Maximum number of recommendations"
|
|
1812
|
+
});
|
|
1813
|
+
// Minimum score threshold
|
|
1814
|
+
minScore = Option13.String("--min-score", {
|
|
1815
|
+
description: "Minimum match score (0-100)"
|
|
1816
|
+
});
|
|
1817
|
+
// Filter by category/tag
|
|
1818
|
+
category = Option13.Array("--category,-c", {
|
|
1819
|
+
description: "Filter by category (can be used multiple times)"
|
|
1820
|
+
});
|
|
1821
|
+
// Verbose output with reasons
|
|
1822
|
+
verbose = Option13.Boolean("--verbose,-v", false, {
|
|
1823
|
+
description: "Show detailed match reasons"
|
|
1824
|
+
});
|
|
1825
|
+
// Update index
|
|
1826
|
+
update = Option13.Boolean("--update,-u", false, {
|
|
1827
|
+
description: "Update skill index from sources"
|
|
1828
|
+
});
|
|
1829
|
+
// Search mode
|
|
1830
|
+
search = Option13.String("--search,-s", {
|
|
1831
|
+
description: "Search skills by task/query"
|
|
1832
|
+
});
|
|
1833
|
+
// Include installed skills
|
|
1834
|
+
includeInstalled = Option13.Boolean("--include-installed", false, {
|
|
1835
|
+
description: "Include already installed skills"
|
|
1836
|
+
});
|
|
1837
|
+
// JSON output
|
|
1838
|
+
json = Option13.Boolean("--json,-j", false, {
|
|
1839
|
+
description: "Output in JSON format"
|
|
1840
|
+
});
|
|
1841
|
+
// Project path
|
|
1842
|
+
projectPath = Option13.String("--path,-p", {
|
|
1843
|
+
description: "Project path (default: current directory)"
|
|
1844
|
+
});
|
|
1845
|
+
async execute() {
|
|
1846
|
+
const targetPath = resolve2(this.projectPath || process.cwd());
|
|
1847
|
+
if (this.update) {
|
|
1848
|
+
return this.updateIndex();
|
|
1849
|
+
}
|
|
1850
|
+
const profile = await this.getProjectProfile(targetPath);
|
|
1851
|
+
if (!profile) {
|
|
1852
|
+
console.error(chalk13.red("Failed to analyze project"));
|
|
1853
|
+
return 1;
|
|
1854
|
+
}
|
|
1855
|
+
const index = this.loadIndex();
|
|
1856
|
+
if (!index || index.skills.length === 0) {
|
|
1857
|
+
console.log(chalk13.yellow("No skill index found."));
|
|
1858
|
+
console.log(chalk13.dim('Run "skillkit recommend --update" to fetch skills from known sources.'));
|
|
1859
|
+
console.log(chalk13.dim('Or install skills manually with "skillkit install <source>"\n'));
|
|
1860
|
+
this.showProjectProfile(profile);
|
|
1861
|
+
return 0;
|
|
1862
|
+
}
|
|
1863
|
+
const engine = new RecommendationEngine();
|
|
1864
|
+
engine.loadIndex(index);
|
|
1865
|
+
if (this.search) {
|
|
1866
|
+
return this.handleSearch(engine, this.search);
|
|
1867
|
+
}
|
|
1868
|
+
const result = engine.recommend(profile, {
|
|
1869
|
+
limit: this.limit ? parseInt(this.limit, 10) : 10,
|
|
1870
|
+
minScore: this.minScore ? parseInt(this.minScore, 10) : 30,
|
|
1871
|
+
categories: this.category,
|
|
1872
|
+
excludeInstalled: !this.includeInstalled,
|
|
1873
|
+
includeReasons: this.verbose
|
|
1874
|
+
});
|
|
1875
|
+
if (this.json) {
|
|
1876
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1877
|
+
return 0;
|
|
1878
|
+
}
|
|
1879
|
+
this.displayRecommendations(result.recommendations, profile, result.totalSkillsScanned);
|
|
1880
|
+
return 0;
|
|
1881
|
+
}
|
|
1882
|
+
/**
|
|
1883
|
+
* Get project profile from context or by analyzing project
|
|
1884
|
+
*/
|
|
1885
|
+
async getProjectProfile(projectPath) {
|
|
1886
|
+
const manager = new ContextManager2(projectPath);
|
|
1887
|
+
let context = manager.get();
|
|
1888
|
+
if (!context) {
|
|
1889
|
+
if (!this.json) {
|
|
1890
|
+
console.log(chalk13.dim("Analyzing project...\n"));
|
|
1891
|
+
}
|
|
1892
|
+
context = manager.init();
|
|
1893
|
+
}
|
|
1894
|
+
if (!context) {
|
|
1895
|
+
return null;
|
|
1896
|
+
}
|
|
1897
|
+
return {
|
|
1898
|
+
name: context.project.name,
|
|
1899
|
+
type: context.project.type,
|
|
1900
|
+
stack: context.stack,
|
|
1901
|
+
patterns: context.patterns,
|
|
1902
|
+
installedSkills: context.skills?.installed || [],
|
|
1903
|
+
excludedSkills: context.skills?.excluded || []
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
/**
|
|
1907
|
+
* Show project profile summary
|
|
1908
|
+
*/
|
|
1909
|
+
showProjectProfile(profile) {
|
|
1910
|
+
console.log(chalk13.cyan("Project Profile:"));
|
|
1911
|
+
console.log(` Name: ${chalk13.bold(profile.name)}`);
|
|
1912
|
+
if (profile.type) {
|
|
1913
|
+
console.log(` Type: ${profile.type}`);
|
|
1914
|
+
}
|
|
1915
|
+
const stackItems = [];
|
|
1916
|
+
for (const lang of profile.stack.languages) {
|
|
1917
|
+
stackItems.push(`${lang.name}${lang.version ? ` ${lang.version}` : ""}`);
|
|
1918
|
+
}
|
|
1919
|
+
for (const fw of profile.stack.frameworks) {
|
|
1920
|
+
stackItems.push(`${fw.name}${fw.version ? ` ${fw.version}` : ""}`);
|
|
1921
|
+
}
|
|
1922
|
+
if (stackItems.length > 0) {
|
|
1923
|
+
console.log(` Stack: ${chalk13.dim(stackItems.join(", "))}`);
|
|
1924
|
+
}
|
|
1925
|
+
console.log();
|
|
1926
|
+
}
|
|
1927
|
+
/**
|
|
1928
|
+
* Display recommendations
|
|
1929
|
+
*/
|
|
1930
|
+
displayRecommendations(recommendations, profile, totalScanned) {
|
|
1931
|
+
this.showProjectProfile(profile);
|
|
1932
|
+
if (recommendations.length === 0) {
|
|
1933
|
+
console.log(chalk13.yellow("No matching skills found."));
|
|
1934
|
+
console.log(chalk13.dim("Try lowering the minimum score with --min-score"));
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
console.log(chalk13.cyan(`Recommended Skills (${recommendations.length} of ${totalScanned} scanned):
|
|
1938
|
+
`));
|
|
1939
|
+
for (const rec of recommendations) {
|
|
1940
|
+
const scoreColor = rec.score >= 70 ? chalk13.green : rec.score >= 50 ? chalk13.yellow : chalk13.dim;
|
|
1941
|
+
const scoreBar = this.getScoreBar(rec.score);
|
|
1942
|
+
console.log(` ${scoreColor(`${rec.score}%`)} ${scoreBar} ${chalk13.bold(rec.skill.name)}`);
|
|
1943
|
+
if (rec.skill.description) {
|
|
1944
|
+
console.log(` ${chalk13.dim(truncate2(rec.skill.description, 70))}`);
|
|
1945
|
+
}
|
|
1946
|
+
if (rec.skill.source) {
|
|
1947
|
+
console.log(` ${chalk13.dim("Source:")} ${rec.skill.source}`);
|
|
1948
|
+
}
|
|
1949
|
+
if (this.verbose && rec.reasons.length > 0) {
|
|
1950
|
+
console.log(chalk13.dim(" Reasons:"));
|
|
1951
|
+
for (const reason of rec.reasons.filter((r) => r.weight > 0)) {
|
|
1952
|
+
console.log(` ${chalk13.dim("\u2022")} ${reason.description} (+${reason.weight})`);
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
if (rec.warnings.length > 0) {
|
|
1956
|
+
for (const warning of rec.warnings) {
|
|
1957
|
+
console.log(` ${chalk13.yellow("\u26A0")} ${warning}`);
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
console.log();
|
|
1961
|
+
}
|
|
1962
|
+
console.log(chalk13.dim("Install with: skillkit install <source>"));
|
|
1963
|
+
}
|
|
1964
|
+
/**
|
|
1965
|
+
* Generate a visual score bar
|
|
1966
|
+
*/
|
|
1967
|
+
getScoreBar(score) {
|
|
1968
|
+
const filled = Math.round(score / 10);
|
|
1969
|
+
const empty = 10 - filled;
|
|
1970
|
+
return chalk13.green("\u2588".repeat(filled)) + chalk13.dim("\u2591".repeat(empty));
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* Handle search mode
|
|
1974
|
+
*/
|
|
1975
|
+
handleSearch(engine, query) {
|
|
1976
|
+
const results = engine.search({
|
|
1977
|
+
query,
|
|
1978
|
+
limit: this.limit ? parseInt(this.limit, 10) : 10,
|
|
1979
|
+
semantic: true,
|
|
1980
|
+
filters: {
|
|
1981
|
+
minScore: this.minScore ? parseInt(this.minScore, 10) : void 0
|
|
1982
|
+
}
|
|
1983
|
+
});
|
|
1984
|
+
if (this.json) {
|
|
1985
|
+
console.log(JSON.stringify(results, null, 2));
|
|
1986
|
+
return 0;
|
|
1987
|
+
}
|
|
1988
|
+
if (results.length === 0) {
|
|
1989
|
+
console.log(chalk13.yellow(`No skills found matching "${query}"`));
|
|
1990
|
+
return 0;
|
|
1991
|
+
}
|
|
1992
|
+
console.log(chalk13.cyan(`Search results for "${query}" (${results.length} found):
|
|
1993
|
+
`));
|
|
1994
|
+
for (const result of results) {
|
|
1995
|
+
const relevanceColor = result.relevance >= 70 ? chalk13.green : result.relevance >= 50 ? chalk13.yellow : chalk13.dim;
|
|
1996
|
+
console.log(` ${relevanceColor(`${result.relevance}%`)} ${chalk13.bold(result.skill.name)}`);
|
|
1997
|
+
if (result.snippet) {
|
|
1998
|
+
console.log(` ${chalk13.dim(result.snippet)}`);
|
|
1999
|
+
}
|
|
2000
|
+
if (result.matchedTerms.length > 0) {
|
|
2001
|
+
console.log(` ${chalk13.dim("Matched:")} ${result.matchedTerms.join(", ")}`);
|
|
2002
|
+
}
|
|
2003
|
+
console.log();
|
|
2004
|
+
}
|
|
2005
|
+
return 0;
|
|
2006
|
+
}
|
|
2007
|
+
/**
|
|
2008
|
+
* Load skill index from cache
|
|
2009
|
+
*/
|
|
2010
|
+
loadIndex() {
|
|
2011
|
+
if (!existsSync9(INDEX_PATH)) {
|
|
2012
|
+
return null;
|
|
2013
|
+
}
|
|
2014
|
+
try {
|
|
2015
|
+
const content = readFileSync4(INDEX_PATH, "utf-8");
|
|
2016
|
+
const index = JSON.parse(content);
|
|
2017
|
+
const lastUpdated = new Date(index.lastUpdated);
|
|
2018
|
+
const hoursSinceUpdate = (Date.now() - lastUpdated.getTime()) / (1e3 * 60 * 60);
|
|
2019
|
+
if (hoursSinceUpdate > INDEX_CACHE_HOURS && !this.json) {
|
|
2020
|
+
console.log(
|
|
2021
|
+
chalk13.dim(`Index is ${Math.round(hoursSinceUpdate)} hours old. Run --update to refresh.
|
|
2022
|
+
`)
|
|
2023
|
+
);
|
|
2024
|
+
}
|
|
2025
|
+
return index;
|
|
2026
|
+
} catch {
|
|
2027
|
+
return null;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Update skill index from sources
|
|
2032
|
+
*/
|
|
2033
|
+
updateIndex() {
|
|
2034
|
+
console.log(chalk13.cyan("Updating skill index...\n"));
|
|
2035
|
+
const sampleIndex = {
|
|
2036
|
+
version: 1,
|
|
2037
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2038
|
+
skills: getSampleSkills(),
|
|
2039
|
+
sources: [
|
|
2040
|
+
{
|
|
2041
|
+
name: "vercel-labs",
|
|
2042
|
+
url: "https://github.com/vercel-labs/agent-skills",
|
|
2043
|
+
lastFetched: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2044
|
+
skillCount: 5
|
|
2045
|
+
},
|
|
2046
|
+
{
|
|
2047
|
+
name: "anthropics",
|
|
2048
|
+
url: "https://github.com/anthropics/skills",
|
|
2049
|
+
lastFetched: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2050
|
+
skillCount: 3
|
|
2051
|
+
}
|
|
2052
|
+
]
|
|
2053
|
+
};
|
|
2054
|
+
const indexDir = join6(process.env.HOME || "~", ".skillkit");
|
|
2055
|
+
if (!existsSync9(indexDir)) {
|
|
2056
|
+
mkdirSync5(indexDir, { recursive: true });
|
|
2057
|
+
}
|
|
2058
|
+
writeFileSync5(INDEX_PATH, JSON.stringify(sampleIndex, null, 2));
|
|
2059
|
+
console.log(chalk13.green(`\u2713 Updated index with ${sampleIndex.skills.length} skills`));
|
|
2060
|
+
console.log(chalk13.dim(` Sources: ${sampleIndex.sources.map((s) => s.name).join(", ")}`));
|
|
2061
|
+
console.log(chalk13.dim(` Saved to: ${INDEX_PATH}
|
|
2062
|
+
`));
|
|
2063
|
+
return 0;
|
|
2064
|
+
}
|
|
2065
|
+
};
|
|
2066
|
+
function truncate2(str, maxLen) {
|
|
2067
|
+
if (str.length <= maxLen) return str;
|
|
2068
|
+
return str.slice(0, maxLen - 3) + "...";
|
|
2069
|
+
}
|
|
2070
|
+
function getSampleSkills() {
|
|
2071
|
+
return [
|
|
2072
|
+
{
|
|
2073
|
+
name: "vercel-react-best-practices",
|
|
2074
|
+
description: "Modern React patterns including Server Components, hooks best practices, and performance optimization",
|
|
2075
|
+
source: "vercel-labs/agent-skills",
|
|
2076
|
+
tags: ["react", "frontend", "typescript", "nextjs", "performance"],
|
|
2077
|
+
compatibility: {
|
|
2078
|
+
frameworks: ["react", "nextjs"],
|
|
2079
|
+
languages: ["typescript", "javascript"],
|
|
2080
|
+
libraries: []
|
|
2081
|
+
},
|
|
2082
|
+
popularity: 1500,
|
|
2083
|
+
quality: 95,
|
|
2084
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2085
|
+
verified: true
|
|
2086
|
+
},
|
|
2087
|
+
{
|
|
2088
|
+
name: "tailwind-v4-patterns",
|
|
2089
|
+
description: "Tailwind CSS v4 utility patterns, responsive design, and component styling best practices",
|
|
2090
|
+
source: "vercel-labs/agent-skills",
|
|
2091
|
+
tags: ["tailwind", "css", "styling", "frontend", "responsive"],
|
|
2092
|
+
compatibility: {
|
|
2093
|
+
frameworks: [],
|
|
2094
|
+
languages: ["typescript", "javascript"],
|
|
2095
|
+
libraries: ["tailwindcss"]
|
|
2096
|
+
},
|
|
2097
|
+
popularity: 1200,
|
|
2098
|
+
quality: 92,
|
|
2099
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2100
|
+
verified: true
|
|
2101
|
+
},
|
|
2102
|
+
{
|
|
2103
|
+
name: "nextjs-app-router",
|
|
2104
|
+
description: "Next.js App Router patterns including layouts, server actions, and data fetching",
|
|
2105
|
+
source: "vercel-labs/agent-skills",
|
|
2106
|
+
tags: ["nextjs", "react", "routing", "server-actions", "frontend"],
|
|
2107
|
+
compatibility: {
|
|
2108
|
+
frameworks: ["nextjs"],
|
|
2109
|
+
languages: ["typescript", "javascript"],
|
|
2110
|
+
libraries: []
|
|
2111
|
+
},
|
|
2112
|
+
popularity: 1100,
|
|
2113
|
+
quality: 94,
|
|
2114
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2115
|
+
verified: true
|
|
2116
|
+
},
|
|
2117
|
+
{
|
|
2118
|
+
name: "typescript-strict-patterns",
|
|
2119
|
+
description: "TypeScript strict mode patterns, type safety, and advanced type utilities",
|
|
2120
|
+
source: "anthropics/skills",
|
|
2121
|
+
tags: ["typescript", "types", "safety", "patterns"],
|
|
2122
|
+
compatibility: {
|
|
2123
|
+
frameworks: [],
|
|
2124
|
+
languages: ["typescript"],
|
|
2125
|
+
libraries: []
|
|
2126
|
+
},
|
|
2127
|
+
popularity: 900,
|
|
2128
|
+
quality: 90,
|
|
2129
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2130
|
+
verified: true
|
|
2131
|
+
},
|
|
2132
|
+
{
|
|
2133
|
+
name: "supabase-best-practices",
|
|
2134
|
+
description: "Supabase integration patterns including auth, database queries, and real-time subscriptions",
|
|
2135
|
+
source: "anthropics/skills",
|
|
2136
|
+
tags: ["supabase", "database", "auth", "backend", "postgresql"],
|
|
2137
|
+
compatibility: {
|
|
2138
|
+
frameworks: [],
|
|
2139
|
+
languages: ["typescript", "javascript"],
|
|
2140
|
+
libraries: ["@supabase/supabase-js"]
|
|
2141
|
+
},
|
|
2142
|
+
popularity: 800,
|
|
2143
|
+
quality: 88,
|
|
2144
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2145
|
+
verified: true
|
|
2146
|
+
},
|
|
2147
|
+
{
|
|
2148
|
+
name: "vitest-testing-patterns",
|
|
2149
|
+
description: "Testing patterns with Vitest including mocking, assertions, and test organization",
|
|
2150
|
+
source: "anthropics/skills",
|
|
2151
|
+
tags: ["vitest", "testing", "typescript", "mocking", "tdd"],
|
|
2152
|
+
compatibility: {
|
|
2153
|
+
frameworks: [],
|
|
2154
|
+
languages: ["typescript", "javascript"],
|
|
2155
|
+
libraries: ["vitest"]
|
|
2156
|
+
},
|
|
2157
|
+
popularity: 700,
|
|
2158
|
+
quality: 86,
|
|
2159
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2160
|
+
verified: false
|
|
2161
|
+
},
|
|
2162
|
+
{
|
|
2163
|
+
name: "prisma-database-patterns",
|
|
2164
|
+
description: "Prisma ORM patterns for schema design, migrations, and efficient queries",
|
|
2165
|
+
source: "vercel-labs/agent-skills",
|
|
2166
|
+
tags: ["prisma", "database", "orm", "postgresql", "backend"],
|
|
2167
|
+
compatibility: {
|
|
2168
|
+
frameworks: [],
|
|
2169
|
+
languages: ["typescript"],
|
|
2170
|
+
libraries: ["@prisma/client"]
|
|
2171
|
+
},
|
|
2172
|
+
popularity: 850,
|
|
2173
|
+
quality: 89,
|
|
2174
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2175
|
+
verified: true
|
|
2176
|
+
},
|
|
2177
|
+
{
|
|
2178
|
+
name: "security-best-practices",
|
|
2179
|
+
description: "Security patterns for web applications including XSS prevention, CSRF, and secure headers",
|
|
2180
|
+
source: "trailofbits/skills",
|
|
2181
|
+
tags: ["security", "xss", "csrf", "headers", "owasp"],
|
|
2182
|
+
compatibility: {
|
|
2183
|
+
frameworks: [],
|
|
2184
|
+
languages: ["typescript", "javascript", "python"],
|
|
2185
|
+
libraries: []
|
|
2186
|
+
},
|
|
2187
|
+
popularity: 600,
|
|
2188
|
+
quality: 95,
|
|
2189
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2190
|
+
verified: true
|
|
2191
|
+
},
|
|
2192
|
+
{
|
|
2193
|
+
name: "python-fastapi-patterns",
|
|
2194
|
+
description: "FastAPI best practices for building high-performance Python APIs",
|
|
2195
|
+
source: "python-skills/fastapi",
|
|
2196
|
+
tags: ["python", "fastapi", "backend", "api", "async"],
|
|
2197
|
+
compatibility: {
|
|
2198
|
+
frameworks: ["fastapi"],
|
|
2199
|
+
languages: ["python"],
|
|
2200
|
+
libraries: []
|
|
2201
|
+
},
|
|
2202
|
+
popularity: 550,
|
|
2203
|
+
quality: 85,
|
|
2204
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2205
|
+
verified: false
|
|
2206
|
+
},
|
|
2207
|
+
{
|
|
2208
|
+
name: "zustand-state-management",
|
|
2209
|
+
description: "Zustand state management patterns for React applications",
|
|
2210
|
+
source: "react-skills/state",
|
|
2211
|
+
tags: ["zustand", "react", "state-management", "frontend"],
|
|
2212
|
+
compatibility: {
|
|
2213
|
+
frameworks: ["react"],
|
|
2214
|
+
languages: ["typescript", "javascript"],
|
|
2215
|
+
libraries: ["zustand"]
|
|
2216
|
+
},
|
|
2217
|
+
popularity: 650,
|
|
2218
|
+
quality: 84,
|
|
2219
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2220
|
+
verified: false
|
|
2221
|
+
}
|
|
2222
|
+
];
|
|
2223
|
+
}
|
|
2224
|
+
export {
|
|
2225
|
+
ContextCommand,
|
|
2226
|
+
CreateCommand,
|
|
2227
|
+
DisableCommand,
|
|
2228
|
+
EnableCommand,
|
|
2229
|
+
InitCommand,
|
|
2230
|
+
InstallCommand,
|
|
2231
|
+
ListCommand,
|
|
2232
|
+
ReadCommand,
|
|
2233
|
+
RecommendCommand,
|
|
2234
|
+
RemoveCommand,
|
|
2235
|
+
SyncCommand,
|
|
2236
|
+
TranslateCommand,
|
|
2237
|
+
UICommand,
|
|
2238
|
+
UpdateCommand,
|
|
2239
|
+
ValidateCommand,
|
|
2240
|
+
getAgentConfigPath,
|
|
2241
|
+
getInstallDir,
|
|
2242
|
+
getSearchDirs,
|
|
2243
|
+
initProject,
|
|
2244
|
+
loadSkillMetadata,
|
|
2245
|
+
saveSkillMetadata
|
|
2246
|
+
};
|
|
2247
|
+
//# sourceMappingURL=index.js.map
|