@skillkit/cli 1.5.0 → 1.6.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/README.md +58 -0
- package/dist/index.d.ts +137 -1
- package/dist/index.js +1979 -12
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -3802,10 +3802,10 @@ ${learning.title}
|
|
|
3802
3802
|
return 0;
|
|
3803
3803
|
}
|
|
3804
3804
|
const outputPath = this.output || `.skillkit/exports/${skillName}/SKILL.md`;
|
|
3805
|
-
const { dirname:
|
|
3806
|
-
const { existsSync:
|
|
3807
|
-
const outputDir =
|
|
3808
|
-
if (!
|
|
3805
|
+
const { dirname: dirname5 } = await import("path");
|
|
3806
|
+
const { existsSync: existsSync14, mkdirSync: mkdirSync7, writeFileSync: writeFileSync6 } = await import("fs");
|
|
3807
|
+
const outputDir = dirname5(outputPath);
|
|
3808
|
+
if (!existsSync14(outputDir)) {
|
|
3809
3809
|
mkdirSync7(outputDir, { recursive: true });
|
|
3810
3810
|
}
|
|
3811
3811
|
writeFileSync6(outputPath, skillContent, "utf-8");
|
|
@@ -3823,10 +3823,10 @@ ${learning.title}
|
|
|
3823
3823
|
console.log(chalk23.gray("Usage: skillkit memory import --input <path>"));
|
|
3824
3824
|
return 1;
|
|
3825
3825
|
}
|
|
3826
|
-
const { existsSync:
|
|
3827
|
-
const { resolve:
|
|
3828
|
-
const fullPath =
|
|
3829
|
-
if (!
|
|
3826
|
+
const { existsSync: existsSync14, readFileSync: readFileSync7 } = await import("fs");
|
|
3827
|
+
const { resolve: resolve16 } = await import("path");
|
|
3828
|
+
const fullPath = resolve16(inputPath);
|
|
3829
|
+
if (!existsSync14(fullPath)) {
|
|
3830
3830
|
console.error(chalk23.red(`File not found: ${fullPath}`));
|
|
3831
3831
|
return 1;
|
|
3832
3832
|
}
|
|
@@ -4905,8 +4905,8 @@ var TeamCommand = class extends Command27 {
|
|
|
4905
4905
|
}
|
|
4906
4906
|
const projectPath = process.cwd();
|
|
4907
4907
|
const bundlePath = join9(projectPath, ".skillkit", "bundles", `${this.name}.json`);
|
|
4908
|
-
const { existsSync:
|
|
4909
|
-
if (!
|
|
4908
|
+
const { existsSync: existsSync14, readFileSync: readFileSync7, writeFileSync: writeFileSync6 } = await import("fs");
|
|
4909
|
+
if (!existsSync14(bundlePath)) {
|
|
4910
4910
|
this.context.stderr.write(chalk26.red(`Bundle "${this.name}" not found. Create it first with bundle-create.
|
|
4911
4911
|
`));
|
|
4912
4912
|
return 1;
|
|
@@ -4922,8 +4922,8 @@ var TeamCommand = class extends Command27 {
|
|
|
4922
4922
|
this.context.stderr.write(chalk26.red("--source <path> is required for bundle-import\n"));
|
|
4923
4923
|
return 1;
|
|
4924
4924
|
}
|
|
4925
|
-
const { existsSync:
|
|
4926
|
-
if (!
|
|
4925
|
+
const { existsSync: existsSync14 } = await import("fs");
|
|
4926
|
+
if (!existsSync14(this.source)) {
|
|
4927
4927
|
this.context.stderr.write(chalk26.red(`Bundle file not found: ${this.source}
|
|
4928
4928
|
`));
|
|
4929
4929
|
return 1;
|
|
@@ -5235,18 +5235,1985 @@ var PluginCommand = class extends Command28 {
|
|
|
5235
5235
|
return 0;
|
|
5236
5236
|
}
|
|
5237
5237
|
};
|
|
5238
|
+
|
|
5239
|
+
// src/commands/methodology.ts
|
|
5240
|
+
import { Command as Command29, Option as Option28 } from "clipanion";
|
|
5241
|
+
import chalk28 from "chalk";
|
|
5242
|
+
import { createMethodologyManager, createMethodologyLoader } from "@skillkit/core";
|
|
5243
|
+
var MethodologyCommand = class extends Command29 {
|
|
5244
|
+
static paths = [["methodology"]];
|
|
5245
|
+
static usage = Command29.Usage({
|
|
5246
|
+
description: "Manage methodology packs for AI coding agents",
|
|
5247
|
+
examples: [
|
|
5248
|
+
["List available packs", "$0 methodology list"],
|
|
5249
|
+
["Install all methodology packs", "$0 methodology install"],
|
|
5250
|
+
["Install specific pack", "$0 methodology install testing"],
|
|
5251
|
+
["Install specific skill", "$0 methodology install testing/red-green-refactor"],
|
|
5252
|
+
["Sync methodology skills to agents", "$0 methodology sync"],
|
|
5253
|
+
["Search methodology skills", "$0 methodology search tdd"],
|
|
5254
|
+
["Show pack details", "$0 methodology info testing"]
|
|
5255
|
+
]
|
|
5256
|
+
});
|
|
5257
|
+
action = Option28.String({ required: true });
|
|
5258
|
+
target = Option28.String({ required: false });
|
|
5259
|
+
agent = Option28.String("--agent,-a", { description: "Target agent for sync" });
|
|
5260
|
+
dryRun = Option28.Boolean("--dry-run", { description: "Preview without making changes" });
|
|
5261
|
+
verbose = Option28.Boolean("--verbose,-v", { description: "Show detailed output" });
|
|
5262
|
+
async execute() {
|
|
5263
|
+
const projectPath = process.cwd();
|
|
5264
|
+
try {
|
|
5265
|
+
switch (this.action) {
|
|
5266
|
+
case "list":
|
|
5267
|
+
return await this.listPacks();
|
|
5268
|
+
case "install":
|
|
5269
|
+
return await this.installPacks(projectPath);
|
|
5270
|
+
case "uninstall":
|
|
5271
|
+
return await this.uninstallPack(projectPath);
|
|
5272
|
+
case "sync":
|
|
5273
|
+
return await this.syncSkills(projectPath);
|
|
5274
|
+
case "search":
|
|
5275
|
+
return await this.searchSkills();
|
|
5276
|
+
case "info":
|
|
5277
|
+
return await this.showPackInfo();
|
|
5278
|
+
case "installed":
|
|
5279
|
+
return await this.listInstalled(projectPath);
|
|
5280
|
+
default:
|
|
5281
|
+
this.context.stderr.write(chalk28.red(`Unknown action: ${this.action}
|
|
5282
|
+
`));
|
|
5283
|
+
this.context.stderr.write("Available actions: list, install, uninstall, sync, search, info, installed\n");
|
|
5284
|
+
return 1;
|
|
5285
|
+
}
|
|
5286
|
+
} catch (err) {
|
|
5287
|
+
this.context.stderr.write(chalk28.red(`\u2717 ${err instanceof Error ? err.message : "Unknown error"}
|
|
5288
|
+
`));
|
|
5289
|
+
return 1;
|
|
5290
|
+
}
|
|
5291
|
+
}
|
|
5292
|
+
async listPacks() {
|
|
5293
|
+
const loader = createMethodologyLoader();
|
|
5294
|
+
const packs = await loader.loadAllPacks();
|
|
5295
|
+
if (packs.length === 0) {
|
|
5296
|
+
this.context.stdout.write("No methodology packs available.\n");
|
|
5297
|
+
return 0;
|
|
5298
|
+
}
|
|
5299
|
+
this.context.stdout.write(chalk28.cyan("Available Methodology Packs:\n\n"));
|
|
5300
|
+
for (const pack of packs) {
|
|
5301
|
+
this.context.stdout.write(chalk28.green(` ${pack.name}`) + ` v${pack.version}
|
|
5302
|
+
`);
|
|
5303
|
+
this.context.stdout.write(chalk28.gray(` ${pack.description}
|
|
5304
|
+
`));
|
|
5305
|
+
this.context.stdout.write(` Skills: ${pack.skills.join(", ")}
|
|
5306
|
+
`);
|
|
5307
|
+
this.context.stdout.write(` Tags: ${pack.tags.join(", ")}
|
|
5308
|
+
`);
|
|
5309
|
+
this.context.stdout.write("\n");
|
|
5310
|
+
}
|
|
5311
|
+
this.context.stdout.write(`Total: ${packs.length} packs
|
|
5312
|
+
`);
|
|
5313
|
+
this.context.stdout.write(chalk28.gray("\nRun `skillkit methodology install` to install all packs.\n"));
|
|
5314
|
+
return 0;
|
|
5315
|
+
}
|
|
5316
|
+
async installPacks(projectPath) {
|
|
5317
|
+
const manager = createMethodologyManager({ projectPath });
|
|
5318
|
+
const loader = manager.getLoader();
|
|
5319
|
+
if (this.target) {
|
|
5320
|
+
if (this.target.includes("/")) {
|
|
5321
|
+
this.context.stdout.write(`Installing skill: ${chalk28.cyan(this.target)}...
|
|
5322
|
+
`);
|
|
5323
|
+
if (this.dryRun) {
|
|
5324
|
+
this.context.stdout.write(chalk28.yellow("[dry-run] Would install skill.\n"));
|
|
5325
|
+
return 0;
|
|
5326
|
+
}
|
|
5327
|
+
const result = await manager.installSkill(this.target);
|
|
5328
|
+
if (result.success) {
|
|
5329
|
+
this.context.stdout.write(chalk28.green(`\u2713 Skill installed: ${this.target}
|
|
5330
|
+
`));
|
|
5331
|
+
} else {
|
|
5332
|
+
for (const failed of result.failed) {
|
|
5333
|
+
this.context.stderr.write(chalk28.red(`\u2717 ${failed.name}: ${failed.error}
|
|
5334
|
+
`));
|
|
5335
|
+
}
|
|
5336
|
+
return 1;
|
|
5337
|
+
}
|
|
5338
|
+
} else {
|
|
5339
|
+
this.context.stdout.write(`Installing pack: ${chalk28.cyan(this.target)}...
|
|
5340
|
+
`);
|
|
5341
|
+
if (this.dryRun) {
|
|
5342
|
+
const pack = await loader.loadPack(this.target);
|
|
5343
|
+
if (pack) {
|
|
5344
|
+
this.context.stdout.write(chalk28.yellow(`[dry-run] Would install ${pack.skills.length} skills.
|
|
5345
|
+
`));
|
|
5346
|
+
}
|
|
5347
|
+
return 0;
|
|
5348
|
+
}
|
|
5349
|
+
const result = await manager.installPack(this.target);
|
|
5350
|
+
if (result.success) {
|
|
5351
|
+
this.context.stdout.write(chalk28.green(`\u2713 Pack "${this.target}" installed!
|
|
5352
|
+
`));
|
|
5353
|
+
this.context.stdout.write(` Installed: ${result.installed.length} skills
|
|
5354
|
+
`);
|
|
5355
|
+
if (result.skipped.length > 0) {
|
|
5356
|
+
this.context.stdout.write(` Skipped (already installed): ${result.skipped.length}
|
|
5357
|
+
`);
|
|
5358
|
+
}
|
|
5359
|
+
} else {
|
|
5360
|
+
this.context.stderr.write(chalk28.red(`\u2717 Failed to install pack
|
|
5361
|
+
`));
|
|
5362
|
+
for (const failed of result.failed) {
|
|
5363
|
+
this.context.stderr.write(chalk28.red(` - ${failed.name}: ${failed.error}
|
|
5364
|
+
`));
|
|
5365
|
+
}
|
|
5366
|
+
return 1;
|
|
5367
|
+
}
|
|
5368
|
+
}
|
|
5369
|
+
} else {
|
|
5370
|
+
this.context.stdout.write("Installing all methodology packs...\n\n");
|
|
5371
|
+
if (this.dryRun) {
|
|
5372
|
+
const packs = await loader.loadAllPacks();
|
|
5373
|
+
let totalSkills = 0;
|
|
5374
|
+
for (const pack of packs) {
|
|
5375
|
+
this.context.stdout.write(chalk28.yellow(`[dry-run] Would install ${pack.name} (${pack.skills.length} skills)
|
|
5376
|
+
`));
|
|
5377
|
+
totalSkills += pack.skills.length;
|
|
5378
|
+
}
|
|
5379
|
+
this.context.stdout.write(chalk28.yellow(`
|
|
5380
|
+
[dry-run] Would install ${totalSkills} skills total.
|
|
5381
|
+
`));
|
|
5382
|
+
return 0;
|
|
5383
|
+
}
|
|
5384
|
+
const result = await manager.installAllPacks();
|
|
5385
|
+
if (result.success) {
|
|
5386
|
+
this.context.stdout.write(chalk28.green("\n\u2713 All methodology packs installed!\n"));
|
|
5387
|
+
this.context.stdout.write(` Installed: ${result.installed.length} skills
|
|
5388
|
+
`);
|
|
5389
|
+
if (result.skipped.length > 0) {
|
|
5390
|
+
this.context.stdout.write(` Skipped (already installed): ${result.skipped.length}
|
|
5391
|
+
`);
|
|
5392
|
+
}
|
|
5393
|
+
} else {
|
|
5394
|
+
this.context.stdout.write(chalk28.yellow("\n\u26A0 Some skills failed to install:\n"));
|
|
5395
|
+
for (const failed of result.failed) {
|
|
5396
|
+
this.context.stderr.write(chalk28.red(` - ${failed.name}: ${failed.error}
|
|
5397
|
+
`));
|
|
5398
|
+
}
|
|
5399
|
+
this.context.stdout.write(`
|
|
5400
|
+
Installed: ${result.installed.length} skills
|
|
5401
|
+
`);
|
|
5402
|
+
}
|
|
5403
|
+
}
|
|
5404
|
+
this.context.stdout.write(chalk28.gray("\nRun `skillkit methodology sync` to sync to detected agents.\n"));
|
|
5405
|
+
return 0;
|
|
5406
|
+
}
|
|
5407
|
+
async uninstallPack(projectPath) {
|
|
5408
|
+
const manager = createMethodologyManager({ projectPath });
|
|
5409
|
+
if (!this.target) {
|
|
5410
|
+
this.context.stderr.write(chalk28.red("Pack name required for uninstall.\n"));
|
|
5411
|
+
this.context.stderr.write("Usage: skillkit methodology uninstall <pack-name>\n");
|
|
5412
|
+
return 1;
|
|
5413
|
+
}
|
|
5414
|
+
if (this.dryRun) {
|
|
5415
|
+
this.context.stdout.write(chalk28.yellow(`[dry-run] Would uninstall pack: ${this.target}
|
|
5416
|
+
`));
|
|
5417
|
+
return 0;
|
|
5418
|
+
}
|
|
5419
|
+
await manager.uninstallPack(this.target);
|
|
5420
|
+
this.context.stdout.write(chalk28.green(`\u2713 Pack "${this.target}" uninstalled.
|
|
5421
|
+
`));
|
|
5422
|
+
return 0;
|
|
5423
|
+
}
|
|
5424
|
+
async syncSkills(projectPath) {
|
|
5425
|
+
const manager = createMethodologyManager({ projectPath, autoSync: false });
|
|
5426
|
+
this.context.stdout.write("Syncing methodology skills to detected agents...\n\n");
|
|
5427
|
+
if (this.dryRun) {
|
|
5428
|
+
const installed = manager.listInstalledSkills();
|
|
5429
|
+
this.context.stdout.write(chalk28.yellow(`[dry-run] Would sync ${installed.length} skills.
|
|
5430
|
+
`));
|
|
5431
|
+
return 0;
|
|
5432
|
+
}
|
|
5433
|
+
const result = await manager.syncAll();
|
|
5434
|
+
if (result.synced.length > 0) {
|
|
5435
|
+
this.context.stdout.write(chalk28.green("\u2713 Sync complete!\n"));
|
|
5436
|
+
for (const sync of result.synced) {
|
|
5437
|
+
this.context.stdout.write(` ${sync.skill} \u2192 ${sync.agents.join(", ")}
|
|
5438
|
+
`);
|
|
5439
|
+
}
|
|
5440
|
+
}
|
|
5441
|
+
if (result.failed.length > 0) {
|
|
5442
|
+
this.context.stdout.write(chalk28.yellow("\n\u26A0 Some syncs failed:\n"));
|
|
5443
|
+
for (const fail of result.failed) {
|
|
5444
|
+
this.context.stderr.write(chalk28.red(` ${fail.skill} (${fail.agent}): ${fail.error}
|
|
5445
|
+
`));
|
|
5446
|
+
}
|
|
5447
|
+
}
|
|
5448
|
+
if (result.synced.length === 0 && result.failed.length === 0) {
|
|
5449
|
+
this.context.stdout.write("No installed skills to sync. Run `skillkit methodology install` first.\n");
|
|
5450
|
+
}
|
|
5451
|
+
return result.success ? 0 : 1;
|
|
5452
|
+
}
|
|
5453
|
+
async searchSkills() {
|
|
5454
|
+
const loader = createMethodologyLoader();
|
|
5455
|
+
if (!this.target) {
|
|
5456
|
+
this.context.stderr.write(chalk28.red("Search query required.\n"));
|
|
5457
|
+
this.context.stderr.write("Usage: skillkit methodology search <query>\n");
|
|
5458
|
+
return 1;
|
|
5459
|
+
}
|
|
5460
|
+
const skills = await loader.searchSkills(this.target);
|
|
5461
|
+
if (skills.length === 0) {
|
|
5462
|
+
this.context.stdout.write(`No skills found matching "${this.target}".
|
|
5463
|
+
`);
|
|
5464
|
+
return 0;
|
|
5465
|
+
}
|
|
5466
|
+
this.context.stdout.write(chalk28.cyan(`Found ${skills.length} skills matching "${this.target}":
|
|
5467
|
+
|
|
5468
|
+
`));
|
|
5469
|
+
for (const skill of skills) {
|
|
5470
|
+
this.context.stdout.write(chalk28.green(` ${skill.id}`) + ` v${skill.version}
|
|
5471
|
+
`);
|
|
5472
|
+
this.context.stdout.write(chalk28.gray(` ${skill.description || "No description"}
|
|
5473
|
+
`));
|
|
5474
|
+
if (skill.tags.length > 0) {
|
|
5475
|
+
this.context.stdout.write(` Tags: ${skill.tags.join(", ")}
|
|
5476
|
+
`);
|
|
5477
|
+
}
|
|
5478
|
+
if (this.verbose && skill.metadata.triggers) {
|
|
5479
|
+
this.context.stdout.write(` Triggers: ${skill.metadata.triggers.join(", ")}
|
|
5480
|
+
`);
|
|
5481
|
+
}
|
|
5482
|
+
this.context.stdout.write("\n");
|
|
5483
|
+
}
|
|
5484
|
+
return 0;
|
|
5485
|
+
}
|
|
5486
|
+
async showPackInfo() {
|
|
5487
|
+
const loader = createMethodologyLoader();
|
|
5488
|
+
if (!this.target) {
|
|
5489
|
+
this.context.stderr.write(chalk28.red("Pack name required.\n"));
|
|
5490
|
+
this.context.stderr.write("Usage: skillkit methodology info <pack-name>\n");
|
|
5491
|
+
return 1;
|
|
5492
|
+
}
|
|
5493
|
+
const pack = await loader.loadPack(this.target);
|
|
5494
|
+
if (!pack) {
|
|
5495
|
+
this.context.stderr.write(chalk28.red(`Pack not found: ${this.target}
|
|
5496
|
+
`));
|
|
5497
|
+
return 1;
|
|
5498
|
+
}
|
|
5499
|
+
this.context.stdout.write(chalk28.cyan(`
|
|
5500
|
+
Pack: ${pack.name}
|
|
5501
|
+
`));
|
|
5502
|
+
this.context.stdout.write(`Version: ${pack.version}
|
|
5503
|
+
`);
|
|
5504
|
+
this.context.stdout.write(`Description: ${pack.description}
|
|
5505
|
+
`);
|
|
5506
|
+
this.context.stdout.write(`Tags: ${pack.tags.join(", ")}
|
|
5507
|
+
`);
|
|
5508
|
+
this.context.stdout.write(`Author: ${pack.author || "Unknown"}
|
|
5509
|
+
`);
|
|
5510
|
+
this.context.stdout.write(`License: ${pack.license || "Unknown"}
|
|
5511
|
+
`);
|
|
5512
|
+
this.context.stdout.write(`Compatibility: ${pack.compatibility.join(", ")}
|
|
5513
|
+
`);
|
|
5514
|
+
this.context.stdout.write(chalk28.cyan(`
|
|
5515
|
+
Skills (${pack.skills.length}):
|
|
5516
|
+
`));
|
|
5517
|
+
const skills = await loader.loadPackSkills(this.target);
|
|
5518
|
+
for (const skill of skills) {
|
|
5519
|
+
this.context.stdout.write(chalk28.green(` ${skill.id.split("/")[1]}
|
|
5520
|
+
`));
|
|
5521
|
+
this.context.stdout.write(chalk28.gray(` ${skill.description || "No description"}
|
|
5522
|
+
`));
|
|
5523
|
+
if (this.verbose) {
|
|
5524
|
+
if (skill.metadata.triggers) {
|
|
5525
|
+
this.context.stdout.write(` Triggers: ${skill.metadata.triggers.join(", ")}
|
|
5526
|
+
`);
|
|
5527
|
+
}
|
|
5528
|
+
if (skill.metadata.difficulty) {
|
|
5529
|
+
this.context.stdout.write(` Difficulty: ${skill.metadata.difficulty}
|
|
5530
|
+
`);
|
|
5531
|
+
}
|
|
5532
|
+
if (skill.metadata.estimatedTime) {
|
|
5533
|
+
this.context.stdout.write(` Est. Time: ${skill.metadata.estimatedTime} min
|
|
5534
|
+
`);
|
|
5535
|
+
}
|
|
5536
|
+
}
|
|
5537
|
+
}
|
|
5538
|
+
this.context.stdout.write(chalk28.gray(`
|
|
5539
|
+
Run \`skillkit methodology install ${this.target}\` to install this pack.
|
|
5540
|
+
`));
|
|
5541
|
+
return 0;
|
|
5542
|
+
}
|
|
5543
|
+
async listInstalled(projectPath) {
|
|
5544
|
+
const manager = createMethodologyManager({ projectPath });
|
|
5545
|
+
const installedPacks = manager.listInstalledPacks();
|
|
5546
|
+
const installedSkills = manager.listInstalledSkills();
|
|
5547
|
+
if (installedPacks.length === 0 && installedSkills.length === 0) {
|
|
5548
|
+
this.context.stdout.write("No methodology skills installed.\n");
|
|
5549
|
+
this.context.stdout.write(chalk28.gray("Run `skillkit methodology install` to install methodology packs.\n"));
|
|
5550
|
+
return 0;
|
|
5551
|
+
}
|
|
5552
|
+
if (installedPacks.length > 0) {
|
|
5553
|
+
this.context.stdout.write(chalk28.cyan("Installed Packs:\n"));
|
|
5554
|
+
for (const pack of installedPacks) {
|
|
5555
|
+
this.context.stdout.write(chalk28.green(` ${pack.name}`) + ` v${pack.version}
|
|
5556
|
+
`);
|
|
5557
|
+
this.context.stdout.write(` Skills: ${pack.skills.length}
|
|
5558
|
+
`);
|
|
5559
|
+
}
|
|
5560
|
+
this.context.stdout.write("\n");
|
|
5561
|
+
}
|
|
5562
|
+
if (installedSkills.length > 0) {
|
|
5563
|
+
this.context.stdout.write(chalk28.cyan("Installed Skills:\n"));
|
|
5564
|
+
for (const skill of installedSkills) {
|
|
5565
|
+
const syncStatus = skill.syncedAgents.length > 0 ? chalk28.green(`synced to ${skill.syncedAgents.length} agents`) : chalk28.yellow("not synced");
|
|
5566
|
+
this.context.stdout.write(` ${chalk28.green(skill.id)} v${skill.version} [${syncStatus}]
|
|
5567
|
+
`);
|
|
5568
|
+
}
|
|
5569
|
+
}
|
|
5570
|
+
this.context.stdout.write(chalk28.gray(`
|
|
5571
|
+
Total: ${installedSkills.length} skills installed.
|
|
5572
|
+
`));
|
|
5573
|
+
return 0;
|
|
5574
|
+
}
|
|
5575
|
+
};
|
|
5576
|
+
|
|
5577
|
+
// src/commands/hook.ts
|
|
5578
|
+
import { Command as Command30, Option as Option29 } from "clipanion";
|
|
5579
|
+
import chalk29 from "chalk";
|
|
5580
|
+
import { createHookManager } from "@skillkit/core";
|
|
5581
|
+
var HookCommand = class extends Command30 {
|
|
5582
|
+
static paths = [["hook"]];
|
|
5583
|
+
static usage = Command30.Usage({
|
|
5584
|
+
description: "Manage skill hooks for automatic triggering",
|
|
5585
|
+
examples: [
|
|
5586
|
+
["List all hooks", "$0 hook list"],
|
|
5587
|
+
["Add a session start hook", "$0 hook add session:start tdd-workflow"],
|
|
5588
|
+
["Add a file save hook with pattern", '$0 hook add file:save code-review --pattern "*.ts"'],
|
|
5589
|
+
["Remove a hook", "$0 hook remove <hook-id>"],
|
|
5590
|
+
["Enable a hook", "$0 hook enable <hook-id>"],
|
|
5591
|
+
["Disable a hook", "$0 hook disable <hook-id>"],
|
|
5592
|
+
["Generate agent hooks", "$0 hook generate --agent claude-code"]
|
|
5593
|
+
]
|
|
5594
|
+
});
|
|
5595
|
+
action = Option29.String({ required: true });
|
|
5596
|
+
target = Option29.String({ required: false });
|
|
5597
|
+
skill = Option29.String({ required: false });
|
|
5598
|
+
pattern = Option29.String("--pattern,-p", { description: "File pattern matcher" });
|
|
5599
|
+
agent = Option29.String("--agent,-a", { description: "Target agent for generation" });
|
|
5600
|
+
inject = Option29.String("--inject,-i", { description: "Injection mode: content, reference, prompt" });
|
|
5601
|
+
priority = Option29.String("--priority", { description: "Hook priority (higher = earlier)" });
|
|
5602
|
+
verbose = Option29.Boolean("--verbose,-v", { description: "Show detailed output" });
|
|
5603
|
+
async execute() {
|
|
5604
|
+
const projectPath = process.cwd();
|
|
5605
|
+
try {
|
|
5606
|
+
switch (this.action) {
|
|
5607
|
+
case "list":
|
|
5608
|
+
return await this.listHooks(projectPath);
|
|
5609
|
+
case "add":
|
|
5610
|
+
return await this.addHook(projectPath);
|
|
5611
|
+
case "remove":
|
|
5612
|
+
return await this.removeHook(projectPath);
|
|
5613
|
+
case "enable":
|
|
5614
|
+
return await this.enableHook(projectPath);
|
|
5615
|
+
case "disable":
|
|
5616
|
+
return await this.disableHook(projectPath);
|
|
5617
|
+
case "generate":
|
|
5618
|
+
return await this.generateHooks(projectPath);
|
|
5619
|
+
case "info":
|
|
5620
|
+
return await this.showHookInfo(projectPath);
|
|
5621
|
+
default:
|
|
5622
|
+
this.context.stderr.write(chalk29.red(`Unknown action: ${this.action}
|
|
5623
|
+
`));
|
|
5624
|
+
this.context.stderr.write("Available actions: list, add, remove, enable, disable, generate, info\n");
|
|
5625
|
+
return 1;
|
|
5626
|
+
}
|
|
5627
|
+
} catch (err) {
|
|
5628
|
+
this.context.stderr.write(chalk29.red(`\u2717 ${err instanceof Error ? err.message : "Unknown error"}
|
|
5629
|
+
`));
|
|
5630
|
+
return 1;
|
|
5631
|
+
}
|
|
5632
|
+
}
|
|
5633
|
+
async listHooks(projectPath) {
|
|
5634
|
+
const manager = createHookManager({ projectPath });
|
|
5635
|
+
const hooks = manager.getAllHooks();
|
|
5636
|
+
if (hooks.length === 0) {
|
|
5637
|
+
this.context.stdout.write("No hooks configured.\n");
|
|
5638
|
+
this.context.stdout.write(chalk29.gray("Run `skillkit hook add <event> <skill>` to add a hook.\n"));
|
|
5639
|
+
return 0;
|
|
5640
|
+
}
|
|
5641
|
+
this.context.stdout.write(chalk29.cyan("Configured Hooks:\n\n"));
|
|
5642
|
+
const eventGroups = /* @__PURE__ */ new Map();
|
|
5643
|
+
for (const hook of hooks) {
|
|
5644
|
+
const group = eventGroups.get(hook.event) || [];
|
|
5645
|
+
group.push(hook);
|
|
5646
|
+
eventGroups.set(hook.event, group);
|
|
5647
|
+
}
|
|
5648
|
+
for (const [event, eventHooks] of eventGroups) {
|
|
5649
|
+
this.context.stdout.write(chalk29.yellow(`${event}:
|
|
5650
|
+
`));
|
|
5651
|
+
for (const hook of eventHooks) {
|
|
5652
|
+
const status = hook.enabled ? chalk29.green("\u25CF") : chalk29.gray("\u25CB");
|
|
5653
|
+
this.context.stdout.write(` ${status} ${chalk29.white(hook.id.slice(0, 8))} \u2192 ${hook.skills.join(", ")}
|
|
5654
|
+
`);
|
|
5655
|
+
if (hook.matcher) {
|
|
5656
|
+
this.context.stdout.write(chalk29.gray(` Pattern: ${hook.matcher}
|
|
5657
|
+
`));
|
|
5658
|
+
}
|
|
5659
|
+
if (this.verbose) {
|
|
5660
|
+
this.context.stdout.write(chalk29.gray(` Inject: ${hook.inject}, Priority: ${hook.priority || 0}
|
|
5661
|
+
`));
|
|
5662
|
+
}
|
|
5663
|
+
}
|
|
5664
|
+
}
|
|
5665
|
+
this.context.stdout.write(`
|
|
5666
|
+
Total: ${hooks.length} hooks
|
|
5667
|
+
`);
|
|
5668
|
+
return 0;
|
|
5669
|
+
}
|
|
5670
|
+
async addHook(projectPath) {
|
|
5671
|
+
if (!this.target) {
|
|
5672
|
+
this.context.stderr.write(chalk29.red("Event type required.\n"));
|
|
5673
|
+
this.context.stderr.write("Usage: skillkit hook add <event> <skill>\n");
|
|
5674
|
+
this.context.stderr.write("Events: session:start, session:end, file:save, file:open, task:start, commit:pre, commit:post, error:occur, test:fail, build:fail\n");
|
|
5675
|
+
return 1;
|
|
5676
|
+
}
|
|
5677
|
+
if (!this.skill) {
|
|
5678
|
+
this.context.stderr.write(chalk29.red("Skill name required.\n"));
|
|
5679
|
+
this.context.stderr.write("Usage: skillkit hook add <event> <skill>\n");
|
|
5680
|
+
return 1;
|
|
5681
|
+
}
|
|
5682
|
+
const event = this.target;
|
|
5683
|
+
const validEvents = [
|
|
5684
|
+
"session:start",
|
|
5685
|
+
"session:resume",
|
|
5686
|
+
"session:end",
|
|
5687
|
+
"file:open",
|
|
5688
|
+
"file:save",
|
|
5689
|
+
"file:create",
|
|
5690
|
+
"file:delete",
|
|
5691
|
+
"task:start",
|
|
5692
|
+
"task:complete",
|
|
5693
|
+
"commit:pre",
|
|
5694
|
+
"commit:post",
|
|
5695
|
+
"error:occur",
|
|
5696
|
+
"test:fail",
|
|
5697
|
+
"test:pass",
|
|
5698
|
+
"build:start",
|
|
5699
|
+
"build:fail",
|
|
5700
|
+
"build:success"
|
|
5701
|
+
];
|
|
5702
|
+
if (!validEvents.includes(event)) {
|
|
5703
|
+
this.context.stderr.write(chalk29.red(`Invalid event: ${this.target}
|
|
5704
|
+
`));
|
|
5705
|
+
this.context.stderr.write(`Valid events: ${validEvents.join(", ")}
|
|
5706
|
+
`);
|
|
5707
|
+
return 1;
|
|
5708
|
+
}
|
|
5709
|
+
const manager = createHookManager({ projectPath });
|
|
5710
|
+
const hook = manager.registerHook({
|
|
5711
|
+
event,
|
|
5712
|
+
skills: [this.skill],
|
|
5713
|
+
inject: this.inject || "reference",
|
|
5714
|
+
matcher: this.pattern,
|
|
5715
|
+
priority: this.priority ? parseInt(this.priority, 10) : 0,
|
|
5716
|
+
enabled: true
|
|
5717
|
+
});
|
|
5718
|
+
manager.save();
|
|
5719
|
+
this.context.stdout.write(chalk29.green(`\u2713 Hook added: ${hook.id.slice(0, 8)}
|
|
5720
|
+
`));
|
|
5721
|
+
this.context.stdout.write(` Event: ${event}
|
|
5722
|
+
`);
|
|
5723
|
+
this.context.stdout.write(` Skill: ${this.skill}
|
|
5724
|
+
`);
|
|
5725
|
+
if (this.pattern) {
|
|
5726
|
+
this.context.stdout.write(` Pattern: ${this.pattern}
|
|
5727
|
+
`);
|
|
5728
|
+
}
|
|
5729
|
+
return 0;
|
|
5730
|
+
}
|
|
5731
|
+
async removeHook(projectPath) {
|
|
5732
|
+
if (!this.target) {
|
|
5733
|
+
this.context.stderr.write(chalk29.red("Hook ID required.\n"));
|
|
5734
|
+
this.context.stderr.write("Usage: skillkit hook remove <hook-id>\n");
|
|
5735
|
+
return 1;
|
|
5736
|
+
}
|
|
5737
|
+
const manager = createHookManager({ projectPath });
|
|
5738
|
+
const hooks = manager.getAllHooks();
|
|
5739
|
+
const hook = hooks.find((h) => h.id.startsWith(this.target));
|
|
5740
|
+
if (!hook) {
|
|
5741
|
+
this.context.stderr.write(chalk29.red(`Hook not found: ${this.target}
|
|
5742
|
+
`));
|
|
5743
|
+
return 1;
|
|
5744
|
+
}
|
|
5745
|
+
manager.unregisterHook(hook.id);
|
|
5746
|
+
manager.save();
|
|
5747
|
+
this.context.stdout.write(chalk29.green(`\u2713 Hook removed: ${hook.id.slice(0, 8)}
|
|
5748
|
+
`));
|
|
5749
|
+
return 0;
|
|
5750
|
+
}
|
|
5751
|
+
async enableHook(projectPath) {
|
|
5752
|
+
if (!this.target) {
|
|
5753
|
+
this.context.stderr.write(chalk29.red("Hook ID required.\n"));
|
|
5754
|
+
return 1;
|
|
5755
|
+
}
|
|
5756
|
+
const manager = createHookManager({ projectPath });
|
|
5757
|
+
const hooks = manager.getAllHooks();
|
|
5758
|
+
const hook = hooks.find((h) => h.id.startsWith(this.target));
|
|
5759
|
+
if (!hook) {
|
|
5760
|
+
this.context.stderr.write(chalk29.red(`Hook not found: ${this.target}
|
|
5761
|
+
`));
|
|
5762
|
+
return 1;
|
|
5763
|
+
}
|
|
5764
|
+
manager.enableHook(hook.id);
|
|
5765
|
+
manager.save();
|
|
5766
|
+
this.context.stdout.write(chalk29.green(`\u2713 Hook enabled: ${hook.id.slice(0, 8)}
|
|
5767
|
+
`));
|
|
5768
|
+
return 0;
|
|
5769
|
+
}
|
|
5770
|
+
async disableHook(projectPath) {
|
|
5771
|
+
if (!this.target) {
|
|
5772
|
+
this.context.stderr.write(chalk29.red("Hook ID required.\n"));
|
|
5773
|
+
return 1;
|
|
5774
|
+
}
|
|
5775
|
+
const manager = createHookManager({ projectPath });
|
|
5776
|
+
const hooks = manager.getAllHooks();
|
|
5777
|
+
const hook = hooks.find((h) => h.id.startsWith(this.target));
|
|
5778
|
+
if (!hook) {
|
|
5779
|
+
this.context.stderr.write(chalk29.red(`Hook not found: ${this.target}
|
|
5780
|
+
`));
|
|
5781
|
+
return 1;
|
|
5782
|
+
}
|
|
5783
|
+
manager.disableHook(hook.id);
|
|
5784
|
+
manager.save();
|
|
5785
|
+
this.context.stdout.write(chalk29.yellow(`\u25CB Hook disabled: ${hook.id.slice(0, 8)}
|
|
5786
|
+
`));
|
|
5787
|
+
return 0;
|
|
5788
|
+
}
|
|
5789
|
+
async generateHooks(projectPath) {
|
|
5790
|
+
const manager = createHookManager({ projectPath });
|
|
5791
|
+
const hooks = manager.getAllHooks();
|
|
5792
|
+
if (hooks.length === 0) {
|
|
5793
|
+
this.context.stdout.write("No hooks configured to generate.\n");
|
|
5794
|
+
return 0;
|
|
5795
|
+
}
|
|
5796
|
+
const agent = this.agent || "claude-code";
|
|
5797
|
+
const generated = manager.generateAgentHooks(agent);
|
|
5798
|
+
this.context.stdout.write(chalk29.cyan(`Generated hooks for ${agent}:
|
|
5799
|
+
|
|
5800
|
+
`));
|
|
5801
|
+
if (typeof generated === "string") {
|
|
5802
|
+
this.context.stdout.write(generated);
|
|
5803
|
+
} else {
|
|
5804
|
+
this.context.stdout.write(JSON.stringify(generated, null, 2));
|
|
5805
|
+
}
|
|
5806
|
+
this.context.stdout.write("\n");
|
|
5807
|
+
return 0;
|
|
5808
|
+
}
|
|
5809
|
+
async showHookInfo(projectPath) {
|
|
5810
|
+
if (!this.target) {
|
|
5811
|
+
this.context.stderr.write(chalk29.red("Hook ID required.\n"));
|
|
5812
|
+
return 1;
|
|
5813
|
+
}
|
|
5814
|
+
const manager = createHookManager({ projectPath });
|
|
5815
|
+
const hooks = manager.getAllHooks();
|
|
5816
|
+
const hook = hooks.find((h) => h.id.startsWith(this.target));
|
|
5817
|
+
if (!hook) {
|
|
5818
|
+
this.context.stderr.write(chalk29.red(`Hook not found: ${this.target}
|
|
5819
|
+
`));
|
|
5820
|
+
return 1;
|
|
5821
|
+
}
|
|
5822
|
+
this.context.stdout.write(chalk29.cyan(`
|
|
5823
|
+
Hook: ${hook.id}
|
|
5824
|
+
`));
|
|
5825
|
+
this.context.stdout.write(`Event: ${hook.event}
|
|
5826
|
+
`);
|
|
5827
|
+
this.context.stdout.write(`Skills: ${hook.skills.join(", ")}
|
|
5828
|
+
`);
|
|
5829
|
+
this.context.stdout.write(`Enabled: ${hook.enabled ? "Yes" : "No"}
|
|
5830
|
+
`);
|
|
5831
|
+
this.context.stdout.write(`Inject: ${hook.inject}
|
|
5832
|
+
`);
|
|
5833
|
+
this.context.stdout.write(`Priority: ${hook.priority || 0}
|
|
5834
|
+
`);
|
|
5835
|
+
if (hook.matcher) {
|
|
5836
|
+
this.context.stdout.write(`Pattern: ${hook.matcher}
|
|
5837
|
+
`);
|
|
5838
|
+
}
|
|
5839
|
+
if (hook.condition) {
|
|
5840
|
+
this.context.stdout.write(`Condition: ${hook.condition}
|
|
5841
|
+
`);
|
|
5842
|
+
}
|
|
5843
|
+
if (hook.agentOverrides) {
|
|
5844
|
+
this.context.stdout.write(`Agent Overrides: ${Object.keys(hook.agentOverrides).join(", ")}
|
|
5845
|
+
`);
|
|
5846
|
+
}
|
|
5847
|
+
return 0;
|
|
5848
|
+
}
|
|
5849
|
+
};
|
|
5850
|
+
|
|
5851
|
+
// src/commands/plan.ts
|
|
5852
|
+
import { Command as Command31, Option as Option30 } from "clipanion";
|
|
5853
|
+
import { readFile, writeFile } from "fs/promises";
|
|
5854
|
+
import { resolve as resolve12 } from "path";
|
|
5855
|
+
import {
|
|
5856
|
+
createPlanParser,
|
|
5857
|
+
createPlanValidator,
|
|
5858
|
+
createPlanGenerator,
|
|
5859
|
+
createPlanExecutor,
|
|
5860
|
+
dryRunExecutor,
|
|
5861
|
+
shellExecutor,
|
|
5862
|
+
TASK_TEMPLATES
|
|
5863
|
+
} from "@skillkit/core";
|
|
5864
|
+
var PlanCommand = class extends Command31 {
|
|
5865
|
+
static paths = [["plan"]];
|
|
5866
|
+
static usage = Command31.Usage({
|
|
5867
|
+
category: "Development",
|
|
5868
|
+
description: "Manage structured development plans",
|
|
5869
|
+
details: `
|
|
5870
|
+
This command provides tools for working with structured development plans.
|
|
5871
|
+
|
|
5872
|
+
Actions:
|
|
5873
|
+
- parse: Parse a markdown plan to structured format
|
|
5874
|
+
- validate: Validate a plan for completeness
|
|
5875
|
+
- execute: Execute a plan (with optional dry-run)
|
|
5876
|
+
- generate: Generate a plan from task list
|
|
5877
|
+
- templates: List available task templates
|
|
5878
|
+
- create: Create a new empty plan
|
|
5879
|
+
|
|
5880
|
+
Examples:
|
|
5881
|
+
$ skillkit plan parse ./plan.md
|
|
5882
|
+
$ skillkit plan validate ./plan.md
|
|
5883
|
+
$ skillkit plan execute ./plan.md --dry-run
|
|
5884
|
+
$ skillkit plan generate "Add auth" --tasks "Setup,Login,Logout"
|
|
5885
|
+
$ skillkit plan templates
|
|
5886
|
+
`,
|
|
5887
|
+
examples: [
|
|
5888
|
+
["Parse a markdown plan", "$0 plan parse ./development-plan.md"],
|
|
5889
|
+
["Validate a plan", "$0 plan validate ./plan.md --strict"],
|
|
5890
|
+
["Dry-run execute a plan", "$0 plan execute ./plan.md --dry-run"],
|
|
5891
|
+
["List task templates", "$0 plan templates"]
|
|
5892
|
+
]
|
|
5893
|
+
});
|
|
5894
|
+
action = Option30.String({ required: true });
|
|
5895
|
+
file = Option30.String("--file,-f", {
|
|
5896
|
+
description: "Plan file path"
|
|
5897
|
+
});
|
|
5898
|
+
output = Option30.String("--output,-o", {
|
|
5899
|
+
description: "Output file path"
|
|
5900
|
+
});
|
|
5901
|
+
name = Option30.String("--name,-n", {
|
|
5902
|
+
description: "Plan name"
|
|
5903
|
+
});
|
|
5904
|
+
goal = Option30.String("--goal,-g", {
|
|
5905
|
+
description: "Plan goal"
|
|
5906
|
+
});
|
|
5907
|
+
tasks = Option30.String("--tasks,-t", {
|
|
5908
|
+
description: "Comma-separated list of task names"
|
|
5909
|
+
});
|
|
5910
|
+
template = Option30.String("--template", {
|
|
5911
|
+
description: "Task template to use"
|
|
5912
|
+
});
|
|
5913
|
+
techStack = Option30.String("--tech-stack", {
|
|
5914
|
+
description: "Comma-separated tech stack"
|
|
5915
|
+
});
|
|
5916
|
+
dryRun = Option30.Boolean("--dry-run", false, {
|
|
5917
|
+
description: "Dry run execution (no actual changes)"
|
|
5918
|
+
});
|
|
5919
|
+
strict = Option30.Boolean("--strict", false, {
|
|
5920
|
+
description: "Strict validation (treat warnings as errors)"
|
|
5921
|
+
});
|
|
5922
|
+
stopOnError = Option30.Boolean("--stop-on-error", true, {
|
|
5923
|
+
description: "Stop execution on first error"
|
|
5924
|
+
});
|
|
5925
|
+
verbose = Option30.Boolean("--verbose,-v", false, {
|
|
5926
|
+
description: "Verbose output"
|
|
5927
|
+
});
|
|
5928
|
+
json = Option30.Boolean("--json", false, {
|
|
5929
|
+
description: "Output as JSON"
|
|
5930
|
+
});
|
|
5931
|
+
async execute() {
|
|
5932
|
+
try {
|
|
5933
|
+
switch (this.action) {
|
|
5934
|
+
case "parse":
|
|
5935
|
+
return await this.parsePlan();
|
|
5936
|
+
case "validate":
|
|
5937
|
+
return await this.validatePlan();
|
|
5938
|
+
case "execute":
|
|
5939
|
+
return await this.executePlan();
|
|
5940
|
+
case "generate":
|
|
5941
|
+
return await this.generatePlan();
|
|
5942
|
+
case "templates":
|
|
5943
|
+
return await this.listTemplates();
|
|
5944
|
+
case "create":
|
|
5945
|
+
return await this.createPlan();
|
|
5946
|
+
default:
|
|
5947
|
+
this.context.stderr.write(
|
|
5948
|
+
`Unknown action: ${this.action}
|
|
5949
|
+
Available actions: parse, validate, execute, generate, templates, create
|
|
5950
|
+
`
|
|
5951
|
+
);
|
|
5952
|
+
return 1;
|
|
5953
|
+
}
|
|
5954
|
+
} catch (error) {
|
|
5955
|
+
this.context.stderr.write(`Error: ${error.message}
|
|
5956
|
+
`);
|
|
5957
|
+
return 1;
|
|
5958
|
+
}
|
|
5959
|
+
}
|
|
5960
|
+
async parsePlan() {
|
|
5961
|
+
if (!this.file) {
|
|
5962
|
+
this.context.stderr.write("Error: --file is required for parse action\n");
|
|
5963
|
+
return 1;
|
|
5964
|
+
}
|
|
5965
|
+
const filePath = resolve12(this.file);
|
|
5966
|
+
const content = await readFile(filePath, "utf-8");
|
|
5967
|
+
const parser = createPlanParser();
|
|
5968
|
+
const plan = parser.parse(content);
|
|
5969
|
+
if (this.json) {
|
|
5970
|
+
this.context.stdout.write(JSON.stringify(plan, null, 2) + "\n");
|
|
5971
|
+
} else {
|
|
5972
|
+
this.context.stdout.write(`Plan: ${plan.name}
|
|
5973
|
+
`);
|
|
5974
|
+
this.context.stdout.write(`Goal: ${plan.goal}
|
|
5975
|
+
`);
|
|
5976
|
+
this.context.stdout.write(`Status: ${plan.status}
|
|
5977
|
+
`);
|
|
5978
|
+
this.context.stdout.write(`Tasks: ${plan.tasks.length}
|
|
5979
|
+
|
|
5980
|
+
`);
|
|
5981
|
+
for (const task of plan.tasks) {
|
|
5982
|
+
this.context.stdout.write(` Task ${task.id}: ${task.name}
|
|
5983
|
+
`);
|
|
5984
|
+
this.context.stdout.write(` Steps: ${task.steps.length}
|
|
5985
|
+
`);
|
|
5986
|
+
if (task.estimatedMinutes) {
|
|
5987
|
+
this.context.stdout.write(` Estimated: ${task.estimatedMinutes} min
|
|
5988
|
+
`);
|
|
5989
|
+
}
|
|
5990
|
+
}
|
|
5991
|
+
}
|
|
5992
|
+
if (this.output) {
|
|
5993
|
+
const outputPath = resolve12(this.output);
|
|
5994
|
+
await writeFile(outputPath, JSON.stringify(plan, null, 2));
|
|
5995
|
+
this.context.stdout.write(`
|
|
5996
|
+
Saved to: ${outputPath}
|
|
5997
|
+
`);
|
|
5998
|
+
}
|
|
5999
|
+
return 0;
|
|
6000
|
+
}
|
|
6001
|
+
async validatePlan() {
|
|
6002
|
+
if (!this.file) {
|
|
6003
|
+
this.context.stderr.write("Error: --file is required for validate action\n");
|
|
6004
|
+
return 1;
|
|
6005
|
+
}
|
|
6006
|
+
const filePath = resolve12(this.file);
|
|
6007
|
+
const content = await readFile(filePath, "utf-8");
|
|
6008
|
+
const parser = createPlanParser();
|
|
6009
|
+
const plan = parser.parse(content);
|
|
6010
|
+
const validator = createPlanValidator({ strict: this.strict });
|
|
6011
|
+
const result = validator.validate(plan);
|
|
6012
|
+
if (this.json) {
|
|
6013
|
+
this.context.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
6014
|
+
} else {
|
|
6015
|
+
this.context.stdout.write(`Validation: ${result.valid ? "PASSED" : "FAILED"}
|
|
6016
|
+
|
|
6017
|
+
`);
|
|
6018
|
+
this.context.stdout.write(`Statistics:
|
|
6019
|
+
`);
|
|
6020
|
+
this.context.stdout.write(` Total tasks: ${result.stats.totalTasks}
|
|
6021
|
+
`);
|
|
6022
|
+
this.context.stdout.write(` Total steps: ${result.stats.totalSteps}
|
|
6023
|
+
`);
|
|
6024
|
+
this.context.stdout.write(` Estimated time: ${result.stats.estimatedMinutes} minutes
|
|
6025
|
+
`);
|
|
6026
|
+
this.context.stdout.write(` Tasks with tests: ${result.stats.tasksWithTests}
|
|
6027
|
+
`);
|
|
6028
|
+
this.context.stdout.write(` Avg steps/task: ${result.stats.avgStepsPerTask}
|
|
6029
|
+
|
|
6030
|
+
`);
|
|
6031
|
+
if (result.issues.length > 0) {
|
|
6032
|
+
this.context.stdout.write(`Issues:
|
|
6033
|
+
`);
|
|
6034
|
+
for (const issue of result.issues) {
|
|
6035
|
+
const icon = issue.type === "error" ? "\u274C" : issue.type === "warning" ? "\u26A0\uFE0F" : "\u2139\uFE0F";
|
|
6036
|
+
const location = issue.taskId ? `Task ${issue.taskId}` : "Plan";
|
|
6037
|
+
this.context.stdout.write(` ${icon} [${location}] ${issue.message}
|
|
6038
|
+
`);
|
|
6039
|
+
if (issue.suggestion && this.verbose) {
|
|
6040
|
+
this.context.stdout.write(` Suggestion: ${issue.suggestion}
|
|
6041
|
+
`);
|
|
6042
|
+
}
|
|
6043
|
+
}
|
|
6044
|
+
} else {
|
|
6045
|
+
this.context.stdout.write("No issues found.\n");
|
|
6046
|
+
}
|
|
6047
|
+
}
|
|
6048
|
+
return result.valid ? 0 : 1;
|
|
6049
|
+
}
|
|
6050
|
+
async executePlan() {
|
|
6051
|
+
if (!this.file) {
|
|
6052
|
+
this.context.stderr.write("Error: --file is required for execute action\n");
|
|
6053
|
+
return 1;
|
|
6054
|
+
}
|
|
6055
|
+
const filePath = resolve12(this.file);
|
|
6056
|
+
const content = await readFile(filePath, "utf-8");
|
|
6057
|
+
const parser = createPlanParser();
|
|
6058
|
+
const plan = parser.parse(content);
|
|
6059
|
+
const validator = createPlanValidator({ strict: this.strict });
|
|
6060
|
+
const validation = validator.validate(plan);
|
|
6061
|
+
if (!validation.valid) {
|
|
6062
|
+
this.context.stderr.write("Plan validation failed. Fix errors before executing.\n");
|
|
6063
|
+
for (const issue of validation.issues.filter((i) => i.type === "error")) {
|
|
6064
|
+
this.context.stderr.write(` \u274C ${issue.message}
|
|
6065
|
+
`);
|
|
6066
|
+
}
|
|
6067
|
+
return 1;
|
|
6068
|
+
}
|
|
6069
|
+
const executor = createPlanExecutor({
|
|
6070
|
+
stepExecutor: this.dryRun ? dryRunExecutor : shellExecutor
|
|
6071
|
+
});
|
|
6072
|
+
executor.addListener((event, _plan, task) => {
|
|
6073
|
+
if (this.verbose) {
|
|
6074
|
+
if (event === "plan:task_started" && task) {
|
|
6075
|
+
this.context.stdout.write(`Starting task ${task.id}: ${task.name}
|
|
6076
|
+
`);
|
|
6077
|
+
} else if (event === "plan:task_completed" && task) {
|
|
6078
|
+
this.context.stdout.write(`Completed task ${task.id}: ${task.name}
|
|
6079
|
+
`);
|
|
6080
|
+
} else if (event === "plan:task_failed" && task) {
|
|
6081
|
+
this.context.stderr.write(`Failed task ${task.id}: ${task.name}
|
|
6082
|
+
`);
|
|
6083
|
+
}
|
|
6084
|
+
}
|
|
6085
|
+
});
|
|
6086
|
+
this.context.stdout.write(`Executing plan: ${plan.name}
|
|
6087
|
+
`);
|
|
6088
|
+
if (this.dryRun) {
|
|
6089
|
+
this.context.stdout.write("(Dry run mode - no actual changes)\n");
|
|
6090
|
+
}
|
|
6091
|
+
this.context.stdout.write("\n");
|
|
6092
|
+
const result = await executor.execute(plan, {
|
|
6093
|
+
dryRun: this.dryRun,
|
|
6094
|
+
stopOnError: this.stopOnError,
|
|
6095
|
+
onProgress: (taskId, step, status) => {
|
|
6096
|
+
if (this.verbose) {
|
|
6097
|
+
this.context.stdout.write(` Task ${taskId}, Step ${step}: ${status}
|
|
6098
|
+
`);
|
|
6099
|
+
}
|
|
6100
|
+
}
|
|
6101
|
+
});
|
|
6102
|
+
this.context.stdout.write("\n");
|
|
6103
|
+
if (this.json) {
|
|
6104
|
+
this.context.stdout.write(
|
|
6105
|
+
JSON.stringify(
|
|
6106
|
+
{
|
|
6107
|
+
...result,
|
|
6108
|
+
taskResults: Object.fromEntries(result.taskResults)
|
|
6109
|
+
},
|
|
6110
|
+
null,
|
|
6111
|
+
2
|
|
6112
|
+
) + "\n"
|
|
6113
|
+
);
|
|
6114
|
+
} else {
|
|
6115
|
+
this.context.stdout.write(`Execution: ${result.success ? "SUCCESS" : "FAILED"}
|
|
6116
|
+
`);
|
|
6117
|
+
this.context.stdout.write(`Duration: ${result.durationMs}ms
|
|
6118
|
+
`);
|
|
6119
|
+
this.context.stdout.write(`Completed: ${result.completedTasks.length} tasks
|
|
6120
|
+
`);
|
|
6121
|
+
this.context.stdout.write(`Failed: ${result.failedTasks.length} tasks
|
|
6122
|
+
`);
|
|
6123
|
+
this.context.stdout.write(`Skipped: ${result.skippedTasks.length} tasks
|
|
6124
|
+
`);
|
|
6125
|
+
if (result.errors && result.errors.length > 0) {
|
|
6126
|
+
this.context.stdout.write("\nErrors:\n");
|
|
6127
|
+
for (const error of result.errors) {
|
|
6128
|
+
this.context.stderr.write(` - ${error}
|
|
6129
|
+
`);
|
|
6130
|
+
}
|
|
6131
|
+
}
|
|
6132
|
+
}
|
|
6133
|
+
return result.success ? 0 : 1;
|
|
6134
|
+
}
|
|
6135
|
+
async generatePlan() {
|
|
6136
|
+
if (!this.name) {
|
|
6137
|
+
this.context.stderr.write("Error: --name is required for generate action\n");
|
|
6138
|
+
return 1;
|
|
6139
|
+
}
|
|
6140
|
+
const generator = createPlanGenerator({
|
|
6141
|
+
includeTests: true,
|
|
6142
|
+
includeCommits: true,
|
|
6143
|
+
techStack: this.techStack?.split(",").map((s) => s.trim()).filter((s) => s !== "")
|
|
6144
|
+
});
|
|
6145
|
+
let plan;
|
|
6146
|
+
if (this.tasks) {
|
|
6147
|
+
const taskNames = this.tasks.split(",").map((s) => s.trim()).filter((s) => s !== "");
|
|
6148
|
+
plan = generator.fromTaskList(this.name, this.goal || "Complete the tasks", taskNames, {
|
|
6149
|
+
template: this.template,
|
|
6150
|
+
techStack: this.techStack?.split(",").map((s) => s.trim()).filter((s) => s !== "")
|
|
6151
|
+
});
|
|
6152
|
+
} else {
|
|
6153
|
+
plan = generator.createPlan(this.name, this.goal || "Complete the project");
|
|
6154
|
+
if (this.techStack) {
|
|
6155
|
+
plan.techStack = this.techStack.split(",").map((s) => s.trim()).filter((s) => s !== "");
|
|
6156
|
+
}
|
|
6157
|
+
}
|
|
6158
|
+
const markdown = generator.toMarkdown(plan);
|
|
6159
|
+
if (this.output) {
|
|
6160
|
+
const outputPath = resolve12(this.output);
|
|
6161
|
+
await writeFile(outputPath, markdown);
|
|
6162
|
+
this.context.stdout.write(`Plan saved to: ${outputPath}
|
|
6163
|
+
`);
|
|
6164
|
+
} else if (this.json) {
|
|
6165
|
+
this.context.stdout.write(JSON.stringify(plan, null, 2) + "\n");
|
|
6166
|
+
} else {
|
|
6167
|
+
this.context.stdout.write(markdown);
|
|
6168
|
+
}
|
|
6169
|
+
return 0;
|
|
6170
|
+
}
|
|
6171
|
+
async listTemplates() {
|
|
6172
|
+
if (this.json) {
|
|
6173
|
+
this.context.stdout.write(JSON.stringify(TASK_TEMPLATES, null, 2) + "\n");
|
|
6174
|
+
} else {
|
|
6175
|
+
this.context.stdout.write("Available Task Templates:\n\n");
|
|
6176
|
+
for (const [name, template] of Object.entries(TASK_TEMPLATES)) {
|
|
6177
|
+
this.context.stdout.write(` ${name}
|
|
6178
|
+
`);
|
|
6179
|
+
this.context.stdout.write(` ${template.description}
|
|
6180
|
+
`);
|
|
6181
|
+
this.context.stdout.write(` Steps: ${template.steps.length}
|
|
6182
|
+
`);
|
|
6183
|
+
this.context.stdout.write(` Estimated: ${template.estimatedMinutes} min
|
|
6184
|
+
|
|
6185
|
+
`);
|
|
6186
|
+
}
|
|
6187
|
+
}
|
|
6188
|
+
return 0;
|
|
6189
|
+
}
|
|
6190
|
+
async createPlan() {
|
|
6191
|
+
if (!this.name) {
|
|
6192
|
+
this.context.stderr.write("Error: --name is required for create action\n");
|
|
6193
|
+
return 1;
|
|
6194
|
+
}
|
|
6195
|
+
const generator = createPlanGenerator({
|
|
6196
|
+
includeTests: true,
|
|
6197
|
+
includeCommits: true
|
|
6198
|
+
});
|
|
6199
|
+
const plan = generator.createPlan(this.name, this.goal || "Complete the project");
|
|
6200
|
+
if (this.techStack) {
|
|
6201
|
+
plan.techStack = this.techStack.split(",").map((s) => s.trim()).filter((s) => s !== "");
|
|
6202
|
+
}
|
|
6203
|
+
const markdown = generator.toMarkdown(plan);
|
|
6204
|
+
const outputPath = this.output || resolve12(`${this.name.toLowerCase().replace(/\s+/g, "-")}-plan.md`);
|
|
6205
|
+
await writeFile(outputPath, markdown);
|
|
6206
|
+
this.context.stdout.write(`Plan created: ${outputPath}
|
|
6207
|
+
`);
|
|
6208
|
+
return 0;
|
|
6209
|
+
}
|
|
6210
|
+
};
|
|
6211
|
+
|
|
6212
|
+
// src/commands/command.ts
|
|
6213
|
+
import { Command as Command32, Option as Option31 } from "clipanion";
|
|
6214
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir } from "fs/promises";
|
|
6215
|
+
import { resolve as resolve13, join as join11, dirname as dirname3, extname } from "path";
|
|
6216
|
+
import * as path from "path";
|
|
6217
|
+
import * as fs from "fs/promises";
|
|
6218
|
+
import { existsSync as existsSync13 } from "fs";
|
|
6219
|
+
import {
|
|
6220
|
+
createCommandRegistry,
|
|
6221
|
+
createCommandGenerator,
|
|
6222
|
+
getAgentFormat,
|
|
6223
|
+
supportsSlashCommands
|
|
6224
|
+
} from "@skillkit/core";
|
|
6225
|
+
var CommandCmd = class extends Command32 {
|
|
6226
|
+
static paths = [["command"]];
|
|
6227
|
+
static usage = Command32.Usage({
|
|
6228
|
+
category: "Commands",
|
|
6229
|
+
description: "Manage slash commands and agent integration",
|
|
6230
|
+
details: `
|
|
6231
|
+
This command provides tools for working with slash commands.
|
|
6232
|
+
|
|
6233
|
+
Actions:
|
|
6234
|
+
- list: List registered commands
|
|
6235
|
+
- create: Create a new command from a skill
|
|
6236
|
+
- generate: Generate agent-specific command files
|
|
6237
|
+
- validate: Validate command definitions
|
|
6238
|
+
- export: Export commands to a bundle
|
|
6239
|
+
- import: Import commands from a bundle
|
|
6240
|
+
|
|
6241
|
+
Examples:
|
|
6242
|
+
$ skillkit command list
|
|
6243
|
+
$ skillkit command create my-skill --name my-command
|
|
6244
|
+
$ skillkit command generate --agent claude-code
|
|
6245
|
+
$ skillkit command validate ./commands.json
|
|
6246
|
+
`,
|
|
6247
|
+
examples: [
|
|
6248
|
+
["List all commands", "$0 command list"],
|
|
6249
|
+
["Create command from skill", "$0 command create tdd --name run-tdd"],
|
|
6250
|
+
["Generate for Claude Code", "$0 command generate --agent claude-code"],
|
|
6251
|
+
["Export commands", "$0 command export --output commands.json"]
|
|
6252
|
+
]
|
|
6253
|
+
});
|
|
6254
|
+
action = Option31.String({ required: true });
|
|
6255
|
+
skill = Option31.String("--skill,-s", {
|
|
6256
|
+
description: "Skill to create command from"
|
|
6257
|
+
});
|
|
6258
|
+
name = Option31.String("--name,-n", {
|
|
6259
|
+
description: "Command name"
|
|
6260
|
+
});
|
|
6261
|
+
description = Option31.String("--description,-d", {
|
|
6262
|
+
description: "Command description"
|
|
6263
|
+
});
|
|
6264
|
+
agent = Option31.String("--agent,-a", {
|
|
6265
|
+
description: "Target agent for generation"
|
|
6266
|
+
});
|
|
6267
|
+
output = Option31.String("--output,-o", {
|
|
6268
|
+
description: "Output file or directory"
|
|
6269
|
+
});
|
|
6270
|
+
input = Option31.String("--input,-i", {
|
|
6271
|
+
description: "Input file"
|
|
6272
|
+
});
|
|
6273
|
+
category = Option31.String("--category,-c", {
|
|
6274
|
+
description: "Command category"
|
|
6275
|
+
});
|
|
6276
|
+
all = Option31.Boolean("--all", false, {
|
|
6277
|
+
description: "Include all commands (hidden and disabled)"
|
|
6278
|
+
});
|
|
6279
|
+
json = Option31.Boolean("--json", false, {
|
|
6280
|
+
description: "Output as JSON"
|
|
6281
|
+
});
|
|
6282
|
+
dryRun = Option31.Boolean("--dry-run", false, {
|
|
6283
|
+
description: "Show what would be generated without writing files"
|
|
6284
|
+
});
|
|
6285
|
+
async execute() {
|
|
6286
|
+
try {
|
|
6287
|
+
switch (this.action) {
|
|
6288
|
+
case "list":
|
|
6289
|
+
return await this.listCommands();
|
|
6290
|
+
case "create":
|
|
6291
|
+
return await this.createCommand();
|
|
6292
|
+
case "generate":
|
|
6293
|
+
return await this.generateCommands();
|
|
6294
|
+
case "validate":
|
|
6295
|
+
return await this.validateCommands();
|
|
6296
|
+
case "export":
|
|
6297
|
+
return await this.exportCommands();
|
|
6298
|
+
case "import":
|
|
6299
|
+
return await this.importCommands();
|
|
6300
|
+
case "info":
|
|
6301
|
+
return await this.showInfo();
|
|
6302
|
+
default:
|
|
6303
|
+
this.context.stderr.write(
|
|
6304
|
+
`Unknown action: ${this.action}
|
|
6305
|
+
Available actions: list, create, generate, validate, export, import, info
|
|
6306
|
+
`
|
|
6307
|
+
);
|
|
6308
|
+
return 1;
|
|
6309
|
+
}
|
|
6310
|
+
} catch (error) {
|
|
6311
|
+
this.context.stderr.write(`Error: ${error.message}
|
|
6312
|
+
`);
|
|
6313
|
+
return 1;
|
|
6314
|
+
}
|
|
6315
|
+
}
|
|
6316
|
+
async listCommands() {
|
|
6317
|
+
const registry = createCommandRegistry();
|
|
6318
|
+
await this.loadCommandsFromConfig(registry);
|
|
6319
|
+
const commands = registry.search({
|
|
6320
|
+
category: this.category,
|
|
6321
|
+
includeHidden: this.all,
|
|
6322
|
+
includeDisabled: this.all
|
|
6323
|
+
});
|
|
6324
|
+
if (this.json) {
|
|
6325
|
+
this.context.stdout.write(JSON.stringify(commands, null, 2) + "\n");
|
|
6326
|
+
} else {
|
|
6327
|
+
if (commands.length === 0) {
|
|
6328
|
+
this.context.stdout.write("No commands registered.\n");
|
|
6329
|
+
this.context.stdout.write('Use "skillkit command create" to create commands from skills.\n');
|
|
6330
|
+
return 0;
|
|
6331
|
+
}
|
|
6332
|
+
this.context.stdout.write(`Registered Commands (${commands.length}):
|
|
6333
|
+
|
|
6334
|
+
`);
|
|
6335
|
+
const categories = /* @__PURE__ */ new Map();
|
|
6336
|
+
for (const cmd of commands) {
|
|
6337
|
+
const cat = cmd.category || "uncategorized";
|
|
6338
|
+
if (!categories.has(cat)) {
|
|
6339
|
+
categories.set(cat, []);
|
|
6340
|
+
}
|
|
6341
|
+
categories.get(cat).push(cmd);
|
|
6342
|
+
}
|
|
6343
|
+
for (const [category, cmds] of categories) {
|
|
6344
|
+
this.context.stdout.write(` ${category.toUpperCase()}
|
|
6345
|
+
`);
|
|
6346
|
+
for (const cmd of cmds) {
|
|
6347
|
+
const status = cmd.enabled ? "" : " (disabled)";
|
|
6348
|
+
const hidden = cmd.hidden ? " (hidden)" : "";
|
|
6349
|
+
this.context.stdout.write(` /${cmd.name}${status}${hidden}
|
|
6350
|
+
`);
|
|
6351
|
+
this.context.stdout.write(` ${cmd.description}
|
|
6352
|
+
`);
|
|
6353
|
+
if (cmd.aliases && cmd.aliases.length > 0) {
|
|
6354
|
+
this.context.stdout.write(` Aliases: ${cmd.aliases.join(", ")}
|
|
6355
|
+
`);
|
|
6356
|
+
}
|
|
6357
|
+
}
|
|
6358
|
+
this.context.stdout.write("\n");
|
|
6359
|
+
}
|
|
6360
|
+
}
|
|
6361
|
+
return 0;
|
|
6362
|
+
}
|
|
6363
|
+
async createCommand() {
|
|
6364
|
+
if (!this.skill) {
|
|
6365
|
+
this.context.stderr.write("Error: --skill is required for create action\n");
|
|
6366
|
+
return 1;
|
|
6367
|
+
}
|
|
6368
|
+
const commandName = this.name || this.skill.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
6369
|
+
const command = {
|
|
6370
|
+
name: commandName,
|
|
6371
|
+
description: this.description || `Execute ${this.skill} skill`,
|
|
6372
|
+
skill: this.skill,
|
|
6373
|
+
category: this.category,
|
|
6374
|
+
examples: [`/${commandName}`]
|
|
6375
|
+
};
|
|
6376
|
+
const configPath = resolve13(process.cwd(), ".skillkit", "commands.json");
|
|
6377
|
+
let commands = [];
|
|
6378
|
+
if (existsSync13(configPath)) {
|
|
6379
|
+
const content = await readFile2(configPath, "utf-8");
|
|
6380
|
+
const config = JSON.parse(content);
|
|
6381
|
+
commands = config.commands || [];
|
|
6382
|
+
}
|
|
6383
|
+
const existing = commands.findIndex((c) => c.name === commandName);
|
|
6384
|
+
if (existing >= 0) {
|
|
6385
|
+
const previous = commands[existing];
|
|
6386
|
+
commands[existing] = {
|
|
6387
|
+
...previous,
|
|
6388
|
+
name: commandName,
|
|
6389
|
+
skill: this.skill,
|
|
6390
|
+
description: this.description ?? previous.description ?? command.description,
|
|
6391
|
+
category: this.category ?? previous.category,
|
|
6392
|
+
examples: command.examples?.length ? command.examples : previous.examples,
|
|
6393
|
+
// Preserve existing metadata if not provided
|
|
6394
|
+
aliases: previous.aliases,
|
|
6395
|
+
args: previous.args,
|
|
6396
|
+
tags: previous.tags,
|
|
6397
|
+
hidden: previous.hidden,
|
|
6398
|
+
metadata: previous.metadata
|
|
6399
|
+
};
|
|
6400
|
+
this.context.stdout.write(`Updated command: /${commandName}
|
|
6401
|
+
`);
|
|
6402
|
+
} else {
|
|
6403
|
+
commands.push(command);
|
|
6404
|
+
this.context.stdout.write(`Created command: /${commandName}
|
|
6405
|
+
`);
|
|
6406
|
+
}
|
|
6407
|
+
await mkdir(dirname3(configPath), { recursive: true });
|
|
6408
|
+
await writeFile2(
|
|
6409
|
+
configPath,
|
|
6410
|
+
JSON.stringify({ version: "1.0.0", commands }, null, 2)
|
|
6411
|
+
);
|
|
6412
|
+
this.context.stdout.write(`Saved to: ${configPath}
|
|
6413
|
+
`);
|
|
6414
|
+
return 0;
|
|
6415
|
+
}
|
|
6416
|
+
async generateCommands() {
|
|
6417
|
+
const agent = this.agent || "claude-code";
|
|
6418
|
+
if (!supportsSlashCommands(agent)) {
|
|
6419
|
+
this.context.stdout.write(`Note: ${agent} has limited slash command support.
|
|
6420
|
+
`);
|
|
6421
|
+
}
|
|
6422
|
+
const registry = createCommandRegistry();
|
|
6423
|
+
await this.loadCommandsFromConfig(registry);
|
|
6424
|
+
const commands = registry.getAll(this.all);
|
|
6425
|
+
if (commands.length === 0) {
|
|
6426
|
+
this.context.stderr.write("No commands to generate. Create commands first.\n");
|
|
6427
|
+
return 1;
|
|
6428
|
+
}
|
|
6429
|
+
const generator = createCommandGenerator({
|
|
6430
|
+
includeHidden: this.all,
|
|
6431
|
+
includeDisabled: this.all
|
|
6432
|
+
});
|
|
6433
|
+
const format = getAgentFormat(agent);
|
|
6434
|
+
const outputTarget = this.output ? resolve13(this.output) : resolve13(process.cwd(), format.directory);
|
|
6435
|
+
const outputDir = extname(outputTarget) ? dirname3(outputTarget) : outputTarget;
|
|
6436
|
+
const result = generator.generate(commands, agent);
|
|
6437
|
+
if (typeof result === "string") {
|
|
6438
|
+
const extension = format.extension ? format.extension.startsWith(".") ? format.extension : `.${format.extension}` : ".mdc";
|
|
6439
|
+
const filePath = extname(outputTarget) ? outputTarget : join11(outputTarget, `commands${extension}`);
|
|
6440
|
+
if (this.dryRun) {
|
|
6441
|
+
this.context.stdout.write("Would write to: " + filePath + "\n\n");
|
|
6442
|
+
this.context.stdout.write(result);
|
|
6443
|
+
} else {
|
|
6444
|
+
await mkdir(dirname3(filePath), { recursive: true });
|
|
6445
|
+
await writeFile2(filePath, result);
|
|
6446
|
+
this.context.stdout.write(`Generated: ${filePath}
|
|
6447
|
+
`);
|
|
6448
|
+
}
|
|
6449
|
+
} else {
|
|
6450
|
+
if (extname(outputTarget)) {
|
|
6451
|
+
this.context.stderr.write(
|
|
6452
|
+
"Error: --output must be a directory for multi-file formats\n"
|
|
6453
|
+
);
|
|
6454
|
+
return 1;
|
|
6455
|
+
}
|
|
6456
|
+
if (this.dryRun) {
|
|
6457
|
+
this.context.stdout.write("Would write to: " + outputDir + "\n\n");
|
|
6458
|
+
for (const [filename, content] of result) {
|
|
6459
|
+
this.context.stdout.write(`--- ${filename} ---
|
|
6460
|
+
`);
|
|
6461
|
+
this.context.stdout.write(content.substring(0, 500));
|
|
6462
|
+
if (content.length > 500) {
|
|
6463
|
+
this.context.stdout.write("\n... (truncated)\n");
|
|
6464
|
+
}
|
|
6465
|
+
this.context.stdout.write("\n");
|
|
6466
|
+
}
|
|
6467
|
+
} else {
|
|
6468
|
+
await mkdir(outputDir, { recursive: true });
|
|
6469
|
+
for (const [filename, content] of result) {
|
|
6470
|
+
const filePath = join11(outputDir, filename);
|
|
6471
|
+
await writeFile2(filePath, content);
|
|
6472
|
+
this.context.stdout.write(`Generated: ${filePath}
|
|
6473
|
+
`);
|
|
6474
|
+
}
|
|
6475
|
+
}
|
|
6476
|
+
}
|
|
6477
|
+
const manifestPath = join11(outputDir, "manifest.json");
|
|
6478
|
+
const manifest = generator.generateManifest(commands);
|
|
6479
|
+
if (!this.dryRun) {
|
|
6480
|
+
await writeFile2(manifestPath, manifest);
|
|
6481
|
+
this.context.stdout.write(`Generated: ${manifestPath}
|
|
6482
|
+
`);
|
|
6483
|
+
}
|
|
6484
|
+
this.context.stdout.write(`
|
|
6485
|
+
Generated ${commands.length} commands for ${agent}
|
|
6486
|
+
`);
|
|
6487
|
+
return 0;
|
|
6488
|
+
}
|
|
6489
|
+
async validateCommands() {
|
|
6490
|
+
const inputPath = this.input || resolve13(process.cwd(), ".skillkit", "commands.json");
|
|
6491
|
+
if (!existsSync13(inputPath)) {
|
|
6492
|
+
this.context.stderr.write(`File not found: ${inputPath}
|
|
6493
|
+
`);
|
|
6494
|
+
return 1;
|
|
6495
|
+
}
|
|
6496
|
+
const content = await readFile2(inputPath, "utf-8");
|
|
6497
|
+
const config = JSON.parse(content);
|
|
6498
|
+
const commands = config.commands || [];
|
|
6499
|
+
const registry = createCommandRegistry({ validateOnRegister: false });
|
|
6500
|
+
let valid = 0;
|
|
6501
|
+
let invalid = 0;
|
|
6502
|
+
this.context.stdout.write(`Validating ${commands.length} commands...
|
|
6503
|
+
|
|
6504
|
+
`);
|
|
6505
|
+
for (const command of commands) {
|
|
6506
|
+
const result = registry.validate(command);
|
|
6507
|
+
if (result.valid) {
|
|
6508
|
+
valid++;
|
|
6509
|
+
if (!this.json) {
|
|
6510
|
+
this.context.stdout.write(`\u2713 /${command.name}
|
|
6511
|
+
`);
|
|
6512
|
+
}
|
|
6513
|
+
} else {
|
|
6514
|
+
invalid++;
|
|
6515
|
+
if (!this.json) {
|
|
6516
|
+
this.context.stdout.write(`\u2717 /${command.name}
|
|
6517
|
+
`);
|
|
6518
|
+
for (const error of result.errors) {
|
|
6519
|
+
this.context.stdout.write(` Error: ${error}
|
|
6520
|
+
`);
|
|
6521
|
+
}
|
|
6522
|
+
}
|
|
6523
|
+
}
|
|
6524
|
+
if (!this.json && result.warnings.length > 0) {
|
|
6525
|
+
for (const warning of result.warnings) {
|
|
6526
|
+
this.context.stdout.write(` Warning: ${warning}
|
|
6527
|
+
`);
|
|
6528
|
+
}
|
|
6529
|
+
}
|
|
6530
|
+
}
|
|
6531
|
+
if (this.json) {
|
|
6532
|
+
this.context.stdout.write(
|
|
6533
|
+
JSON.stringify({
|
|
6534
|
+
total: commands.length,
|
|
6535
|
+
valid,
|
|
6536
|
+
invalid
|
|
6537
|
+
}) + "\n"
|
|
6538
|
+
);
|
|
6539
|
+
} else {
|
|
6540
|
+
this.context.stdout.write(`
|
|
6541
|
+
Validation complete: ${valid} valid, ${invalid} invalid
|
|
6542
|
+
`);
|
|
6543
|
+
}
|
|
6544
|
+
return invalid > 0 ? 1 : 0;
|
|
6545
|
+
}
|
|
6546
|
+
async exportCommands() {
|
|
6547
|
+
const registry = createCommandRegistry();
|
|
6548
|
+
await this.loadCommandsFromConfig(registry);
|
|
6549
|
+
const bundle = registry.export(this.all);
|
|
6550
|
+
if (this.output) {
|
|
6551
|
+
let outputPath = resolve13(this.output);
|
|
6552
|
+
const stat2 = await fs.stat(outputPath).catch(() => null);
|
|
6553
|
+
if (stat2 && stat2.isDirectory() || this.output.endsWith(path.sep)) {
|
|
6554
|
+
outputPath = path.join(outputPath, "commands.json");
|
|
6555
|
+
}
|
|
6556
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
6557
|
+
await writeFile2(outputPath, JSON.stringify(bundle, null, 2));
|
|
6558
|
+
this.context.stdout.write(`Exported ${bundle.commands.length} commands to: ${outputPath}
|
|
6559
|
+
`);
|
|
6560
|
+
} else {
|
|
6561
|
+
this.context.stdout.write(JSON.stringify(bundle, null, 2) + "\n");
|
|
6562
|
+
}
|
|
6563
|
+
return 0;
|
|
6564
|
+
}
|
|
6565
|
+
async importCommands() {
|
|
6566
|
+
if (!this.input) {
|
|
6567
|
+
this.context.stderr.write("Error: --input is required for import action\n");
|
|
6568
|
+
return 1;
|
|
6569
|
+
}
|
|
6570
|
+
const inputPath = resolve13(this.input);
|
|
6571
|
+
if (!existsSync13(inputPath)) {
|
|
6572
|
+
this.context.stderr.write(`File not found: ${inputPath}
|
|
6573
|
+
`);
|
|
6574
|
+
return 1;
|
|
6575
|
+
}
|
|
6576
|
+
const content = await readFile2(inputPath, "utf-8");
|
|
6577
|
+
const bundle = JSON.parse(content);
|
|
6578
|
+
const configPath = resolve13(process.cwd(), ".skillkit", "commands.json");
|
|
6579
|
+
let existingCommands = [];
|
|
6580
|
+
if (existsSync13(configPath)) {
|
|
6581
|
+
const existingContent = await readFile2(configPath, "utf-8");
|
|
6582
|
+
const config = JSON.parse(existingContent);
|
|
6583
|
+
existingCommands = config.commands || [];
|
|
6584
|
+
}
|
|
6585
|
+
const commandMap = /* @__PURE__ */ new Map();
|
|
6586
|
+
for (const cmd of existingCommands) {
|
|
6587
|
+
commandMap.set(cmd.name, cmd);
|
|
6588
|
+
}
|
|
6589
|
+
for (const cmd of bundle.commands) {
|
|
6590
|
+
commandMap.set(cmd.name, cmd);
|
|
6591
|
+
}
|
|
6592
|
+
const mergedCommands = Array.from(commandMap.values());
|
|
6593
|
+
await mkdir(dirname3(configPath), { recursive: true });
|
|
6594
|
+
await writeFile2(
|
|
6595
|
+
configPath,
|
|
6596
|
+
JSON.stringify({ version: "1.0.0", commands: mergedCommands }, null, 2)
|
|
6597
|
+
);
|
|
6598
|
+
const imported = bundle.commands.length;
|
|
6599
|
+
this.context.stdout.write(`Imported ${imported} commands
|
|
6600
|
+
`);
|
|
6601
|
+
this.context.stdout.write(`Total commands: ${mergedCommands.length}
|
|
6602
|
+
`);
|
|
6603
|
+
this.context.stdout.write(`Saved to: ${configPath}
|
|
6604
|
+
`);
|
|
6605
|
+
return 0;
|
|
6606
|
+
}
|
|
6607
|
+
async showInfo() {
|
|
6608
|
+
const agent = this.agent || "claude-code";
|
|
6609
|
+
const format = getAgentFormat(agent);
|
|
6610
|
+
if (this.json) {
|
|
6611
|
+
this.context.stdout.write(JSON.stringify(format, null, 2) + "\n");
|
|
6612
|
+
} else {
|
|
6613
|
+
this.context.stdout.write(`Agent: ${agent}
|
|
6614
|
+
`);
|
|
6615
|
+
this.context.stdout.write(`Directory: ${format.directory}
|
|
6616
|
+
`);
|
|
6617
|
+
this.context.stdout.write(`Extension: ${format.extension}
|
|
6618
|
+
`);
|
|
6619
|
+
this.context.stdout.write(`Supports Slash Commands: ${format.supportsSlashCommands}
|
|
6620
|
+
`);
|
|
6621
|
+
this.context.stdout.write(`Supports Command Files: ${format.supportsCommandFiles}
|
|
6622
|
+
`);
|
|
6623
|
+
}
|
|
6624
|
+
return 0;
|
|
6625
|
+
}
|
|
6626
|
+
async loadCommandsFromConfig(registry) {
|
|
6627
|
+
const configPath = resolve13(process.cwd(), ".skillkit", "commands.json");
|
|
6628
|
+
if (existsSync13(configPath)) {
|
|
6629
|
+
const content = await readFile2(configPath, "utf-8");
|
|
6630
|
+
const config = JSON.parse(content);
|
|
6631
|
+
const commands = config.commands || [];
|
|
6632
|
+
for (const command of commands) {
|
|
6633
|
+
try {
|
|
6634
|
+
registry.register(command);
|
|
6635
|
+
} catch {
|
|
6636
|
+
}
|
|
6637
|
+
}
|
|
6638
|
+
}
|
|
6639
|
+
}
|
|
6640
|
+
};
|
|
6641
|
+
|
|
6642
|
+
// src/commands/ai.ts
|
|
6643
|
+
import { Command as Command33, Option as Option32 } from "clipanion";
|
|
6644
|
+
import { resolve as resolve14 } from "path";
|
|
6645
|
+
import { promises as fs2 } from "fs";
|
|
6646
|
+
import chalk30 from "chalk";
|
|
6647
|
+
import ora7 from "ora";
|
|
6648
|
+
import {
|
|
6649
|
+
AIManager,
|
|
6650
|
+
loadIndex as loadIndexFromCache2
|
|
6651
|
+
} from "@skillkit/core";
|
|
6652
|
+
var AICommand = class extends Command33 {
|
|
6653
|
+
static paths = [["ai"]];
|
|
6654
|
+
static usage = Command33.Usage({
|
|
6655
|
+
description: "AI-powered skill search and generation",
|
|
6656
|
+
details: `
|
|
6657
|
+
The ai command provides AI-powered features for skills:
|
|
6658
|
+
|
|
6659
|
+
- Natural language search: Find skills using conversational queries
|
|
6660
|
+
- Skill generation: Automatically generate skills from examples
|
|
6661
|
+
- Similar skills: Find skills similar to a given skill
|
|
6662
|
+
|
|
6663
|
+
Note: Without an API key, the command uses a basic mock AI provider.
|
|
6664
|
+
Set ANTHROPIC_API_KEY or OPENAI_API_KEY to use real AI features.
|
|
6665
|
+
`,
|
|
6666
|
+
examples: [
|
|
6667
|
+
["Search skills naturally", '$0 ai search "help me write better tests"'],
|
|
6668
|
+
["Generate skill from description", '$0 ai generate --description "skill for code review"'],
|
|
6669
|
+
["Generate from code example", '$0 ai generate --from-code example.ts --description "..."'],
|
|
6670
|
+
["Find similar skills", "$0 ai similar my-skill-name"]
|
|
6671
|
+
]
|
|
6672
|
+
});
|
|
6673
|
+
// Subcommand
|
|
6674
|
+
subcommand = Option32.String({ required: true });
|
|
6675
|
+
// Search options
|
|
6676
|
+
query = Option32.String("--query,-q", {
|
|
6677
|
+
description: "Search query for natural language search"
|
|
6678
|
+
});
|
|
6679
|
+
// Generation options
|
|
6680
|
+
description = Option32.String("--description,-d", {
|
|
6681
|
+
description: "Description of the skill to generate"
|
|
6682
|
+
});
|
|
6683
|
+
fromCode = Option32.String("--from-code", {
|
|
6684
|
+
description: "Generate skill from code example file"
|
|
6685
|
+
});
|
|
6686
|
+
additionalContext = Option32.String("--context", {
|
|
6687
|
+
description: "Additional context for generation"
|
|
6688
|
+
});
|
|
6689
|
+
targetAgent = Option32.String("--agent,-a", {
|
|
6690
|
+
description: "Target agent for generated skill"
|
|
6691
|
+
});
|
|
6692
|
+
// Similar skills option
|
|
6693
|
+
skillName = Option32.String({ required: false });
|
|
6694
|
+
// Common options
|
|
6695
|
+
limit = Option32.String("--limit,-l", {
|
|
6696
|
+
description: "Maximum number of results"
|
|
6697
|
+
});
|
|
6698
|
+
minRelevance = Option32.String("--min-relevance", {
|
|
6699
|
+
description: "Minimum relevance score (0-1)"
|
|
6700
|
+
});
|
|
6701
|
+
json = Option32.Boolean("--json,-j", false, {
|
|
6702
|
+
description: "Output in JSON format"
|
|
6703
|
+
});
|
|
6704
|
+
output = Option32.String("--output,-o", {
|
|
6705
|
+
description: "Output file for generated skill"
|
|
6706
|
+
});
|
|
6707
|
+
async execute() {
|
|
6708
|
+
const aiConfig = this.getAIConfig();
|
|
6709
|
+
const manager = new AIManager(aiConfig);
|
|
6710
|
+
if (!this.json) {
|
|
6711
|
+
const providerName = manager.getProviderName();
|
|
6712
|
+
if (providerName === "mock") {
|
|
6713
|
+
console.log(
|
|
6714
|
+
chalk30.yellow(
|
|
6715
|
+
"\u26A0 Using mock AI provider (limited functionality)\n Set ANTHROPIC_API_KEY or OPENAI_API_KEY for real AI features\n"
|
|
6716
|
+
)
|
|
6717
|
+
);
|
|
6718
|
+
}
|
|
6719
|
+
}
|
|
6720
|
+
switch (this.subcommand) {
|
|
6721
|
+
case "search":
|
|
6722
|
+
return await this.handleSearch(manager);
|
|
6723
|
+
case "generate":
|
|
6724
|
+
case "gen":
|
|
6725
|
+
return await this.handleGenerate(manager);
|
|
6726
|
+
case "similar":
|
|
6727
|
+
return await this.handleSimilar(manager);
|
|
6728
|
+
default:
|
|
6729
|
+
console.error(
|
|
6730
|
+
chalk30.red(`Unknown subcommand: ${this.subcommand}
|
|
6731
|
+
`)
|
|
6732
|
+
);
|
|
6733
|
+
console.log("Valid subcommands: search, generate, similar");
|
|
6734
|
+
return 1;
|
|
6735
|
+
}
|
|
6736
|
+
}
|
|
6737
|
+
async handleSearch(manager) {
|
|
6738
|
+
const query = this.query || this.skillName;
|
|
6739
|
+
if (!query) {
|
|
6740
|
+
console.error(chalk30.red("Search query required (--query or positional argument)"));
|
|
6741
|
+
return 1;
|
|
6742
|
+
}
|
|
6743
|
+
const skills = await this.loadSkills();
|
|
6744
|
+
if (skills.length === 0) {
|
|
6745
|
+
console.log(
|
|
6746
|
+
chalk30.yellow('No skills found. Run "skillkit recommend --update" first.')
|
|
6747
|
+
);
|
|
6748
|
+
return 0;
|
|
6749
|
+
}
|
|
6750
|
+
const spinner = ora7("Searching with AI...").start();
|
|
6751
|
+
try {
|
|
6752
|
+
const results = await manager.searchSkills(query, skills, {
|
|
6753
|
+
limit: this.limit ? parseInt(this.limit, 10) : 10,
|
|
6754
|
+
minRelevance: this.minRelevance ? parseFloat(this.minRelevance) : 0.5,
|
|
6755
|
+
includeReasoning: !this.json
|
|
6756
|
+
});
|
|
6757
|
+
spinner.stop();
|
|
6758
|
+
if (this.json) {
|
|
6759
|
+
console.log(JSON.stringify(results, null, 2));
|
|
6760
|
+
return 0;
|
|
6761
|
+
}
|
|
6762
|
+
if (results.length === 0) {
|
|
6763
|
+
console.log(chalk30.yellow(`No skills found matching "${query}"`));
|
|
6764
|
+
return 0;
|
|
6765
|
+
}
|
|
6766
|
+
console.log(chalk30.cyan(`
|
|
6767
|
+
AI Search Results (${results.length} found):
|
|
6768
|
+
`));
|
|
6769
|
+
for (const result of results) {
|
|
6770
|
+
const score = Math.round(result.relevance * 100);
|
|
6771
|
+
const scoreColor = score >= 70 ? chalk30.green : score >= 50 ? chalk30.yellow : chalk30.dim;
|
|
6772
|
+
console.log(
|
|
6773
|
+
` ${scoreColor(`${score}%`)} ${chalk30.bold(result.skill.name)}`
|
|
6774
|
+
);
|
|
6775
|
+
if (result.skill.description) {
|
|
6776
|
+
console.log(` ${chalk30.dim(result.skill.description)}`);
|
|
6777
|
+
}
|
|
6778
|
+
if (result.reasoning) {
|
|
6779
|
+
console.log(` ${chalk30.dim("\u2192 " + result.reasoning)}`);
|
|
6780
|
+
}
|
|
6781
|
+
console.log();
|
|
6782
|
+
}
|
|
6783
|
+
return 0;
|
|
6784
|
+
} catch (error) {
|
|
6785
|
+
spinner.fail("Search failed");
|
|
6786
|
+
console.error(
|
|
6787
|
+
chalk30.red(error instanceof Error ? error.message : String(error))
|
|
6788
|
+
);
|
|
6789
|
+
return 1;
|
|
6790
|
+
}
|
|
6791
|
+
}
|
|
6792
|
+
async handleGenerate(manager) {
|
|
6793
|
+
const description = this.description;
|
|
6794
|
+
if (!description) {
|
|
6795
|
+
console.error(chalk30.red("Description required (--description)"));
|
|
6796
|
+
return 1;
|
|
6797
|
+
}
|
|
6798
|
+
let codeExamples = [];
|
|
6799
|
+
if (this.fromCode) {
|
|
6800
|
+
const codePath = resolve14(this.fromCode);
|
|
6801
|
+
try {
|
|
6802
|
+
const code = await fs2.readFile(codePath, "utf-8");
|
|
6803
|
+
codeExamples = [code];
|
|
6804
|
+
} catch {
|
|
6805
|
+
console.error(chalk30.red(`Failed to read code file: ${codePath}`));
|
|
6806
|
+
return 1;
|
|
6807
|
+
}
|
|
6808
|
+
}
|
|
6809
|
+
const example = {
|
|
6810
|
+
description,
|
|
6811
|
+
context: this.additionalContext,
|
|
6812
|
+
codeExamples,
|
|
6813
|
+
targetAgent: this.targetAgent
|
|
6814
|
+
};
|
|
6815
|
+
const spinner = ora7("Generating skill with AI...").start();
|
|
6816
|
+
try {
|
|
6817
|
+
const generated = await manager.generateSkill(example, {
|
|
6818
|
+
targetAgent: this.targetAgent,
|
|
6819
|
+
includeTests: true,
|
|
6820
|
+
includeDocumentation: true
|
|
6821
|
+
});
|
|
6822
|
+
spinner.stop();
|
|
6823
|
+
const validation = manager.validateGenerated(generated);
|
|
6824
|
+
if (!validation.valid) {
|
|
6825
|
+
console.log(chalk30.yellow("\n\u26A0 Generated skill has validation warnings:"));
|
|
6826
|
+
for (const error of validation.errors) {
|
|
6827
|
+
console.log(chalk30.dim(` \u2022 ${error}`));
|
|
6828
|
+
}
|
|
6829
|
+
console.log();
|
|
6830
|
+
}
|
|
6831
|
+
if (this.json) {
|
|
6832
|
+
console.log(JSON.stringify(generated, null, 2));
|
|
6833
|
+
return 0;
|
|
6834
|
+
}
|
|
6835
|
+
console.log(chalk30.green("\n\u2713 Generated skill successfully\n"));
|
|
6836
|
+
console.log(chalk30.cyan("Name:") + ` ${generated.name}`);
|
|
6837
|
+
console.log(chalk30.cyan("Description:") + ` ${generated.description}`);
|
|
6838
|
+
console.log(
|
|
6839
|
+
chalk30.cyan("Tags:") + ` ${generated.tags.join(", ")}`
|
|
6840
|
+
);
|
|
6841
|
+
console.log(
|
|
6842
|
+
chalk30.cyan("Confidence:") + ` ${Math.round(generated.confidence * 100)}%`
|
|
6843
|
+
);
|
|
6844
|
+
if (generated.reasoning) {
|
|
6845
|
+
console.log(chalk30.cyan("Reasoning:") + ` ${generated.reasoning}`);
|
|
6846
|
+
}
|
|
6847
|
+
console.log("\n" + chalk30.dim("Content:"));
|
|
6848
|
+
console.log(chalk30.dim("\u2500".repeat(60)));
|
|
6849
|
+
console.log(generated.content);
|
|
6850
|
+
console.log(chalk30.dim("\u2500".repeat(60)));
|
|
6851
|
+
if (this.output) {
|
|
6852
|
+
const outputPath = resolve14(this.output);
|
|
6853
|
+
await fs2.writeFile(outputPath, generated.content, "utf-8");
|
|
6854
|
+
console.log(chalk30.green(`
|
|
6855
|
+
\u2713 Saved to: ${outputPath}`));
|
|
6856
|
+
} else {
|
|
6857
|
+
console.log(
|
|
6858
|
+
chalk30.dim("\nSave with: --output <filename>")
|
|
6859
|
+
);
|
|
6860
|
+
}
|
|
6861
|
+
return 0;
|
|
6862
|
+
} catch (error) {
|
|
6863
|
+
spinner.fail("Generation failed");
|
|
6864
|
+
console.error(
|
|
6865
|
+
chalk30.red(error instanceof Error ? error.message : String(error))
|
|
6866
|
+
);
|
|
6867
|
+
return 1;
|
|
6868
|
+
}
|
|
6869
|
+
}
|
|
6870
|
+
async handleSimilar(manager) {
|
|
6871
|
+
const skillName = this.skillName;
|
|
6872
|
+
if (!skillName) {
|
|
6873
|
+
console.error(chalk30.red("Skill name required"));
|
|
6874
|
+
return 1;
|
|
6875
|
+
}
|
|
6876
|
+
const skills = await this.loadSkills();
|
|
6877
|
+
if (skills.length === 0) {
|
|
6878
|
+
console.log(
|
|
6879
|
+
chalk30.yellow('No skills found. Run "skillkit recommend --update" first.')
|
|
6880
|
+
);
|
|
6881
|
+
return 0;
|
|
6882
|
+
}
|
|
6883
|
+
const targetSkill = skills.find(
|
|
6884
|
+
(s) => s.name.toLowerCase() === skillName.toLowerCase()
|
|
6885
|
+
);
|
|
6886
|
+
if (!targetSkill) {
|
|
6887
|
+
console.error(chalk30.red(`Skill not found: ${skillName}`));
|
|
6888
|
+
return 1;
|
|
6889
|
+
}
|
|
6890
|
+
const spinner = ora7("Finding similar skills...").start();
|
|
6891
|
+
try {
|
|
6892
|
+
const results = await manager.findSimilar(targetSkill, skills, {
|
|
6893
|
+
limit: this.limit ? parseInt(this.limit, 10) : 10,
|
|
6894
|
+
minRelevance: this.minRelevance ? parseFloat(this.minRelevance) : 0.5,
|
|
6895
|
+
includeReasoning: !this.json
|
|
6896
|
+
});
|
|
6897
|
+
spinner.stop();
|
|
6898
|
+
if (this.json) {
|
|
6899
|
+
console.log(JSON.stringify(results, null, 2));
|
|
6900
|
+
return 0;
|
|
6901
|
+
}
|
|
6902
|
+
if (results.length === 0) {
|
|
6903
|
+
console.log(
|
|
6904
|
+
chalk30.yellow(`No similar skills found for "${skillName}"`)
|
|
6905
|
+
);
|
|
6906
|
+
return 0;
|
|
6907
|
+
}
|
|
6908
|
+
console.log(
|
|
6909
|
+
chalk30.cyan(`
|
|
6910
|
+
Skills similar to "${skillName}" (${results.length} found):
|
|
6911
|
+
`)
|
|
6912
|
+
);
|
|
6913
|
+
for (const result of results) {
|
|
6914
|
+
const score = Math.round(result.relevance * 100);
|
|
6915
|
+
const scoreColor = score >= 70 ? chalk30.green : score >= 50 ? chalk30.yellow : chalk30.dim;
|
|
6916
|
+
console.log(
|
|
6917
|
+
` ${scoreColor(`${score}%`)} ${chalk30.bold(result.skill.name)}`
|
|
6918
|
+
);
|
|
6919
|
+
if (result.skill.description) {
|
|
6920
|
+
console.log(` ${chalk30.dim(result.skill.description)}`);
|
|
6921
|
+
}
|
|
6922
|
+
if (result.reasoning) {
|
|
6923
|
+
console.log(` ${chalk30.dim("\u2192 " + result.reasoning)}`);
|
|
6924
|
+
}
|
|
6925
|
+
console.log();
|
|
6926
|
+
}
|
|
6927
|
+
return 0;
|
|
6928
|
+
} catch (error) {
|
|
6929
|
+
spinner.fail("Search failed");
|
|
6930
|
+
console.error(
|
|
6931
|
+
chalk30.red(error instanceof Error ? error.message : String(error))
|
|
6932
|
+
);
|
|
6933
|
+
return 1;
|
|
6934
|
+
}
|
|
6935
|
+
}
|
|
6936
|
+
async loadSkills() {
|
|
6937
|
+
const index = loadIndexFromCache2();
|
|
6938
|
+
if (!index) {
|
|
6939
|
+
return [];
|
|
6940
|
+
}
|
|
6941
|
+
return index.skills.map((s) => ({
|
|
6942
|
+
name: s.name,
|
|
6943
|
+
description: s.description,
|
|
6944
|
+
content: "",
|
|
6945
|
+
tags: s.tags,
|
|
6946
|
+
source: s.source
|
|
6947
|
+
}));
|
|
6948
|
+
}
|
|
6949
|
+
getAIConfig() {
|
|
6950
|
+
const apiKey = process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY;
|
|
6951
|
+
return {
|
|
6952
|
+
provider: process.env.ANTHROPIC_API_KEY ? "anthropic" : process.env.OPENAI_API_KEY ? "openai" : "none",
|
|
6953
|
+
apiKey,
|
|
6954
|
+
model: process.env.ANTHROPIC_API_KEY ? "claude-3-sonnet-20240229" : void 0,
|
|
6955
|
+
maxTokens: 4096,
|
|
6956
|
+
temperature: 0.7
|
|
6957
|
+
};
|
|
6958
|
+
}
|
|
6959
|
+
};
|
|
6960
|
+
|
|
6961
|
+
// src/commands/audit.ts
|
|
6962
|
+
import { Command as Command34, Option as Option33 } from "clipanion";
|
|
6963
|
+
import { resolve as resolve15 } from "path";
|
|
6964
|
+
import { promises as fs3 } from "fs";
|
|
6965
|
+
import path2 from "path";
|
|
6966
|
+
import chalk31 from "chalk";
|
|
6967
|
+
import { AuditLogger } from "@skillkit/core";
|
|
6968
|
+
var AuditCommand = class extends Command34 {
|
|
6969
|
+
static paths = [["audit"]];
|
|
6970
|
+
static usage = Command34.Usage({
|
|
6971
|
+
description: "View and manage audit logs",
|
|
6972
|
+
details: `
|
|
6973
|
+
The audit command provides access to the audit log system.
|
|
6974
|
+
All skill operations, team activities, and plugin actions are logged.
|
|
6975
|
+
|
|
6976
|
+
Subcommands:
|
|
6977
|
+
log View recent audit log entries
|
|
6978
|
+
export Export audit logs to file
|
|
6979
|
+
stats Show audit statistics
|
|
6980
|
+
clear Clear old audit entries
|
|
6981
|
+
`,
|
|
6982
|
+
examples: [
|
|
6983
|
+
["View recent logs", "$0 audit log"],
|
|
6984
|
+
["View logs with filters", "$0 audit log --type skill.install --limit 20"],
|
|
6985
|
+
["Export logs to JSON", "$0 audit export --format json --output audit.json"],
|
|
6986
|
+
["Show statistics", "$0 audit stats"],
|
|
6987
|
+
["Clear logs older than 30 days", "$0 audit clear --days 30"]
|
|
6988
|
+
]
|
|
6989
|
+
});
|
|
6990
|
+
subcommand = Option33.String({ required: true });
|
|
6991
|
+
// Query options
|
|
6992
|
+
type = Option33.Array("--type,-t", {
|
|
6993
|
+
description: "Filter by event type"
|
|
6994
|
+
});
|
|
6995
|
+
user = Option33.String("--user,-u", {
|
|
6996
|
+
description: "Filter by user"
|
|
6997
|
+
});
|
|
6998
|
+
resource = Option33.String("--resource,-r", {
|
|
6999
|
+
description: "Filter by resource"
|
|
7000
|
+
});
|
|
7001
|
+
success = Option33.Boolean("--success", {
|
|
7002
|
+
description: "Show only successful operations"
|
|
7003
|
+
});
|
|
7004
|
+
failed = Option33.Boolean("--failed", {
|
|
7005
|
+
description: "Show only failed operations"
|
|
7006
|
+
});
|
|
7007
|
+
since = Option33.String("--since", {
|
|
7008
|
+
description: "Show events since date (ISO 8601)"
|
|
7009
|
+
});
|
|
7010
|
+
until = Option33.String("--until", {
|
|
7011
|
+
description: "Show events until date (ISO 8601)"
|
|
7012
|
+
});
|
|
7013
|
+
limit = Option33.String("--limit,-l", {
|
|
7014
|
+
description: "Maximum number of entries"
|
|
7015
|
+
});
|
|
7016
|
+
// Export options
|
|
7017
|
+
format = Option33.String("--format,-f", {
|
|
7018
|
+
description: "Export format (json, csv, text)"
|
|
7019
|
+
});
|
|
7020
|
+
output = Option33.String("--output,-o", {
|
|
7021
|
+
description: "Output file path"
|
|
7022
|
+
});
|
|
7023
|
+
// Clear options
|
|
7024
|
+
days = Option33.String("--days", {
|
|
7025
|
+
description: "Clear entries older than N days"
|
|
7026
|
+
});
|
|
7027
|
+
// Common options
|
|
7028
|
+
json = Option33.Boolean("--json,-j", false, {
|
|
7029
|
+
description: "Output in JSON format"
|
|
7030
|
+
});
|
|
7031
|
+
projectPath = Option33.String("--path,-p", {
|
|
7032
|
+
description: "Project path (default: current directory)"
|
|
7033
|
+
});
|
|
7034
|
+
async execute() {
|
|
7035
|
+
const targetPath = resolve15(this.projectPath || process.cwd());
|
|
7036
|
+
const logDir = path2.join(targetPath, ".skillkit");
|
|
7037
|
+
const logger = new AuditLogger(logDir);
|
|
7038
|
+
try {
|
|
7039
|
+
switch (this.subcommand) {
|
|
7040
|
+
case "log":
|
|
7041
|
+
case "list":
|
|
7042
|
+
return await this.handleLog(logger);
|
|
7043
|
+
case "export":
|
|
7044
|
+
return await this.handleExport(logger);
|
|
7045
|
+
case "stats":
|
|
7046
|
+
return await this.handleStats(logger);
|
|
7047
|
+
case "clear":
|
|
7048
|
+
return await this.handleClear(logger);
|
|
7049
|
+
default:
|
|
7050
|
+
console.error(chalk31.red(`Unknown subcommand: ${this.subcommand}
|
|
7051
|
+
`));
|
|
7052
|
+
console.log("Valid subcommands: log, export, stats, clear");
|
|
7053
|
+
return 1;
|
|
7054
|
+
}
|
|
7055
|
+
} finally {
|
|
7056
|
+
await logger.destroy();
|
|
7057
|
+
}
|
|
7058
|
+
}
|
|
7059
|
+
async handleLog(logger) {
|
|
7060
|
+
const query = this.buildQuery();
|
|
7061
|
+
const events = await logger.query(query);
|
|
7062
|
+
if (this.json) {
|
|
7063
|
+
console.log(JSON.stringify(events, null, 2));
|
|
7064
|
+
return 0;
|
|
7065
|
+
}
|
|
7066
|
+
if (events.length === 0) {
|
|
7067
|
+
console.log(chalk31.yellow("No audit events found"));
|
|
7068
|
+
return 0;
|
|
7069
|
+
}
|
|
7070
|
+
console.log(chalk31.cyan(`
|
|
7071
|
+
Audit Log (${events.length} entries):
|
|
7072
|
+
`));
|
|
7073
|
+
for (const event of events) {
|
|
7074
|
+
const statusIcon = event.success ? chalk31.green("\u2713") : chalk31.red("\u2717");
|
|
7075
|
+
const timestamp = event.timestamp.toLocaleString();
|
|
7076
|
+
console.log(
|
|
7077
|
+
`${statusIcon} ${chalk31.dim(timestamp)} ${chalk31.bold(event.type)}`
|
|
7078
|
+
);
|
|
7079
|
+
console.log(
|
|
7080
|
+
` ${event.action} on ${chalk31.cyan(event.resource)}`
|
|
7081
|
+
);
|
|
7082
|
+
if (event.user) {
|
|
7083
|
+
console.log(` ${chalk31.dim("User:")} ${event.user}`);
|
|
7084
|
+
}
|
|
7085
|
+
if (event.duration) {
|
|
7086
|
+
console.log(` ${chalk31.dim("Duration:")} ${event.duration}ms`);
|
|
7087
|
+
}
|
|
7088
|
+
if (event.error) {
|
|
7089
|
+
console.log(` ${chalk31.red("Error:")} ${event.error}`);
|
|
7090
|
+
}
|
|
7091
|
+
if (event.details && Object.keys(event.details).length > 0) {
|
|
7092
|
+
console.log(` ${chalk31.dim("Details:")} ${JSON.stringify(event.details)}`);
|
|
7093
|
+
}
|
|
7094
|
+
console.log();
|
|
7095
|
+
}
|
|
7096
|
+
return 0;
|
|
7097
|
+
}
|
|
7098
|
+
async handleExport(logger) {
|
|
7099
|
+
const format = this.format || "json";
|
|
7100
|
+
const query = this.buildQuery();
|
|
7101
|
+
const content = await logger.export({ format, query });
|
|
7102
|
+
if (this.output) {
|
|
7103
|
+
const outputPath = resolve15(this.output);
|
|
7104
|
+
await fs3.writeFile(outputPath, content, "utf-8");
|
|
7105
|
+
console.log(chalk31.green(`\u2713 Exported audit log to: ${outputPath}`));
|
|
7106
|
+
} else {
|
|
7107
|
+
console.log(content);
|
|
7108
|
+
}
|
|
7109
|
+
return 0;
|
|
7110
|
+
}
|
|
7111
|
+
async handleStats(logger) {
|
|
7112
|
+
const stats = await logger.stats();
|
|
7113
|
+
if (this.json) {
|
|
7114
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
7115
|
+
return 0;
|
|
7116
|
+
}
|
|
7117
|
+
console.log(chalk31.cyan("\nAudit Statistics:\n"));
|
|
7118
|
+
console.log(`Total Events: ${chalk31.bold(stats.totalEvents.toString())}`);
|
|
7119
|
+
console.log(
|
|
7120
|
+
`Success Rate: ${chalk31.bold(`${(stats.successRate * 100).toFixed(1)}%`)}`
|
|
7121
|
+
);
|
|
7122
|
+
if (Object.keys(stats.eventsByType).length > 0) {
|
|
7123
|
+
console.log(chalk31.cyan("\nEvents by Type:"));
|
|
7124
|
+
const sorted = Object.entries(stats.eventsByType).sort(
|
|
7125
|
+
([, a], [, b]) => b - a
|
|
7126
|
+
);
|
|
7127
|
+
for (const [type, count] of sorted) {
|
|
7128
|
+
console.log(` ${type.padEnd(30)} ${count}`);
|
|
7129
|
+
}
|
|
7130
|
+
}
|
|
7131
|
+
if (stats.topResources.length > 0) {
|
|
7132
|
+
console.log(chalk31.cyan("\nTop Resources:"));
|
|
7133
|
+
for (const { resource, count } of stats.topResources) {
|
|
7134
|
+
console.log(` ${resource.padEnd(40)} ${count}`);
|
|
7135
|
+
}
|
|
7136
|
+
}
|
|
7137
|
+
if (stats.recentErrors.length > 0) {
|
|
7138
|
+
console.log(chalk31.cyan(`
|
|
7139
|
+
Recent Errors (${stats.recentErrors.length}):`));
|
|
7140
|
+
for (const error of stats.recentErrors.slice(0, 5)) {
|
|
7141
|
+
const timestamp = error.timestamp.toLocaleString();
|
|
7142
|
+
console.log(
|
|
7143
|
+
` ${chalk31.red("\u2717")} ${chalk31.dim(timestamp)} ${error.type}`
|
|
7144
|
+
);
|
|
7145
|
+
if (error.error) {
|
|
7146
|
+
console.log(` ${chalk31.dim(error.error)}`);
|
|
7147
|
+
}
|
|
7148
|
+
}
|
|
7149
|
+
}
|
|
7150
|
+
console.log();
|
|
7151
|
+
return 0;
|
|
7152
|
+
}
|
|
7153
|
+
async handleClear(logger) {
|
|
7154
|
+
if (!this.days) {
|
|
7155
|
+
console.error(chalk31.red("--days option required"));
|
|
7156
|
+
return 1;
|
|
7157
|
+
}
|
|
7158
|
+
const daysAgo = parseInt(this.days, 10);
|
|
7159
|
+
const cutoffDate = /* @__PURE__ */ new Date();
|
|
7160
|
+
cutoffDate.setDate(cutoffDate.getDate() - daysAgo);
|
|
7161
|
+
const cleared = await logger.clear(cutoffDate);
|
|
7162
|
+
console.log(
|
|
7163
|
+
chalk31.green(
|
|
7164
|
+
`\u2713 Cleared ${cleared} audit entries older than ${daysAgo} days`
|
|
7165
|
+
)
|
|
7166
|
+
);
|
|
7167
|
+
return 0;
|
|
7168
|
+
}
|
|
7169
|
+
buildQuery() {
|
|
7170
|
+
const query = {};
|
|
7171
|
+
if (this.type && this.type.length > 0) {
|
|
7172
|
+
query.types = this.type;
|
|
7173
|
+
}
|
|
7174
|
+
if (this.user) {
|
|
7175
|
+
query.user = this.user;
|
|
7176
|
+
}
|
|
7177
|
+
if (this.resource) {
|
|
7178
|
+
query.resource = this.resource;
|
|
7179
|
+
}
|
|
7180
|
+
if (this.success) {
|
|
7181
|
+
query.success = true;
|
|
7182
|
+
} else if (this.failed) {
|
|
7183
|
+
query.success = false;
|
|
7184
|
+
}
|
|
7185
|
+
if (this.since) {
|
|
7186
|
+
query.startDate = new Date(this.since);
|
|
7187
|
+
}
|
|
7188
|
+
if (this.until) {
|
|
7189
|
+
query.endDate = new Date(this.until);
|
|
7190
|
+
}
|
|
7191
|
+
if (this.limit) {
|
|
7192
|
+
query.limit = parseInt(this.limit, 10);
|
|
7193
|
+
} else {
|
|
7194
|
+
query.limit = 50;
|
|
7195
|
+
}
|
|
7196
|
+
return query;
|
|
7197
|
+
}
|
|
7198
|
+
};
|
|
5238
7199
|
export {
|
|
7200
|
+
AICommand,
|
|
7201
|
+
AuditCommand,
|
|
5239
7202
|
CICDCommand,
|
|
7203
|
+
CommandCmd,
|
|
5240
7204
|
ContextCommand,
|
|
5241
7205
|
CreateCommand,
|
|
5242
7206
|
DisableCommand,
|
|
5243
7207
|
EnableCommand,
|
|
7208
|
+
HookCommand,
|
|
5244
7209
|
InitCommand,
|
|
5245
7210
|
InstallCommand,
|
|
5246
7211
|
ListCommand,
|
|
5247
7212
|
MarketplaceCommand,
|
|
5248
7213
|
MemoryCommand,
|
|
7214
|
+
MethodologyCommand,
|
|
5249
7215
|
PauseCommand,
|
|
7216
|
+
PlanCommand,
|
|
5250
7217
|
PluginCommand,
|
|
5251
7218
|
ReadCommand,
|
|
5252
7219
|
RecommendCommand,
|