@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/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