@skillkit/cli 1.4.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 +218 -1
- package/dist/index.js +3182 -16
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -2356,7 +2356,9 @@ import {
|
|
|
2356
2356
|
loadWorkflowByName,
|
|
2357
2357
|
loadWorkflow,
|
|
2358
2358
|
validateWorkflow,
|
|
2359
|
-
createWorkflowOrchestrator
|
|
2359
|
+
createWorkflowOrchestrator,
|
|
2360
|
+
createSkillExecutor,
|
|
2361
|
+
createSimulatedSkillExecutor
|
|
2360
2362
|
} from "@skillkit/core";
|
|
2361
2363
|
var WorkflowRunCommand = class extends Command18 {
|
|
2362
2364
|
static paths = [["workflow", "run"], ["wf", "run"]];
|
|
@@ -2400,6 +2402,14 @@ var WorkflowRunCommand = class extends Command18 {
|
|
|
2400
2402
|
projectPath = Option17.String("--path,-p", {
|
|
2401
2403
|
description: "Project path (default: current directory)"
|
|
2402
2404
|
});
|
|
2405
|
+
// Agent to use for execution
|
|
2406
|
+
agent = Option17.String("--agent,-a", {
|
|
2407
|
+
description: "Agent to use for skill execution (e.g., claude-code, codex)"
|
|
2408
|
+
});
|
|
2409
|
+
// Simulate execution (for testing)
|
|
2410
|
+
simulate = Option17.Boolean("--simulate", false, {
|
|
2411
|
+
description: "Simulate execution without running skills (for testing)"
|
|
2412
|
+
});
|
|
2403
2413
|
async execute() {
|
|
2404
2414
|
const targetPath = resolve6(this.projectPath || process.cwd());
|
|
2405
2415
|
let workflow;
|
|
@@ -2440,11 +2450,40 @@ var WorkflowRunCommand = class extends Command18 {
|
|
|
2440
2450
|
console.log();
|
|
2441
2451
|
const spinner = ora4();
|
|
2442
2452
|
let currentWave = -1;
|
|
2453
|
+
const skillExecutor = this.simulate ? createSimulatedSkillExecutor({
|
|
2454
|
+
delay: 500,
|
|
2455
|
+
onExecute: (skillName) => {
|
|
2456
|
+
if (this.verbose && !this.json) {
|
|
2457
|
+
console.log(chalk17.dim(` [Simulated] ${skillName}`));
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
}) : createSkillExecutor({
|
|
2461
|
+
projectPath: targetPath,
|
|
2462
|
+
preferredAgent: this.agent,
|
|
2463
|
+
fallbackToAvailable: true,
|
|
2464
|
+
onExecutionEvent: (event) => {
|
|
2465
|
+
if (this.verbose && !this.json) {
|
|
2466
|
+
switch (event.type) {
|
|
2467
|
+
case "skill_found":
|
|
2468
|
+
console.log(chalk17.dim(` Found: ${event.message}`));
|
|
2469
|
+
break;
|
|
2470
|
+
case "skill_not_found":
|
|
2471
|
+
console.log(chalk17.red(` Not found: ${event.skillName}`));
|
|
2472
|
+
break;
|
|
2473
|
+
case "agent_selected":
|
|
2474
|
+
console.log(chalk17.dim(` Agent: ${event.agent}`));
|
|
2475
|
+
break;
|
|
2476
|
+
case "execution_complete":
|
|
2477
|
+
if (!event.success) {
|
|
2478
|
+
console.log(chalk17.red(` Error: ${event.error}`));
|
|
2479
|
+
}
|
|
2480
|
+
break;
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
});
|
|
2443
2485
|
const orchestrator = createWorkflowOrchestrator(
|
|
2444
|
-
|
|
2445
|
-
await new Promise((resolve11) => setTimeout(resolve11, 500));
|
|
2446
|
-
return { success: true };
|
|
2447
|
-
},
|
|
2486
|
+
skillExecutor,
|
|
2448
2487
|
(event) => {
|
|
2449
2488
|
if (this.json) return;
|
|
2450
2489
|
switch (event.type) {
|
|
@@ -3763,13 +3802,13 @@ ${learning.title}
|
|
|
3763
3802
|
return 0;
|
|
3764
3803
|
}
|
|
3765
3804
|
const outputPath = this.output || `.skillkit/exports/${skillName}/SKILL.md`;
|
|
3766
|
-
const { dirname:
|
|
3767
|
-
const { existsSync:
|
|
3768
|
-
const outputDir =
|
|
3769
|
-
if (!
|
|
3770
|
-
|
|
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
|
+
mkdirSync7(outputDir, { recursive: true });
|
|
3771
3810
|
}
|
|
3772
|
-
|
|
3811
|
+
writeFileSync6(outputPath, skillContent, "utf-8");
|
|
3773
3812
|
console.log(chalk23.green(`\u2713 Exported learning as skill: ${skillName}`));
|
|
3774
3813
|
console.log(chalk23.gray(` Path: ${outputPath}`));
|
|
3775
3814
|
return 0;
|
|
@@ -3784,10 +3823,10 @@ ${learning.title}
|
|
|
3784
3823
|
console.log(chalk23.gray("Usage: skillkit memory import --input <path>"));
|
|
3785
3824
|
return 1;
|
|
3786
3825
|
}
|
|
3787
|
-
const { existsSync:
|
|
3788
|
-
const { resolve:
|
|
3789
|
-
const fullPath =
|
|
3790
|
-
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)) {
|
|
3791
3830
|
console.error(chalk23.red(`File not found: ${fullPath}`));
|
|
3792
3831
|
return 1;
|
|
3793
3832
|
}
|
|
@@ -3797,7 +3836,7 @@ ${learning.title}
|
|
|
3797
3836
|
this.global ? void 0 : projectPath
|
|
3798
3837
|
);
|
|
3799
3838
|
try {
|
|
3800
|
-
const content =
|
|
3839
|
+
const content = readFileSync7(fullPath, "utf-8");
|
|
3801
3840
|
const { parse: parseYaml2 } = await import("yaml");
|
|
3802
3841
|
const data = parseYaml2(content);
|
|
3803
3842
|
if (!data.learnings || !Array.isArray(data.learnings)) {
|
|
@@ -4040,24 +4079,3151 @@ ${learning.title}
|
|
|
4040
4079
|
return lines.join("\n");
|
|
4041
4080
|
}
|
|
4042
4081
|
};
|
|
4082
|
+
|
|
4083
|
+
// src/commands/settings.ts
|
|
4084
|
+
import { Command as Command25, Option as Option24 } from "clipanion";
|
|
4085
|
+
import chalk24 from "chalk";
|
|
4086
|
+
import { loadConfig as loadConfig3, saveConfig } from "@skillkit/core";
|
|
4087
|
+
var VALID_AGENTS = [
|
|
4088
|
+
"claude-code",
|
|
4089
|
+
"cursor",
|
|
4090
|
+
"codex",
|
|
4091
|
+
"gemini-cli",
|
|
4092
|
+
"opencode",
|
|
4093
|
+
"antigravity",
|
|
4094
|
+
"amp",
|
|
4095
|
+
"clawdbot",
|
|
4096
|
+
"droid",
|
|
4097
|
+
"github-copilot",
|
|
4098
|
+
"goose",
|
|
4099
|
+
"kilo",
|
|
4100
|
+
"kiro-cli",
|
|
4101
|
+
"roo",
|
|
4102
|
+
"trae",
|
|
4103
|
+
"windsurf",
|
|
4104
|
+
"universal"
|
|
4105
|
+
];
|
|
4106
|
+
var SettingsCommand = class extends Command25 {
|
|
4107
|
+
static paths = [["settings"], ["config"]];
|
|
4108
|
+
static usage = Command25.Usage({
|
|
4109
|
+
description: "View and modify SkillKit settings",
|
|
4110
|
+
details: `
|
|
4111
|
+
View or modify SkillKit configuration. Settings are stored in skillkit.yaml.
|
|
4112
|
+
|
|
4113
|
+
Without arguments, shows all current settings.
|
|
4114
|
+
With --set, modifies a specific setting.
|
|
4115
|
+
`,
|
|
4116
|
+
examples: [
|
|
4117
|
+
["Show all settings", "$0 settings"],
|
|
4118
|
+
["Set default agent", "$0 settings --set agent=claude-code"],
|
|
4119
|
+
["Enable auto-sync", "$0 settings --set autoSync=true"],
|
|
4120
|
+
["Set cache directory", "$0 settings --set cacheDir=~/.cache/skillkit"],
|
|
4121
|
+
["Show settings as JSON", "$0 settings --json"],
|
|
4122
|
+
["Save to global config", "$0 settings --set agent=cursor --global"]
|
|
4123
|
+
]
|
|
4124
|
+
});
|
|
4125
|
+
// Set a setting
|
|
4126
|
+
set = Option24.String("--set,-s", {
|
|
4127
|
+
description: "Set a config value (key=value)"
|
|
4128
|
+
});
|
|
4129
|
+
// Get a specific setting
|
|
4130
|
+
get = Option24.String("--get,-g", {
|
|
4131
|
+
description: "Get a specific setting value"
|
|
4132
|
+
});
|
|
4133
|
+
// JSON output
|
|
4134
|
+
json = Option24.Boolean("--json,-j", false, {
|
|
4135
|
+
description: "Output as JSON"
|
|
4136
|
+
});
|
|
4137
|
+
// Global config
|
|
4138
|
+
global = Option24.Boolean("--global", false, {
|
|
4139
|
+
description: "Use global config (~/.config/skillkit/)"
|
|
4140
|
+
});
|
|
4141
|
+
// Reset to defaults
|
|
4142
|
+
reset = Option24.Boolean("--reset", false, {
|
|
4143
|
+
description: "Reset all settings to defaults"
|
|
4144
|
+
});
|
|
4145
|
+
async execute() {
|
|
4146
|
+
const config = loadConfig3(this.global);
|
|
4147
|
+
if (this.reset) {
|
|
4148
|
+
const defaultConfig = {
|
|
4149
|
+
version: 1,
|
|
4150
|
+
agent: "universal",
|
|
4151
|
+
autoSync: true
|
|
4152
|
+
};
|
|
4153
|
+
saveConfig(defaultConfig, this.global);
|
|
4154
|
+
console.log(chalk24.green("Settings reset to defaults"));
|
|
4155
|
+
return 0;
|
|
4156
|
+
}
|
|
4157
|
+
if (this.get) {
|
|
4158
|
+
const knownKeys = [
|
|
4159
|
+
"agent",
|
|
4160
|
+
"autoSync",
|
|
4161
|
+
"cacheDir",
|
|
4162
|
+
"skillsDir",
|
|
4163
|
+
"enabledSkills",
|
|
4164
|
+
"disabledSkills",
|
|
4165
|
+
"marketplaceSources",
|
|
4166
|
+
"defaultTimeout"
|
|
4167
|
+
];
|
|
4168
|
+
if (!knownKeys.includes(this.get)) {
|
|
4169
|
+
console.error(chalk24.red(`Unknown setting: ${this.get}`));
|
|
4170
|
+
return 1;
|
|
4171
|
+
}
|
|
4172
|
+
const value = this.getConfigValue(config, this.get);
|
|
4173
|
+
if (this.json) {
|
|
4174
|
+
console.log(JSON.stringify({ [this.get]: value ?? null }));
|
|
4175
|
+
} else {
|
|
4176
|
+
console.log(value ?? "(not set)");
|
|
4177
|
+
}
|
|
4178
|
+
return 0;
|
|
4179
|
+
}
|
|
4180
|
+
if (this.set) {
|
|
4181
|
+
const [key, ...valueParts] = this.set.split("=");
|
|
4182
|
+
const value = valueParts.join("=");
|
|
4183
|
+
if (!key || !this.set.includes("=")) {
|
|
4184
|
+
console.error(chalk24.red("Invalid format. Use: --set key=value"));
|
|
4185
|
+
return 1;
|
|
4186
|
+
}
|
|
4187
|
+
const result = this.setConfigValue(config, key, value);
|
|
4188
|
+
if (!result.success) {
|
|
4189
|
+
console.error(chalk24.red(result.error));
|
|
4190
|
+
return 1;
|
|
4191
|
+
}
|
|
4192
|
+
saveConfig(config, this.global);
|
|
4193
|
+
console.log(chalk24.green(`${key} = ${value}`));
|
|
4194
|
+
return 0;
|
|
4195
|
+
}
|
|
4196
|
+
if (this.json) {
|
|
4197
|
+
console.log(JSON.stringify(config, null, 2));
|
|
4198
|
+
} else {
|
|
4199
|
+
console.log(chalk24.cyan("SkillKit Settings"));
|
|
4200
|
+
console.log(chalk24.dim("\u2500".repeat(40)));
|
|
4201
|
+
console.log();
|
|
4202
|
+
const settings = [
|
|
4203
|
+
{ key: "agent", label: "Default Agent", value: config.agent },
|
|
4204
|
+
{ key: "autoSync", label: "Auto Sync", value: config.autoSync ? "enabled" : "disabled" },
|
|
4205
|
+
{ key: "cacheDir", label: "Cache Dir", value: config.cacheDir || "~/.skillkit/cache" },
|
|
4206
|
+
{ key: "skillsDir", label: "Skills Dir", value: config.skillsDir || "(default)" },
|
|
4207
|
+
{ key: "defaultTimeout", label: "Timeout", value: config.defaultTimeout ? `${config.defaultTimeout}ms` : "(default)" }
|
|
4208
|
+
];
|
|
4209
|
+
for (const setting of settings) {
|
|
4210
|
+
console.log(` ${chalk24.white(setting.label.padEnd(14))} ${chalk24.dim(setting.value)}`);
|
|
4211
|
+
}
|
|
4212
|
+
if (config.enabledSkills?.length) {
|
|
4213
|
+
console.log();
|
|
4214
|
+
console.log(` ${chalk24.white("Enabled Skills".padEnd(14))} ${chalk24.dim(config.enabledSkills.join(", "))}`);
|
|
4215
|
+
}
|
|
4216
|
+
if (config.disabledSkills?.length) {
|
|
4217
|
+
console.log(` ${chalk24.white("Disabled Skills".padEnd(14))} ${chalk24.dim(config.disabledSkills.join(", "))}`);
|
|
4218
|
+
}
|
|
4219
|
+
if (config.marketplaceSources?.length) {
|
|
4220
|
+
console.log(` ${chalk24.white("Marketplaces".padEnd(14))} ${chalk24.dim(config.marketplaceSources.join(", "))}`);
|
|
4221
|
+
}
|
|
4222
|
+
console.log();
|
|
4223
|
+
console.log(chalk24.dim("Use --set key=value to modify settings"));
|
|
4224
|
+
console.log(chalk24.dim("Available keys: agent, autoSync, cacheDir, skillsDir, defaultTimeout"));
|
|
4225
|
+
}
|
|
4226
|
+
return 0;
|
|
4227
|
+
}
|
|
4228
|
+
getConfigValue(config, key) {
|
|
4229
|
+
switch (key) {
|
|
4230
|
+
case "agent":
|
|
4231
|
+
return config.agent;
|
|
4232
|
+
case "autoSync":
|
|
4233
|
+
return config.autoSync;
|
|
4234
|
+
case "cacheDir":
|
|
4235
|
+
return config.cacheDir;
|
|
4236
|
+
case "skillsDir":
|
|
4237
|
+
return config.skillsDir;
|
|
4238
|
+
case "enabledSkills":
|
|
4239
|
+
return config.enabledSkills;
|
|
4240
|
+
case "disabledSkills":
|
|
4241
|
+
return config.disabledSkills;
|
|
4242
|
+
case "marketplaceSources":
|
|
4243
|
+
return config.marketplaceSources;
|
|
4244
|
+
case "defaultTimeout":
|
|
4245
|
+
return config.defaultTimeout;
|
|
4246
|
+
default:
|
|
4247
|
+
return void 0;
|
|
4248
|
+
}
|
|
4249
|
+
}
|
|
4250
|
+
setConfigValue(config, key, value) {
|
|
4251
|
+
switch (key) {
|
|
4252
|
+
case "agent":
|
|
4253
|
+
if (!VALID_AGENTS.includes(value)) {
|
|
4254
|
+
return {
|
|
4255
|
+
success: false,
|
|
4256
|
+
error: `Invalid agent. Valid options: ${VALID_AGENTS.join(", ")}`
|
|
4257
|
+
};
|
|
4258
|
+
}
|
|
4259
|
+
config.agent = value;
|
|
4260
|
+
break;
|
|
4261
|
+
case "autoSync":
|
|
4262
|
+
if (!["true", "false", "enabled", "disabled"].includes(value.toLowerCase())) {
|
|
4263
|
+
return { success: false, error: "autoSync must be true/false or enabled/disabled" };
|
|
4264
|
+
}
|
|
4265
|
+
config.autoSync = value.toLowerCase() === "true" || value.toLowerCase() === "enabled";
|
|
4266
|
+
break;
|
|
4267
|
+
case "cacheDir":
|
|
4268
|
+
config.cacheDir = value || void 0;
|
|
4269
|
+
break;
|
|
4270
|
+
case "skillsDir":
|
|
4271
|
+
config.skillsDir = value || void 0;
|
|
4272
|
+
break;
|
|
4273
|
+
case "defaultTimeout": {
|
|
4274
|
+
const timeout = parseInt(value, 10);
|
|
4275
|
+
if (isNaN(timeout) || timeout <= 0) {
|
|
4276
|
+
return { success: false, error: "defaultTimeout must be a positive number (milliseconds)" };
|
|
4277
|
+
}
|
|
4278
|
+
config.defaultTimeout = timeout;
|
|
4279
|
+
break;
|
|
4280
|
+
}
|
|
4281
|
+
default:
|
|
4282
|
+
return { success: false, error: `Unknown setting: ${key}` };
|
|
4283
|
+
}
|
|
4284
|
+
return { success: true };
|
|
4285
|
+
}
|
|
4286
|
+
};
|
|
4287
|
+
|
|
4288
|
+
// src/commands/cicd.ts
|
|
4289
|
+
import { Command as Command26, Option as Option25 } from "clipanion";
|
|
4290
|
+
import chalk25 from "chalk";
|
|
4291
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5, readFileSync as readFileSync6 } from "fs";
|
|
4292
|
+
import { join as join8 } from "path";
|
|
4293
|
+
var GITHUB_ACTIONS_WORKFLOW = `name: SkillKit CI
|
|
4294
|
+
|
|
4295
|
+
on:
|
|
4296
|
+
push:
|
|
4297
|
+
branches: [main, master]
|
|
4298
|
+
paths:
|
|
4299
|
+
- '.skillkit/**'
|
|
4300
|
+
- 'skills/**'
|
|
4301
|
+
- '.claude/skills/**'
|
|
4302
|
+
- '.cursor/skills/**'
|
|
4303
|
+
pull_request:
|
|
4304
|
+
branches: [main, master]
|
|
4305
|
+
paths:
|
|
4306
|
+
- '.skillkit/**'
|
|
4307
|
+
- 'skills/**'
|
|
4308
|
+
- '.claude/skills/**'
|
|
4309
|
+
- '.cursor/skills/**'
|
|
4310
|
+
|
|
4311
|
+
jobs:
|
|
4312
|
+
validate:
|
|
4313
|
+
name: Validate Skills
|
|
4314
|
+
runs-on: ubuntu-latest
|
|
4315
|
+
steps:
|
|
4316
|
+
- uses: actions/checkout@v4
|
|
4317
|
+
|
|
4318
|
+
- uses: actions/setup-node@v4
|
|
4319
|
+
with:
|
|
4320
|
+
node-version: '20'
|
|
4321
|
+
|
|
4322
|
+
- name: Install SkillKit
|
|
4323
|
+
run: npm install -g skillkit
|
|
4324
|
+
|
|
4325
|
+
- name: Validate Skills
|
|
4326
|
+
run: skillkit validate --all
|
|
4327
|
+
|
|
4328
|
+
- name: List Skills
|
|
4329
|
+
run: skillkit list
|
|
4330
|
+
|
|
4331
|
+
test:
|
|
4332
|
+
name: Test Skills
|
|
4333
|
+
runs-on: ubuntu-latest
|
|
4334
|
+
needs: validate
|
|
4335
|
+
steps:
|
|
4336
|
+
- uses: actions/checkout@v4
|
|
4337
|
+
|
|
4338
|
+
- uses: actions/setup-node@v4
|
|
4339
|
+
with:
|
|
4340
|
+
node-version: '20'
|
|
4341
|
+
|
|
4342
|
+
- name: Install SkillKit
|
|
4343
|
+
run: npm install -g skillkit
|
|
4344
|
+
|
|
4345
|
+
- name: Run Skill Tests
|
|
4346
|
+
run: skillkit test --all
|
|
4347
|
+
continue-on-error: true
|
|
4348
|
+
|
|
4349
|
+
sync:
|
|
4350
|
+
name: Sync Skills
|
|
4351
|
+
runs-on: ubuntu-latest
|
|
4352
|
+
needs: validate
|
|
4353
|
+
if: github.event_name == 'push'
|
|
4354
|
+
steps:
|
|
4355
|
+
- uses: actions/checkout@v4
|
|
4356
|
+
|
|
4357
|
+
- uses: actions/setup-node@v4
|
|
4358
|
+
with:
|
|
4359
|
+
node-version: '20'
|
|
4360
|
+
|
|
4361
|
+
- name: Install SkillKit
|
|
4362
|
+
run: npm install -g skillkit
|
|
4363
|
+
|
|
4364
|
+
- name: Sync Skills to All Agents
|
|
4365
|
+
run: skillkit sync --yes
|
|
4366
|
+
`;
|
|
4367
|
+
var GITLAB_CI = `stages:
|
|
4368
|
+
- validate
|
|
4369
|
+
- test
|
|
4370
|
+
- sync
|
|
4371
|
+
|
|
4372
|
+
variables:
|
|
4373
|
+
NODE_VERSION: "20"
|
|
4374
|
+
|
|
4375
|
+
.skillkit-base:
|
|
4376
|
+
image: node:\${NODE_VERSION}
|
|
4377
|
+
before_script:
|
|
4378
|
+
- npm install -g skillkit
|
|
4379
|
+
rules:
|
|
4380
|
+
- changes:
|
|
4381
|
+
- .skillkit/**/*
|
|
4382
|
+
- skills/**/*
|
|
4383
|
+
- .claude/skills/**/*
|
|
4384
|
+
- .cursor/skills/**/*
|
|
4385
|
+
|
|
4386
|
+
validate:
|
|
4387
|
+
extends: .skillkit-base
|
|
4388
|
+
stage: validate
|
|
4389
|
+
script:
|
|
4390
|
+
- skillkit validate --all
|
|
4391
|
+
- skillkit list
|
|
4392
|
+
|
|
4393
|
+
test:
|
|
4394
|
+
extends: .skillkit-base
|
|
4395
|
+
stage: test
|
|
4396
|
+
script:
|
|
4397
|
+
- skillkit test --all
|
|
4398
|
+
allow_failure: true
|
|
4399
|
+
needs:
|
|
4400
|
+
- validate
|
|
4401
|
+
|
|
4402
|
+
sync:
|
|
4403
|
+
extends: .skillkit-base
|
|
4404
|
+
stage: sync
|
|
4405
|
+
script:
|
|
4406
|
+
- skillkit sync --yes
|
|
4407
|
+
needs:
|
|
4408
|
+
- validate
|
|
4409
|
+
rules:
|
|
4410
|
+
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master"
|
|
4411
|
+
changes:
|
|
4412
|
+
- .skillkit/**/*
|
|
4413
|
+
- skills/**/*
|
|
4414
|
+
- .claude/skills/**/*
|
|
4415
|
+
- .cursor/skills/**/*
|
|
4416
|
+
`;
|
|
4417
|
+
var CIRCLECI_CONFIG = `version: 2.1
|
|
4418
|
+
|
|
4419
|
+
executors:
|
|
4420
|
+
node:
|
|
4421
|
+
docker:
|
|
4422
|
+
- image: cimg/node:20.0
|
|
4423
|
+
|
|
4424
|
+
jobs:
|
|
4425
|
+
validate:
|
|
4426
|
+
executor: node
|
|
4427
|
+
steps:
|
|
4428
|
+
- checkout
|
|
4429
|
+
- run:
|
|
4430
|
+
name: Install SkillKit
|
|
4431
|
+
command: npm install -g skillkit
|
|
4432
|
+
- run:
|
|
4433
|
+
name: Validate Skills
|
|
4434
|
+
command: skillkit validate --all
|
|
4435
|
+
- run:
|
|
4436
|
+
name: List Skills
|
|
4437
|
+
command: skillkit list
|
|
4438
|
+
|
|
4439
|
+
test:
|
|
4440
|
+
executor: node
|
|
4441
|
+
steps:
|
|
4442
|
+
- checkout
|
|
4443
|
+
- run:
|
|
4444
|
+
name: Install SkillKit
|
|
4445
|
+
command: npm install -g skillkit
|
|
4446
|
+
- run:
|
|
4447
|
+
name: Run Skill Tests
|
|
4448
|
+
command: skillkit test --all || true
|
|
4449
|
+
|
|
4450
|
+
sync:
|
|
4451
|
+
executor: node
|
|
4452
|
+
steps:
|
|
4453
|
+
- checkout
|
|
4454
|
+
- run:
|
|
4455
|
+
name: Install SkillKit
|
|
4456
|
+
command: npm install -g skillkit
|
|
4457
|
+
- run:
|
|
4458
|
+
name: Sync Skills
|
|
4459
|
+
command: skillkit sync --yes
|
|
4460
|
+
|
|
4461
|
+
workflows:
|
|
4462
|
+
skillkit:
|
|
4463
|
+
jobs:
|
|
4464
|
+
- validate:
|
|
4465
|
+
filters:
|
|
4466
|
+
branches:
|
|
4467
|
+
only:
|
|
4468
|
+
- main
|
|
4469
|
+
- master
|
|
4470
|
+
- test:
|
|
4471
|
+
requires:
|
|
4472
|
+
- validate
|
|
4473
|
+
- sync:
|
|
4474
|
+
requires:
|
|
4475
|
+
- validate
|
|
4476
|
+
filters:
|
|
4477
|
+
branches:
|
|
4478
|
+
only:
|
|
4479
|
+
- main
|
|
4480
|
+
- master
|
|
4481
|
+
`;
|
|
4482
|
+
var CICDCommand = class extends Command26 {
|
|
4483
|
+
static paths = [["cicd", "init"]];
|
|
4484
|
+
static usage = Command26.Usage({
|
|
4485
|
+
description: "Initialize CI/CD workflows for skill validation",
|
|
4486
|
+
details: `
|
|
4487
|
+
The cicd command sets up continuous integration workflows to automatically
|
|
4488
|
+
validate and test skills on every push or pull request.
|
|
4489
|
+
|
|
4490
|
+
Supported providers: github, gitlab, circleci
|
|
4491
|
+
`,
|
|
4492
|
+
examples: [
|
|
4493
|
+
["Initialize GitHub Actions workflow", "$0 cicd init"],
|
|
4494
|
+
["Initialize GitLab CI", "$0 cicd init --provider gitlab"],
|
|
4495
|
+
["Initialize CircleCI", "$0 cicd init --provider circleci"],
|
|
4496
|
+
["Initialize all providers", "$0 cicd init --all"],
|
|
4497
|
+
["Force overwrite existing files", "$0 cicd init --force"]
|
|
4498
|
+
]
|
|
4499
|
+
});
|
|
4500
|
+
// Provider selection
|
|
4501
|
+
provider = Option25.String("--provider,-p", "github", {
|
|
4502
|
+
description: "CI/CD provider (github, gitlab, circleci)"
|
|
4503
|
+
});
|
|
4504
|
+
// Generate for all providers
|
|
4505
|
+
all = Option25.Boolean("--all,-a", false, {
|
|
4506
|
+
description: "Generate workflows for all supported providers"
|
|
4507
|
+
});
|
|
4508
|
+
// Force overwrite
|
|
4509
|
+
force = Option25.Boolean("--force,-f", false, {
|
|
4510
|
+
description: "Overwrite existing workflow files"
|
|
4511
|
+
});
|
|
4512
|
+
// Project path
|
|
4513
|
+
targetPath = Option25.String("--path", {
|
|
4514
|
+
description: "Project path (default: current directory)"
|
|
4515
|
+
});
|
|
4516
|
+
async execute() {
|
|
4517
|
+
const projectPath = this.targetPath || process.cwd();
|
|
4518
|
+
const providers = this.all ? ["github", "gitlab", "circleci"] : [this.provider];
|
|
4519
|
+
console.log(chalk25.cyan("Initializing CI/CD workflows..."));
|
|
4520
|
+
console.log();
|
|
4521
|
+
let success = true;
|
|
4522
|
+
let created = false;
|
|
4523
|
+
for (const provider of providers) {
|
|
4524
|
+
const result = this.createWorkflow(projectPath, provider);
|
|
4525
|
+
if (result.skipped) {
|
|
4526
|
+
console.log(chalk25.yellow(` ${result.message}`));
|
|
4527
|
+
continue;
|
|
4528
|
+
}
|
|
4529
|
+
if (!result.success) {
|
|
4530
|
+
success = false;
|
|
4531
|
+
console.error(chalk25.red(` ${result.message}`));
|
|
4532
|
+
continue;
|
|
4533
|
+
}
|
|
4534
|
+
created = true;
|
|
4535
|
+
console.log(chalk25.green(` \u2713 ${result.message}`));
|
|
4536
|
+
}
|
|
4537
|
+
if (success) {
|
|
4538
|
+
console.log();
|
|
4539
|
+
console.log(
|
|
4540
|
+
created ? chalk25.green("CI/CD workflows initialized successfully!") : chalk25.yellow("CI/CD workflows already exist; nothing to do.")
|
|
4541
|
+
);
|
|
4542
|
+
console.log();
|
|
4543
|
+
if (created) {
|
|
4544
|
+
console.log(chalk25.dim("The workflows will run on push/PR to validate your skills."));
|
|
4545
|
+
console.log(chalk25.dim("Commit the generated files to enable CI/CD."));
|
|
4546
|
+
}
|
|
4547
|
+
}
|
|
4548
|
+
return success ? 0 : 1;
|
|
4549
|
+
}
|
|
4550
|
+
createWorkflow(projectPath, provider) {
|
|
4551
|
+
switch (provider) {
|
|
4552
|
+
case "github":
|
|
4553
|
+
return this.createGitHubActions(projectPath);
|
|
4554
|
+
case "gitlab":
|
|
4555
|
+
return this.createGitLabCI(projectPath);
|
|
4556
|
+
case "circleci":
|
|
4557
|
+
return this.createCircleCI(projectPath);
|
|
4558
|
+
default:
|
|
4559
|
+
return { success: false, message: `Unknown provider: ${provider}` };
|
|
4560
|
+
}
|
|
4561
|
+
}
|
|
4562
|
+
createGitHubActions(projectPath) {
|
|
4563
|
+
const workflowDir = join8(projectPath, ".github", "workflows");
|
|
4564
|
+
const workflowFile = join8(workflowDir, "skillkit.yml");
|
|
4565
|
+
if (existsSync11(workflowFile) && !this.force) {
|
|
4566
|
+
return {
|
|
4567
|
+
success: true,
|
|
4568
|
+
message: `GitHub Actions workflow already exists (use --force to overwrite)`,
|
|
4569
|
+
skipped: true
|
|
4570
|
+
};
|
|
4571
|
+
}
|
|
4572
|
+
try {
|
|
4573
|
+
mkdirSync5(workflowDir, { recursive: true });
|
|
4574
|
+
writeFileSync5(workflowFile, GITHUB_ACTIONS_WORKFLOW);
|
|
4575
|
+
return { success: true, message: `Created ${workflowFile}` };
|
|
4576
|
+
} catch (error) {
|
|
4577
|
+
return { success: false, message: `Failed to create GitHub workflow: ${error}` };
|
|
4578
|
+
}
|
|
4579
|
+
}
|
|
4580
|
+
createGitLabCI(projectPath) {
|
|
4581
|
+
const ciFile = join8(projectPath, ".gitlab-ci.yml");
|
|
4582
|
+
if (existsSync11(ciFile) && !this.force) {
|
|
4583
|
+
try {
|
|
4584
|
+
const content = readFileSync6(ciFile, "utf-8");
|
|
4585
|
+
if (content.includes("skillkit")) {
|
|
4586
|
+
return {
|
|
4587
|
+
success: true,
|
|
4588
|
+
message: `.gitlab-ci.yml already contains SkillKit config (use --force to overwrite)`,
|
|
4589
|
+
skipped: true
|
|
4590
|
+
};
|
|
4591
|
+
}
|
|
4592
|
+
return {
|
|
4593
|
+
success: true,
|
|
4594
|
+
message: `.gitlab-ci.yml already exists (use --force to overwrite)`,
|
|
4595
|
+
skipped: true
|
|
4596
|
+
};
|
|
4597
|
+
} catch (error) {
|
|
4598
|
+
return {
|
|
4599
|
+
success: false,
|
|
4600
|
+
message: `Failed to read .gitlab-ci.yml: ${error}`
|
|
4601
|
+
};
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
try {
|
|
4605
|
+
writeFileSync5(ciFile, GITLAB_CI);
|
|
4606
|
+
return { success: true, message: `Created ${ciFile}` };
|
|
4607
|
+
} catch (error) {
|
|
4608
|
+
return { success: false, message: `Failed to create GitLab CI config: ${error}` };
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
createCircleCI(projectPath) {
|
|
4612
|
+
const circleDir = join8(projectPath, ".circleci");
|
|
4613
|
+
const configFile = join8(circleDir, "config.yml");
|
|
4614
|
+
if (existsSync11(configFile) && !this.force) {
|
|
4615
|
+
return {
|
|
4616
|
+
success: true,
|
|
4617
|
+
message: `CircleCI config already exists (use --force to overwrite)`,
|
|
4618
|
+
skipped: true
|
|
4619
|
+
};
|
|
4620
|
+
}
|
|
4621
|
+
try {
|
|
4622
|
+
mkdirSync5(circleDir, { recursive: true });
|
|
4623
|
+
writeFileSync5(configFile, CIRCLECI_CONFIG);
|
|
4624
|
+
return { success: true, message: `Created ${configFile}` };
|
|
4625
|
+
} catch (error) {
|
|
4626
|
+
return { success: false, message: `Failed to create CircleCI config: ${error}` };
|
|
4627
|
+
}
|
|
4628
|
+
}
|
|
4629
|
+
};
|
|
4630
|
+
|
|
4631
|
+
// src/commands/team.ts
|
|
4632
|
+
import { Command as Command27, Option as Option26 } from "clipanion";
|
|
4633
|
+
import chalk26 from "chalk";
|
|
4634
|
+
import { createTeamManager, createSkillBundle, exportBundle, importBundle } from "@skillkit/core";
|
|
4635
|
+
import { join as join9 } from "path";
|
|
4636
|
+
var TeamCommand = class extends Command27 {
|
|
4637
|
+
static paths = [["team"]];
|
|
4638
|
+
static usage = Command27.Usage({
|
|
4639
|
+
description: "Manage team skill sharing and collaboration",
|
|
4640
|
+
examples: [
|
|
4641
|
+
["Initialize team", '$0 team init --name "My Team" --registry https://github.com/myteam/skills'],
|
|
4642
|
+
["Share a skill", "$0 team share --name my-skill"],
|
|
4643
|
+
["Import a skill", "$0 team import --name shared-skill"],
|
|
4644
|
+
["List shared skills", "$0 team list"],
|
|
4645
|
+
["Sync with remote", "$0 team sync"],
|
|
4646
|
+
["Create a bundle", "$0 team bundle-create --name my-bundle --skills skill1,skill2"],
|
|
4647
|
+
["Export a bundle", "$0 team bundle-export --name my-bundle --output ./bundle.json"],
|
|
4648
|
+
["Import a bundle", "$0 team bundle-import --source ./bundle.json"]
|
|
4649
|
+
]
|
|
4650
|
+
});
|
|
4651
|
+
action = Option26.String({ required: true });
|
|
4652
|
+
name = Option26.String("--name", { description: "Team name (for init), skill name, or bundle name" });
|
|
4653
|
+
registry = Option26.String("--registry", { description: "Registry URL (for init)" });
|
|
4654
|
+
description = Option26.String("--description,-d", { description: "Description (for share/bundle)" });
|
|
4655
|
+
tags = Option26.String("--tags,-t", { description: "Comma-separated tags (for share)" });
|
|
4656
|
+
skills = Option26.String("--skills", { description: "Comma-separated skill names (for bundle)" });
|
|
4657
|
+
output = Option26.String("--output,-o", { description: "Output path (for bundle-export)" });
|
|
4658
|
+
source = Option26.String("--source,-s", { description: "Source path (for bundle-import)" });
|
|
4659
|
+
overwrite = Option26.Boolean("--overwrite", { description: "Overwrite existing (for import)" });
|
|
4660
|
+
dryRun = Option26.Boolean("--dry-run", { description: "Preview without changes" });
|
|
4661
|
+
async execute() {
|
|
4662
|
+
const projectPath = process.cwd();
|
|
4663
|
+
const teamManager = createTeamManager(projectPath);
|
|
4664
|
+
try {
|
|
4665
|
+
switch (this.action) {
|
|
4666
|
+
case "init":
|
|
4667
|
+
return await this.initTeam(teamManager);
|
|
4668
|
+
case "share":
|
|
4669
|
+
return await this.shareSkill(teamManager);
|
|
4670
|
+
case "import":
|
|
4671
|
+
return await this.importSkill(teamManager);
|
|
4672
|
+
case "list":
|
|
4673
|
+
return await this.listSkills(teamManager);
|
|
4674
|
+
case "sync":
|
|
4675
|
+
return await this.syncTeam(teamManager);
|
|
4676
|
+
case "remove":
|
|
4677
|
+
return await this.removeSkill(teamManager);
|
|
4678
|
+
case "bundle-create":
|
|
4679
|
+
return await this.createBundle(teamManager);
|
|
4680
|
+
case "bundle-export":
|
|
4681
|
+
return await this.exportSkillBundle(teamManager);
|
|
4682
|
+
case "bundle-import":
|
|
4683
|
+
return await this.importSkillBundle();
|
|
4684
|
+
default:
|
|
4685
|
+
this.context.stderr.write(chalk26.red(`Unknown action: ${this.action}
|
|
4686
|
+
`));
|
|
4687
|
+
this.context.stderr.write("Available actions: init, share, import, list, sync, remove, bundle-create, bundle-export, bundle-import\n");
|
|
4688
|
+
return 1;
|
|
4689
|
+
}
|
|
4690
|
+
} catch (err) {
|
|
4691
|
+
this.context.stderr.write(chalk26.red(`\u2717 ${err instanceof Error ? err.message : "Unknown error"}
|
|
4692
|
+
`));
|
|
4693
|
+
return 1;
|
|
4694
|
+
}
|
|
4695
|
+
}
|
|
4696
|
+
async initTeam(teamManager) {
|
|
4697
|
+
if (!this.name) {
|
|
4698
|
+
this.context.stderr.write(chalk26.red("--name is required for init\n"));
|
|
4699
|
+
return 1;
|
|
4700
|
+
}
|
|
4701
|
+
if (!this.registry) {
|
|
4702
|
+
this.context.stderr.write(chalk26.red("--registry is required for init\n"));
|
|
4703
|
+
return 1;
|
|
4704
|
+
}
|
|
4705
|
+
const config = await teamManager.init({
|
|
4706
|
+
teamName: this.name,
|
|
4707
|
+
registryUrl: this.registry
|
|
4708
|
+
});
|
|
4709
|
+
this.context.stdout.write(chalk26.green("\u2713 Team initialized!\n"));
|
|
4710
|
+
this.context.stdout.write(` Team ID: ${config.teamId}
|
|
4711
|
+
`);
|
|
4712
|
+
this.context.stdout.write(` Registry: ${config.registryUrl}
|
|
4713
|
+
`);
|
|
4714
|
+
return 0;
|
|
4715
|
+
}
|
|
4716
|
+
async shareSkill(teamManager) {
|
|
4717
|
+
const config = teamManager.load();
|
|
4718
|
+
if (!config) {
|
|
4719
|
+
this.context.stderr.write(chalk26.red("Team not initialized. Run `skillkit team init` first.\n"));
|
|
4720
|
+
return 1;
|
|
4721
|
+
}
|
|
4722
|
+
if (!this.name) {
|
|
4723
|
+
this.context.stderr.write(chalk26.red("--name <skill-name> is required for share\n"));
|
|
4724
|
+
return 1;
|
|
4725
|
+
}
|
|
4726
|
+
const shared = await teamManager.shareSkill({
|
|
4727
|
+
skillName: this.name,
|
|
4728
|
+
description: this.description,
|
|
4729
|
+
tags: this.tags?.split(",").map((t) => t.trim())
|
|
4730
|
+
});
|
|
4731
|
+
this.context.stdout.write(chalk26.green("\u2713 Skill shared!\n"));
|
|
4732
|
+
this.context.stdout.write(` Name: ${shared.name}
|
|
4733
|
+
`);
|
|
4734
|
+
this.context.stdout.write(` Version: ${shared.version}
|
|
4735
|
+
`);
|
|
4736
|
+
this.context.stdout.write(` Source: ${shared.source}
|
|
4737
|
+
`);
|
|
4738
|
+
return 0;
|
|
4739
|
+
}
|
|
4740
|
+
async importSkill(teamManager) {
|
|
4741
|
+
const config = teamManager.load();
|
|
4742
|
+
if (!config) {
|
|
4743
|
+
this.context.stderr.write(chalk26.red("Team not initialized. Run `skillkit team init` first.\n"));
|
|
4744
|
+
return 1;
|
|
4745
|
+
}
|
|
4746
|
+
if (!this.name) {
|
|
4747
|
+
this.context.stderr.write(chalk26.red("--name <skill-name> is required for import\n"));
|
|
4748
|
+
return 1;
|
|
4749
|
+
}
|
|
4750
|
+
const result = await teamManager.importSkill(this.name, {
|
|
4751
|
+
overwrite: this.overwrite,
|
|
4752
|
+
dryRun: this.dryRun
|
|
4753
|
+
});
|
|
4754
|
+
if (!result.success) {
|
|
4755
|
+
this.context.stderr.write(chalk26.red(`\u2717 ${result.error}
|
|
4756
|
+
`));
|
|
4757
|
+
return 1;
|
|
4758
|
+
}
|
|
4759
|
+
if (this.dryRun) {
|
|
4760
|
+
this.context.stdout.write(chalk26.cyan(`[dry-run] Would import to: ${result.path}
|
|
4761
|
+
`));
|
|
4762
|
+
} else {
|
|
4763
|
+
this.context.stdout.write(chalk26.green(`\u2713 Skill imported to: ${result.path}
|
|
4764
|
+
`));
|
|
4765
|
+
}
|
|
4766
|
+
return 0;
|
|
4767
|
+
}
|
|
4768
|
+
async listSkills(teamManager) {
|
|
4769
|
+
const config = teamManager.load();
|
|
4770
|
+
if (!config) {
|
|
4771
|
+
this.context.stderr.write(chalk26.red("Team not initialized. Run `skillkit team init` first.\n"));
|
|
4772
|
+
return 1;
|
|
4773
|
+
}
|
|
4774
|
+
const skills = teamManager.listSharedSkills();
|
|
4775
|
+
this.context.stdout.write(chalk26.cyan(`Team: ${config.teamName}
|
|
4776
|
+
`));
|
|
4777
|
+
this.context.stdout.write(chalk26.gray(`Registry: ${config.registryUrl}
|
|
4778
|
+
|
|
4779
|
+
`));
|
|
4780
|
+
if (skills.length === 0) {
|
|
4781
|
+
this.context.stdout.write("No shared skills yet. Use `skillkit team share` to share a skill.\n");
|
|
4782
|
+
return 0;
|
|
4783
|
+
}
|
|
4784
|
+
this.context.stdout.write(`Shared Skills (${skills.length}):
|
|
4785
|
+
`);
|
|
4786
|
+
for (const skill of skills) {
|
|
4787
|
+
this.context.stdout.write(chalk26.cyan(` ${skill.name}`) + ` v${skill.version}
|
|
4788
|
+
`);
|
|
4789
|
+
if (skill.description) {
|
|
4790
|
+
this.context.stdout.write(chalk26.gray(` ${skill.description}
|
|
4791
|
+
`));
|
|
4792
|
+
}
|
|
4793
|
+
this.context.stdout.write(` by ${skill.author} | ${skill.downloads || 0} downloads
|
|
4794
|
+
`);
|
|
4795
|
+
}
|
|
4796
|
+
return 0;
|
|
4797
|
+
}
|
|
4798
|
+
async syncTeam(teamManager) {
|
|
4799
|
+
const config = teamManager.load();
|
|
4800
|
+
if (!config) {
|
|
4801
|
+
this.context.stderr.write(chalk26.red("Team not initialized. Run `skillkit team init` first.\n"));
|
|
4802
|
+
return 1;
|
|
4803
|
+
}
|
|
4804
|
+
this.context.stdout.write(`Syncing with ${config.registryUrl}...
|
|
4805
|
+
`);
|
|
4806
|
+
const result = await teamManager.sync();
|
|
4807
|
+
this.context.stdout.write(chalk26.green("\u2713 Sync complete!\n"));
|
|
4808
|
+
if (result.added.length > 0) {
|
|
4809
|
+
this.context.stdout.write(` Added: ${result.added.join(", ")}
|
|
4810
|
+
`);
|
|
4811
|
+
}
|
|
4812
|
+
if (result.updated.length > 0) {
|
|
4813
|
+
this.context.stdout.write(` Updated: ${result.updated.join(", ")}
|
|
4814
|
+
`);
|
|
4815
|
+
}
|
|
4816
|
+
if (result.added.length === 0 && result.updated.length === 0) {
|
|
4817
|
+
this.context.stdout.write(" Already up to date.\n");
|
|
4818
|
+
}
|
|
4819
|
+
return 0;
|
|
4820
|
+
}
|
|
4821
|
+
async removeSkill(teamManager) {
|
|
4822
|
+
const config = teamManager.load();
|
|
4823
|
+
if (!config) {
|
|
4824
|
+
this.context.stderr.write(chalk26.red("Team not initialized. Run `skillkit team init` first.\n"));
|
|
4825
|
+
return 1;
|
|
4826
|
+
}
|
|
4827
|
+
if (!this.name) {
|
|
4828
|
+
this.context.stderr.write(chalk26.red("--name <skill-name> is required for remove\n"));
|
|
4829
|
+
return 1;
|
|
4830
|
+
}
|
|
4831
|
+
const removed = teamManager.removeSkill(this.name);
|
|
4832
|
+
if (!removed) {
|
|
4833
|
+
this.context.stderr.write(chalk26.red(`Skill "${this.name}" not found in team registry.
|
|
4834
|
+
`));
|
|
4835
|
+
return 1;
|
|
4836
|
+
}
|
|
4837
|
+
this.context.stdout.write(chalk26.green(`\u2713 Skill "${this.name}" removed from team registry.
|
|
4838
|
+
`));
|
|
4839
|
+
return 0;
|
|
4840
|
+
}
|
|
4841
|
+
async createBundle(teamManager) {
|
|
4842
|
+
const config = teamManager.load();
|
|
4843
|
+
if (!config) {
|
|
4844
|
+
this.context.stderr.write(chalk26.red("Team not initialized. Run `skillkit team init` first.\n"));
|
|
4845
|
+
return 1;
|
|
4846
|
+
}
|
|
4847
|
+
if (!this.name) {
|
|
4848
|
+
this.context.stderr.write(chalk26.red("--name <bundle-name> is required for bundle-create\n"));
|
|
4849
|
+
return 1;
|
|
4850
|
+
}
|
|
4851
|
+
if (!this.skills) {
|
|
4852
|
+
this.context.stderr.write(chalk26.red("--skills <skill1,skill2,...> is required for bundle-create\n"));
|
|
4853
|
+
return 1;
|
|
4854
|
+
}
|
|
4855
|
+
const skillNames = this.skills.split(",").map((s) => s.trim());
|
|
4856
|
+
const projectPath = process.cwd();
|
|
4857
|
+
const skillsDir = join9(projectPath, "skills");
|
|
4858
|
+
const bundle = createSkillBundle(this.name, config.teamName, this.description);
|
|
4859
|
+
let addedCount = 0;
|
|
4860
|
+
for (const skillName of skillNames) {
|
|
4861
|
+
const skillPath = join9(skillsDir, skillName);
|
|
4862
|
+
try {
|
|
4863
|
+
bundle.addSkill(skillPath);
|
|
4864
|
+
addedCount++;
|
|
4865
|
+
this.context.stdout.write(chalk26.gray(` + ${skillName}
|
|
4866
|
+
`));
|
|
4867
|
+
} catch (err) {
|
|
4868
|
+
this.context.stderr.write(chalk26.yellow(` \u26A0 Skipping ${skillName}: ${err instanceof Error ? err.message : "Unknown error"}
|
|
4869
|
+
`));
|
|
4870
|
+
}
|
|
4871
|
+
}
|
|
4872
|
+
if (addedCount === 0) {
|
|
4873
|
+
this.context.stderr.write(chalk26.red("No skills were added to the bundle.\n"));
|
|
4874
|
+
return 1;
|
|
4875
|
+
}
|
|
4876
|
+
const outputPath = this.output || join9(projectPath, ".skillkit", "bundles", `${this.name}.json`);
|
|
4877
|
+
const result = exportBundle(bundle, outputPath);
|
|
4878
|
+
if (!result.success) {
|
|
4879
|
+
this.context.stderr.write(chalk26.red(`\u2717 ${result.error}
|
|
4880
|
+
`));
|
|
4881
|
+
return 1;
|
|
4882
|
+
}
|
|
4883
|
+
this.context.stdout.write(chalk26.green(`
|
|
4884
|
+
\u2713 Bundle "${this.name}" created with ${addedCount} skills!
|
|
4885
|
+
`));
|
|
4886
|
+
this.context.stdout.write(` Checksum: ${bundle.getChecksum()}
|
|
4887
|
+
`);
|
|
4888
|
+
this.context.stdout.write(` Output: ${result.path}
|
|
4889
|
+
`);
|
|
4890
|
+
return 0;
|
|
4891
|
+
}
|
|
4892
|
+
async exportSkillBundle(teamManager) {
|
|
4893
|
+
const config = teamManager.load();
|
|
4894
|
+
if (!config) {
|
|
4895
|
+
this.context.stderr.write(chalk26.red("Team not initialized. Run `skillkit team init` first.\n"));
|
|
4896
|
+
return 1;
|
|
4897
|
+
}
|
|
4898
|
+
if (!this.name) {
|
|
4899
|
+
this.context.stderr.write(chalk26.red("--name <bundle-name> is required for bundle-export\n"));
|
|
4900
|
+
return 1;
|
|
4901
|
+
}
|
|
4902
|
+
if (!this.output) {
|
|
4903
|
+
this.context.stderr.write(chalk26.red("--output <path> is required for bundle-export\n"));
|
|
4904
|
+
return 1;
|
|
4905
|
+
}
|
|
4906
|
+
const projectPath = process.cwd();
|
|
4907
|
+
const bundlePath = join9(projectPath, ".skillkit", "bundles", `${this.name}.json`);
|
|
4908
|
+
const { existsSync: existsSync14, readFileSync: readFileSync7, writeFileSync: writeFileSync6 } = await import("fs");
|
|
4909
|
+
if (!existsSync14(bundlePath)) {
|
|
4910
|
+
this.context.stderr.write(chalk26.red(`Bundle "${this.name}" not found. Create it first with bundle-create.
|
|
4911
|
+
`));
|
|
4912
|
+
return 1;
|
|
4913
|
+
}
|
|
4914
|
+
const content = readFileSync7(bundlePath, "utf-8");
|
|
4915
|
+
writeFileSync6(this.output, content, "utf-8");
|
|
4916
|
+
this.context.stdout.write(chalk26.green(`\u2713 Bundle exported to: ${this.output}
|
|
4917
|
+
`));
|
|
4918
|
+
return 0;
|
|
4919
|
+
}
|
|
4920
|
+
async importSkillBundle() {
|
|
4921
|
+
if (!this.source) {
|
|
4922
|
+
this.context.stderr.write(chalk26.red("--source <path> is required for bundle-import\n"));
|
|
4923
|
+
return 1;
|
|
4924
|
+
}
|
|
4925
|
+
const { existsSync: existsSync14 } = await import("fs");
|
|
4926
|
+
if (!existsSync14(this.source)) {
|
|
4927
|
+
this.context.stderr.write(chalk26.red(`Bundle file not found: ${this.source}
|
|
4928
|
+
`));
|
|
4929
|
+
return 1;
|
|
4930
|
+
}
|
|
4931
|
+
const projectPath = process.cwd();
|
|
4932
|
+
const skillsDir = join9(projectPath, "skills");
|
|
4933
|
+
if (this.dryRun) {
|
|
4934
|
+
this.context.stdout.write(chalk26.cyan("[dry-run] Would import bundle to: " + skillsDir + "\n"));
|
|
4935
|
+
return 0;
|
|
4936
|
+
}
|
|
4937
|
+
const result = importBundle(this.source, skillsDir, { overwrite: this.overwrite });
|
|
4938
|
+
if (!result.success && result.imported.length === 0) {
|
|
4939
|
+
this.context.stderr.write(chalk26.red("\u2717 Failed to import bundle:\n"));
|
|
4940
|
+
for (const error of result.errors) {
|
|
4941
|
+
this.context.stderr.write(chalk26.red(` - ${error}
|
|
4942
|
+
`));
|
|
4943
|
+
}
|
|
4944
|
+
return 1;
|
|
4945
|
+
}
|
|
4946
|
+
this.context.stdout.write(chalk26.green(`\u2713 Imported ${result.imported.length} skills from bundle!
|
|
4947
|
+
`));
|
|
4948
|
+
for (const name of result.imported) {
|
|
4949
|
+
this.context.stdout.write(chalk26.gray(` + ${name}
|
|
4950
|
+
`));
|
|
4951
|
+
}
|
|
4952
|
+
if (result.errors.length > 0) {
|
|
4953
|
+
this.context.stdout.write(chalk26.yellow("\nWarnings:\n"));
|
|
4954
|
+
for (const error of result.errors) {
|
|
4955
|
+
this.context.stdout.write(chalk26.yellow(` - ${error}
|
|
4956
|
+
`));
|
|
4957
|
+
}
|
|
4958
|
+
}
|
|
4959
|
+
return 0;
|
|
4960
|
+
}
|
|
4961
|
+
};
|
|
4962
|
+
|
|
4963
|
+
// src/commands/plugin.ts
|
|
4964
|
+
import { Command as Command28, Option as Option27 } from "clipanion";
|
|
4965
|
+
import { join as join10, isAbsolute, resolve as resolve11, sep } from "path";
|
|
4966
|
+
import { homedir } from "os";
|
|
4967
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync6, copyFileSync, cpSync as cpSync3, rmSync as rmSync4 } from "fs";
|
|
4968
|
+
import chalk27 from "chalk";
|
|
4969
|
+
import { createPluginManager, loadPlugin, loadPluginsFromDirectory } from "@skillkit/core";
|
|
4970
|
+
var PluginCommand = class extends Command28 {
|
|
4971
|
+
static paths = [["plugin"]];
|
|
4972
|
+
static usage = Command28.Usage({
|
|
4973
|
+
description: "Manage SkillKit plugins",
|
|
4974
|
+
examples: [
|
|
4975
|
+
["List installed plugins", "$0 plugin list"],
|
|
4976
|
+
["Install a plugin", "$0 plugin install --source ./my-plugin"],
|
|
4977
|
+
["Install from npm", "$0 plugin install --source skillkit-plugin-gitlab"],
|
|
4978
|
+
["Uninstall a plugin", "$0 plugin uninstall --name my-plugin"],
|
|
4979
|
+
["Enable a plugin", "$0 plugin enable --name my-plugin"],
|
|
4980
|
+
["Disable a plugin", "$0 plugin disable --name my-plugin"]
|
|
4981
|
+
]
|
|
4982
|
+
});
|
|
4983
|
+
action = Option27.String({ required: true });
|
|
4984
|
+
source = Option27.String("--source,-s", { description: "Plugin source (file path or npm package)" });
|
|
4985
|
+
name = Option27.String("--name,-n", { description: "Plugin name" });
|
|
4986
|
+
global = Option27.Boolean("--global,-g", { description: "Use global plugin directory" });
|
|
4987
|
+
async execute() {
|
|
4988
|
+
const projectPath = this.global ? join10(homedir(), ".skillkit") : process.cwd();
|
|
4989
|
+
const pluginManager = createPluginManager(projectPath);
|
|
4990
|
+
const pluginsDir = this.global ? join10(projectPath, "plugins") : join10(projectPath, ".skillkit", "plugins");
|
|
4991
|
+
try {
|
|
4992
|
+
const plugins = await loadPluginsFromDirectory(pluginsDir);
|
|
4993
|
+
for (const plugin of plugins) {
|
|
4994
|
+
if (pluginManager.isPluginEnabled(plugin.metadata.name)) {
|
|
4995
|
+
await pluginManager.register(plugin);
|
|
4996
|
+
}
|
|
4997
|
+
}
|
|
4998
|
+
} catch {
|
|
4999
|
+
}
|
|
5000
|
+
try {
|
|
5001
|
+
switch (this.action) {
|
|
5002
|
+
case "list":
|
|
5003
|
+
return this.listPlugins(pluginManager);
|
|
5004
|
+
case "install":
|
|
5005
|
+
return await this.installPlugin(pluginManager);
|
|
5006
|
+
case "uninstall":
|
|
5007
|
+
return await this.uninstallPlugin(pluginManager);
|
|
5008
|
+
case "enable":
|
|
5009
|
+
return this.enablePlugin(pluginManager);
|
|
5010
|
+
case "disable":
|
|
5011
|
+
return this.disablePlugin(pluginManager);
|
|
5012
|
+
case "info":
|
|
5013
|
+
return this.pluginInfo(pluginManager);
|
|
5014
|
+
default:
|
|
5015
|
+
this.context.stderr.write(chalk27.red(`Unknown action: ${this.action}
|
|
5016
|
+
`));
|
|
5017
|
+
this.context.stderr.write("Available actions: list, install, uninstall, enable, disable, info\n");
|
|
5018
|
+
return 1;
|
|
5019
|
+
}
|
|
5020
|
+
} catch (err) {
|
|
5021
|
+
this.context.stderr.write(chalk27.red(`\u2717 ${err instanceof Error ? err.message : "Unknown error"}
|
|
5022
|
+
`));
|
|
5023
|
+
return 1;
|
|
5024
|
+
}
|
|
5025
|
+
}
|
|
5026
|
+
listPlugins(pluginManager) {
|
|
5027
|
+
const plugins = pluginManager.listPlugins();
|
|
5028
|
+
if (plugins.length === 0) {
|
|
5029
|
+
this.context.stdout.write("No plugins installed.\n");
|
|
5030
|
+
this.context.stdout.write("Use `skillkit plugin install --source <source>` to install a plugin.\n");
|
|
5031
|
+
return 0;
|
|
5032
|
+
}
|
|
5033
|
+
this.context.stdout.write(chalk27.cyan(`Installed Plugins (${plugins.length}):
|
|
5034
|
+
|
|
5035
|
+
`));
|
|
5036
|
+
for (const plugin of plugins) {
|
|
5037
|
+
const enabled = pluginManager.isPluginEnabled(plugin.name);
|
|
5038
|
+
const status = enabled ? chalk27.green("enabled") : chalk27.gray("disabled");
|
|
5039
|
+
this.context.stdout.write(chalk27.cyan(` ${plugin.name}`) + ` v${plugin.version} [${status}]
|
|
5040
|
+
`);
|
|
5041
|
+
if (plugin.description) {
|
|
5042
|
+
this.context.stdout.write(chalk27.gray(` ${plugin.description}
|
|
5043
|
+
`));
|
|
5044
|
+
}
|
|
5045
|
+
}
|
|
5046
|
+
const translators = pluginManager.getAllTranslators();
|
|
5047
|
+
const providers = pluginManager.getAllProviders();
|
|
5048
|
+
const commands = pluginManager.getAllCommands();
|
|
5049
|
+
if (translators.size > 0 || providers.size > 0 || commands.length > 0) {
|
|
5050
|
+
this.context.stdout.write(chalk27.cyan("\nRegistered Extensions:\n"));
|
|
5051
|
+
if (translators.size > 0) {
|
|
5052
|
+
this.context.stdout.write(` Translators: ${Array.from(translators.keys()).join(", ")}
|
|
5053
|
+
`);
|
|
5054
|
+
}
|
|
5055
|
+
if (providers.size > 0) {
|
|
5056
|
+
this.context.stdout.write(` Providers: ${Array.from(providers.keys()).join(", ")}
|
|
5057
|
+
`);
|
|
5058
|
+
}
|
|
5059
|
+
if (commands.length > 0) {
|
|
5060
|
+
this.context.stdout.write(` Commands: ${commands.map((c) => c.name).join(", ")}
|
|
5061
|
+
`);
|
|
5062
|
+
}
|
|
5063
|
+
}
|
|
5064
|
+
return 0;
|
|
5065
|
+
}
|
|
5066
|
+
/**
|
|
5067
|
+
* Validate plugin name to prevent path traversal attacks
|
|
5068
|
+
* Allows scoped npm names like @scope/name (mirrors loader.ts validation)
|
|
5069
|
+
*/
|
|
5070
|
+
isValidPluginName(name) {
|
|
5071
|
+
if (!name) return false;
|
|
5072
|
+
if (name.includes("\\") || name.includes("..") || name === "." || name.startsWith(".")) {
|
|
5073
|
+
return false;
|
|
5074
|
+
}
|
|
5075
|
+
return /^(?:@[a-z0-9-]+\/)?[a-z0-9-]+$/.test(name);
|
|
5076
|
+
}
|
|
5077
|
+
async installPlugin(pluginManager) {
|
|
5078
|
+
if (!this.source) {
|
|
5079
|
+
this.context.stderr.write(chalk27.red("--source is required for install\n"));
|
|
5080
|
+
return 1;
|
|
5081
|
+
}
|
|
5082
|
+
const resolvedSource = this.source.startsWith("~") ? join10(homedir(), this.source.slice(1)) : this.source;
|
|
5083
|
+
this.context.stdout.write(`Installing plugin from ${this.source}...
|
|
5084
|
+
`);
|
|
5085
|
+
const plugin = await loadPlugin(resolvedSource);
|
|
5086
|
+
const pluginName = plugin.metadata.name;
|
|
5087
|
+
if (!this.isValidPluginName(pluginName)) {
|
|
5088
|
+
this.context.stderr.write(chalk27.red(`Invalid plugin name: ${pluginName}
|
|
5089
|
+
`));
|
|
5090
|
+
return 1;
|
|
5091
|
+
}
|
|
5092
|
+
const projectPath = this.global ? join10(homedir(), ".skillkit") : process.cwd();
|
|
5093
|
+
const pluginsDir = this.global ? join10(projectPath, "plugins") : join10(projectPath, ".skillkit", "plugins");
|
|
5094
|
+
const isLocalPath3 = this.source.startsWith("./") || this.source.startsWith("../") || this.source.startsWith("/") || this.source.startsWith("~") || this.source.includes("\\") || isAbsolute(this.source);
|
|
5095
|
+
if (isLocalPath3 && existsSync12(resolvedSource)) {
|
|
5096
|
+
const targetDir = join10(pluginsDir, pluginName);
|
|
5097
|
+
const resolvedTarget = resolve11(targetDir);
|
|
5098
|
+
const resolvedPluginsDir = resolve11(pluginsDir);
|
|
5099
|
+
if (!resolvedTarget.startsWith(resolvedPluginsDir + sep)) {
|
|
5100
|
+
this.context.stderr.write(chalk27.red("Invalid plugin name\n"));
|
|
5101
|
+
return 1;
|
|
5102
|
+
}
|
|
5103
|
+
if (!existsSync12(pluginsDir)) {
|
|
5104
|
+
mkdirSync6(pluginsDir, { recursive: true });
|
|
5105
|
+
}
|
|
5106
|
+
const { statSync: statSync2 } = await import("fs");
|
|
5107
|
+
const sourceStat = statSync2(resolvedSource);
|
|
5108
|
+
if (sourceStat.isDirectory()) {
|
|
5109
|
+
cpSync3(resolvedSource, targetDir, { recursive: true });
|
|
5110
|
+
} else {
|
|
5111
|
+
if (!existsSync12(targetDir)) {
|
|
5112
|
+
mkdirSync6(targetDir, { recursive: true });
|
|
5113
|
+
}
|
|
5114
|
+
let destFileName;
|
|
5115
|
+
if (resolvedSource.endsWith(".json")) {
|
|
5116
|
+
destFileName = "plugin.json";
|
|
5117
|
+
} else if (resolvedSource.endsWith(".mjs")) {
|
|
5118
|
+
destFileName = "index.mjs";
|
|
5119
|
+
} else {
|
|
5120
|
+
destFileName = "index.js";
|
|
5121
|
+
}
|
|
5122
|
+
copyFileSync(resolvedSource, join10(targetDir, destFileName));
|
|
5123
|
+
}
|
|
5124
|
+
this.context.stdout.write(chalk27.dim(` Copied to ${targetDir}
|
|
5125
|
+
`));
|
|
5126
|
+
}
|
|
5127
|
+
await pluginManager.register(plugin);
|
|
5128
|
+
this.context.stdout.write(chalk27.green(`\u2713 Plugin "${plugin.metadata.name}" installed!
|
|
5129
|
+
`));
|
|
5130
|
+
this.context.stdout.write(` Version: ${plugin.metadata.version}
|
|
5131
|
+
`);
|
|
5132
|
+
if (plugin.metadata.description) {
|
|
5133
|
+
this.context.stdout.write(` ${plugin.metadata.description}
|
|
5134
|
+
`);
|
|
5135
|
+
}
|
|
5136
|
+
if (plugin.translators?.length) {
|
|
5137
|
+
this.context.stdout.write(` Translators: ${plugin.translators.map((t) => t.agentType).join(", ")}
|
|
5138
|
+
`);
|
|
5139
|
+
}
|
|
5140
|
+
if (plugin.providers?.length) {
|
|
5141
|
+
this.context.stdout.write(` Providers: ${plugin.providers.map((p) => p.providerName).join(", ")}
|
|
5142
|
+
`);
|
|
5143
|
+
}
|
|
5144
|
+
if (plugin.commands?.length) {
|
|
5145
|
+
this.context.stdout.write(` Commands: ${plugin.commands.map((c) => c.name).join(", ")}
|
|
5146
|
+
`);
|
|
5147
|
+
}
|
|
5148
|
+
return 0;
|
|
5149
|
+
}
|
|
5150
|
+
async uninstallPlugin(pluginManager) {
|
|
5151
|
+
if (!this.name) {
|
|
5152
|
+
this.context.stderr.write(chalk27.red("--name is required for uninstall\n"));
|
|
5153
|
+
return 1;
|
|
5154
|
+
}
|
|
5155
|
+
if (!this.isValidPluginName(this.name)) {
|
|
5156
|
+
this.context.stderr.write(chalk27.red("Invalid plugin name\n"));
|
|
5157
|
+
return 1;
|
|
5158
|
+
}
|
|
5159
|
+
await pluginManager.unregister(this.name);
|
|
5160
|
+
const projectPath = this.global ? join10(homedir(), ".skillkit") : process.cwd();
|
|
5161
|
+
const pluginsDir = this.global ? join10(projectPath, "plugins") : join10(projectPath, ".skillkit", "plugins");
|
|
5162
|
+
const pluginDir = join10(pluginsDir, this.name);
|
|
5163
|
+
const resolvedPluginDir = resolve11(pluginDir);
|
|
5164
|
+
const resolvedPluginsDir = resolve11(pluginsDir);
|
|
5165
|
+
if (!resolvedPluginDir.startsWith(resolvedPluginsDir + sep)) {
|
|
5166
|
+
this.context.stderr.write(chalk27.red("Invalid plugin name\n"));
|
|
5167
|
+
return 1;
|
|
5168
|
+
}
|
|
5169
|
+
if (existsSync12(pluginDir)) {
|
|
5170
|
+
rmSync4(pluginDir, { recursive: true, force: true });
|
|
5171
|
+
this.context.stdout.write(chalk27.dim(` Removed ${pluginDir}
|
|
5172
|
+
`));
|
|
5173
|
+
}
|
|
5174
|
+
this.context.stdout.write(chalk27.green(`\u2713 Plugin "${this.name}" uninstalled.
|
|
5175
|
+
`));
|
|
5176
|
+
return 0;
|
|
5177
|
+
}
|
|
5178
|
+
enablePlugin(pluginManager) {
|
|
5179
|
+
if (!this.name) {
|
|
5180
|
+
this.context.stderr.write(chalk27.red("--name is required for enable\n"));
|
|
5181
|
+
return 1;
|
|
5182
|
+
}
|
|
5183
|
+
pluginManager.enablePlugin(this.name);
|
|
5184
|
+
this.context.stdout.write(chalk27.green(`\u2713 Plugin "${this.name}" enabled.
|
|
5185
|
+
`));
|
|
5186
|
+
return 0;
|
|
5187
|
+
}
|
|
5188
|
+
disablePlugin(pluginManager) {
|
|
5189
|
+
if (!this.name) {
|
|
5190
|
+
this.context.stderr.write(chalk27.red("--name is required for disable\n"));
|
|
5191
|
+
return 1;
|
|
5192
|
+
}
|
|
5193
|
+
pluginManager.disablePlugin(this.name);
|
|
5194
|
+
this.context.stdout.write(chalk27.green(`\u2713 Plugin "${this.name}" disabled.
|
|
5195
|
+
`));
|
|
5196
|
+
return 0;
|
|
5197
|
+
}
|
|
5198
|
+
pluginInfo(pluginManager) {
|
|
5199
|
+
if (!this.name) {
|
|
5200
|
+
this.context.stderr.write(chalk27.red("--name is required for info\n"));
|
|
5201
|
+
return 1;
|
|
5202
|
+
}
|
|
5203
|
+
const plugin = pluginManager.getPlugin(this.name);
|
|
5204
|
+
if (!plugin) {
|
|
5205
|
+
this.context.stderr.write(chalk27.red(`Plugin "${this.name}" not found.
|
|
5206
|
+
`));
|
|
5207
|
+
return 1;
|
|
5208
|
+
}
|
|
5209
|
+
const { metadata } = plugin;
|
|
5210
|
+
const enabled = pluginManager.isPluginEnabled(this.name);
|
|
5211
|
+
this.context.stdout.write(chalk27.cyan(`${metadata.name}`) + ` v${metadata.version}
|
|
5212
|
+
`);
|
|
5213
|
+
this.context.stdout.write(`Status: ${enabled ? "enabled" : "disabled"}
|
|
5214
|
+
`);
|
|
5215
|
+
if (metadata.description) {
|
|
5216
|
+
this.context.stdout.write(`Description: ${metadata.description}
|
|
5217
|
+
`);
|
|
5218
|
+
}
|
|
5219
|
+
if (metadata.author) {
|
|
5220
|
+
this.context.stdout.write(`Author: ${metadata.author}
|
|
5221
|
+
`);
|
|
5222
|
+
}
|
|
5223
|
+
if (metadata.homepage) {
|
|
5224
|
+
this.context.stdout.write(`Homepage: ${metadata.homepage}
|
|
5225
|
+
`);
|
|
5226
|
+
}
|
|
5227
|
+
if (metadata.keywords?.length) {
|
|
5228
|
+
this.context.stdout.write(`Keywords: ${metadata.keywords.join(", ")}
|
|
5229
|
+
`);
|
|
5230
|
+
}
|
|
5231
|
+
if (metadata.dependencies?.length) {
|
|
5232
|
+
this.context.stdout.write(`Dependencies: ${metadata.dependencies.join(", ")}
|
|
5233
|
+
`);
|
|
5234
|
+
}
|
|
5235
|
+
return 0;
|
|
5236
|
+
}
|
|
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
|
+
};
|
|
4043
7199
|
export {
|
|
7200
|
+
AICommand,
|
|
7201
|
+
AuditCommand,
|
|
7202
|
+
CICDCommand,
|
|
7203
|
+
CommandCmd,
|
|
4044
7204
|
ContextCommand,
|
|
4045
7205
|
CreateCommand,
|
|
4046
7206
|
DisableCommand,
|
|
4047
7207
|
EnableCommand,
|
|
7208
|
+
HookCommand,
|
|
4048
7209
|
InitCommand,
|
|
4049
7210
|
InstallCommand,
|
|
4050
7211
|
ListCommand,
|
|
4051
7212
|
MarketplaceCommand,
|
|
4052
7213
|
MemoryCommand,
|
|
7214
|
+
MethodologyCommand,
|
|
4053
7215
|
PauseCommand,
|
|
7216
|
+
PlanCommand,
|
|
7217
|
+
PluginCommand,
|
|
4054
7218
|
ReadCommand,
|
|
4055
7219
|
RecommendCommand,
|
|
4056
7220
|
RemoveCommand,
|
|
4057
7221
|
ResumeCommand,
|
|
4058
7222
|
RunCommand,
|
|
7223
|
+
SettingsCommand,
|
|
4059
7224
|
StatusCommand,
|
|
4060
7225
|
SyncCommand,
|
|
7226
|
+
TeamCommand,
|
|
4061
7227
|
TestCommand,
|
|
4062
7228
|
TranslateCommand,
|
|
4063
7229
|
UICommand,
|