@hacksmith/doraval 0.2.29 → 0.2.35
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/bin/doraval.js +2689 -849
- package/package.json +1 -1
package/bin/doraval.js
CHANGED
|
@@ -599,7 +599,7 @@ var init_dist = __esm(() => {
|
|
|
599
599
|
var require_package = __commonJS((exports, module) => {
|
|
600
600
|
module.exports = {
|
|
601
601
|
name: "@hacksmith/doraval",
|
|
602
|
-
version: "0.2.
|
|
602
|
+
version: "0.2.35",
|
|
603
603
|
author: "Saif",
|
|
604
604
|
repository: {
|
|
605
605
|
type: "git",
|
|
@@ -2662,12 +2662,64 @@ var init_sync = __esm(() => {
|
|
|
2662
2662
|
});
|
|
2663
2663
|
});
|
|
2664
2664
|
|
|
2665
|
+
// src/providers/spec.ts
|
|
2666
|
+
function getProviderSpec(id) {
|
|
2667
|
+
return PROVIDER_SPECS[id];
|
|
2668
|
+
}
|
|
2669
|
+
var PROVIDER_SPECS, supportedProviders;
|
|
2670
|
+
var init_spec = __esm(() => {
|
|
2671
|
+
PROVIDER_SPECS = {
|
|
2672
|
+
claude: {
|
|
2673
|
+
id: "claude",
|
|
2674
|
+
name: "Claude Code",
|
|
2675
|
+
manifestPath: ".claude-plugin/plugin.json",
|
|
2676
|
+
marketplacePath: ".claude-plugin/marketplace.json",
|
|
2677
|
+
mcpFilename: ".mcp.json",
|
|
2678
|
+
skillsField: "array-or-dir-string",
|
|
2679
|
+
sourceShape: "string",
|
|
2680
|
+
requiresInterface: false
|
|
2681
|
+
},
|
|
2682
|
+
codex: {
|
|
2683
|
+
id: "codex",
|
|
2684
|
+
name: "Codex",
|
|
2685
|
+
manifestPath: ".codex-plugin/plugin.json",
|
|
2686
|
+
marketplacePath: ".agents/plugins/marketplace.json",
|
|
2687
|
+
mcpFilename: ".mcp.json",
|
|
2688
|
+
skillsField: "directory-string",
|
|
2689
|
+
sourceShape: "object",
|
|
2690
|
+
requiresInterface: true
|
|
2691
|
+
},
|
|
2692
|
+
cursor: {
|
|
2693
|
+
id: "cursor",
|
|
2694
|
+
name: "Cursor",
|
|
2695
|
+
manifestPath: ".cursor-plugin/plugin.json",
|
|
2696
|
+
marketplacePath: ".cursor-plugin/marketplace.json",
|
|
2697
|
+
mcpFilename: "mcp.json",
|
|
2698
|
+
skillsField: "directory-string",
|
|
2699
|
+
sourceShape: "string",
|
|
2700
|
+
requiresInterface: false
|
|
2701
|
+
},
|
|
2702
|
+
copilot: {
|
|
2703
|
+
id: "copilot",
|
|
2704
|
+
name: "Copilot CLI",
|
|
2705
|
+
manifestPath: ".github/plugin/plugin.json",
|
|
2706
|
+
marketplacePath: ".github/plugin/marketplace.json",
|
|
2707
|
+
mcpFilename: ".mcp.json",
|
|
2708
|
+
skillsField: "array-of-paths",
|
|
2709
|
+
sourceShape: "string",
|
|
2710
|
+
requiresInterface: false
|
|
2711
|
+
}
|
|
2712
|
+
};
|
|
2713
|
+
supportedProviders = Object.keys(PROVIDER_SPECS);
|
|
2714
|
+
});
|
|
2715
|
+
|
|
2665
2716
|
// src/cli/commands/claude/context.ts
|
|
2666
2717
|
import { existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
|
|
2667
2718
|
import { join as join7 } from "path";
|
|
2668
2719
|
function detectContext(cwd = process.cwd()) {
|
|
2720
|
+
const claudeSpec = getProviderSpec("claude");
|
|
2669
2721
|
const hasClaudeDir = existsSync8(join7(cwd, ".claude"));
|
|
2670
|
-
const hasPluginManifest = existsSync8(join7(cwd,
|
|
2722
|
+
const hasPluginManifest = existsSync8(join7(cwd, claudeSpec.manifestPath));
|
|
2671
2723
|
let looseSkillFiles = [];
|
|
2672
2724
|
try {
|
|
2673
2725
|
const files = readdirSync3(cwd);
|
|
@@ -2689,7 +2741,9 @@ function detectContext(cwd = process.cwd()) {
|
|
|
2689
2741
|
isEmpty
|
|
2690
2742
|
};
|
|
2691
2743
|
}
|
|
2692
|
-
var init_context = () => {
|
|
2744
|
+
var init_context = __esm(() => {
|
|
2745
|
+
init_spec();
|
|
2746
|
+
});
|
|
2693
2747
|
|
|
2694
2748
|
// src/cli/commands/claude/new.ts
|
|
2695
2749
|
var exports_new = {};
|
|
@@ -2698,7 +2752,7 @@ __export(exports_new, {
|
|
|
2698
2752
|
default: () => new_default,
|
|
2699
2753
|
decidePath: () => decidePath
|
|
2700
2754
|
});
|
|
2701
|
-
import { join as join8, basename as basename2 } from "path";
|
|
2755
|
+
import { join as join8, basename as basename2, dirname } from "path";
|
|
2702
2756
|
import { mkdirSync as mkdirSync2, writeFileSync, existsSync as existsSync9 } from "fs";
|
|
2703
2757
|
function decidePath(ctx, intent, providedName) {
|
|
2704
2758
|
const rawName = providedName || "";
|
|
@@ -2748,13 +2802,15 @@ function scaffold(decision, ctx, migrateContent) {
|
|
|
2748
2802
|
}
|
|
2749
2803
|
if (path === "plugin") {
|
|
2750
2804
|
const pluginName = basename2(targetDir);
|
|
2805
|
+
const claudeSpec = getProviderSpec("claude");
|
|
2806
|
+
const claudeManifestDir = dirname(claudeSpec.manifestPath);
|
|
2751
2807
|
const pluginJson = {
|
|
2752
2808
|
name: pluginName,
|
|
2753
2809
|
description: "Scaffolded by doraval claude new",
|
|
2754
2810
|
version: "0.1.0"
|
|
2755
2811
|
};
|
|
2756
|
-
mkdirSync2(join8(targetDir,
|
|
2757
|
-
writeFileSync(join8(targetDir,
|
|
2812
|
+
mkdirSync2(join8(targetDir, claudeManifestDir), { recursive: true });
|
|
2813
|
+
writeFileSync(join8(targetDir, claudeSpec.manifestPath), JSON.stringify(pluginJson, null, 2));
|
|
2758
2814
|
const marketplaceJson = {
|
|
2759
2815
|
name: pluginName,
|
|
2760
2816
|
version: "0.1.0",
|
|
@@ -2817,6 +2873,7 @@ var init_new = __esm(() => {
|
|
|
2817
2873
|
init_out();
|
|
2818
2874
|
init_context();
|
|
2819
2875
|
init_prompt();
|
|
2876
|
+
init_spec();
|
|
2820
2877
|
import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
2821
2878
|
new_default = defineCommand({
|
|
2822
2879
|
meta: {
|
|
@@ -2860,7 +2917,8 @@ var init_new = __esm(() => {
|
|
|
2860
2917
|
const cmdName = decision.path === "plugin" ? `/${basename2(decision.targetDir)}:doraval` : "/my-skill";
|
|
2861
2918
|
ui.info(` Command: ${cmdName}`);
|
|
2862
2919
|
if (decision.path === "plugin") {
|
|
2863
|
-
|
|
2920
|
+
const claudeSpec = getProviderSpec("claude");
|
|
2921
|
+
ui.info(` Claude: ${claudeSpec.manifestPath}`);
|
|
2864
2922
|
ui.info(` Marketplace: marketplace.json (unified / cross-provider listings)`);
|
|
2865
2923
|
}
|
|
2866
2924
|
ui.info(` Test: claude --plugin-dir ${decision.targetDir} (or use normally for standalone)`);
|
|
@@ -2878,7 +2936,7 @@ var exports_bump = {};
|
|
|
2878
2936
|
__export(exports_bump, {
|
|
2879
2937
|
default: () => bump_default
|
|
2880
2938
|
});
|
|
2881
|
-
import { resolve as resolve4, join as join9, dirname, relative } from "path";
|
|
2939
|
+
import { resolve as resolve4, join as join9, dirname as dirname2, relative } from "path";
|
|
2882
2940
|
import { existsSync as existsSync10, readFileSync, writeFileSync as writeFileSync2, readdirSync as readdirSync4, statSync } from "fs";
|
|
2883
2941
|
function bumpVersion(current, type) {
|
|
2884
2942
|
if (/^\d+\.\d+\.\d+$/.test(type))
|
|
@@ -2931,6 +2989,26 @@ function setVersion(obj, newVersion) {
|
|
|
2931
2989
|
}
|
|
2932
2990
|
return false;
|
|
2933
2991
|
}
|
|
2992
|
+
function bumpPluginEntriesVersions(plugins, bumpType) {
|
|
2993
|
+
if (!Array.isArray(plugins))
|
|
2994
|
+
return 0;
|
|
2995
|
+
let changed = 0;
|
|
2996
|
+
for (const p of plugins) {
|
|
2997
|
+
if (p && typeof p === "object") {
|
|
2998
|
+
const currentVer = typeof p.version === "string" ? p.version : undefined;
|
|
2999
|
+
if (currentVer) {
|
|
3000
|
+
try {
|
|
3001
|
+
const nextVer = bumpVersion(currentVer, bumpType);
|
|
3002
|
+
if (currentVer !== nextVer) {
|
|
3003
|
+
p.version = nextVer;
|
|
3004
|
+
changed++;
|
|
3005
|
+
}
|
|
3006
|
+
} catch {}
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
return changed;
|
|
3011
|
+
}
|
|
2934
3012
|
function walkForTargets(dir, maxDepth = 6, currentDepth = 0) {
|
|
2935
3013
|
const results = [];
|
|
2936
3014
|
if (currentDepth > maxDepth)
|
|
@@ -2954,9 +3032,9 @@ function walkForTargets(dir, maxDepth = 6, currentDepth = 0) {
|
|
|
2954
3032
|
results.push(...sub);
|
|
2955
3033
|
} else if (st.isFile()) {
|
|
2956
3034
|
if (entry === "plugin.json") {
|
|
2957
|
-
const parentDir =
|
|
3035
|
+
const parentDir = dirname2(full);
|
|
2958
3036
|
const parentName = parentDir.split(/[/\\]/).pop();
|
|
2959
|
-
if (parentName === ".claude-plugin" || parentName === ".codex-plugin" || parentName === ".cursor-plugin") {
|
|
3037
|
+
if (parentName === ".claude-plugin" || parentName === ".codex-plugin" || parentName === ".cursor-plugin" || parentName === ".github") {
|
|
2960
3038
|
results.push({
|
|
2961
3039
|
file: full,
|
|
2962
3040
|
kind: "plugin",
|
|
@@ -2985,7 +3063,7 @@ var init_bump = __esm(() => {
|
|
|
2985
3063
|
bump_default = defineCommand({
|
|
2986
3064
|
meta: {
|
|
2987
3065
|
name: "bump",
|
|
2988
|
-
description: "Bump semver versions in plugin.json (manifests) and marketplace.json files (supports Claude, Codex, Cursor)"
|
|
3066
|
+
description: "Bump semver versions in plugin.json (manifests) and marketplace.json files (supports Claude, Codex, Cursor, Copilot)"
|
|
2989
3067
|
},
|
|
2990
3068
|
args: {
|
|
2991
3069
|
type: {
|
|
@@ -3030,7 +3108,7 @@ var init_bump = __esm(() => {
|
|
|
3030
3108
|
}
|
|
3031
3109
|
ui.heading("doraval bump");
|
|
3032
3110
|
ui.info(` scanning: ${root}`);
|
|
3033
|
-
ui.info(` scope: ${scope} (use --only plugin or --only marketplace to narrow; Cursor metadata.version supported)`);
|
|
3111
|
+
ui.info(` scope: ${scope} (use --only plugin or --only marketplace to narrow; Cursor/Copilot metadata.version supported)`);
|
|
3034
3112
|
const discovered = walkForTargets(root);
|
|
3035
3113
|
let targets = discovered;
|
|
3036
3114
|
if (scope === "plugin") {
|
|
@@ -3045,14 +3123,15 @@ var init_bump = __esm(() => {
|
|
|
3045
3123
|
ui.info(" \u2022 **/.claude-plugin/plugin.json");
|
|
3046
3124
|
ui.info(" \u2022 **/.codex-plugin/plugin.json");
|
|
3047
3125
|
ui.info(" \u2022 **/.cursor-plugin/plugin.json (or marketplace.json)");
|
|
3048
|
-
ui.info(" \u2022
|
|
3126
|
+
ui.info(" \u2022 **/.github/plugin/plugin.json (or marketplace.json)");
|
|
3127
|
+
ui.info(" \u2022 **/marketplace.json (top-level/metadata.version + versions inside plugins[] for Cursor/Copilot)");
|
|
3049
3128
|
ui.info("");
|
|
3050
3129
|
ui.info(" Tip: run from inside a plugin directory, or pass a path that contains plugins/.");
|
|
3051
3130
|
ui.info(" Examples:");
|
|
3052
3131
|
ui.info(" dora bump minor");
|
|
3053
3132
|
ui.info(" dora bump minor ./my-claude-plugin");
|
|
3054
3133
|
ui.info(" dora bump --only plugin . # only the manifests");
|
|
3055
|
-
ui.info(" dora bump --only marketplace ./marketplaces-root #
|
|
3134
|
+
ui.info(" dora bump --only marketplace ./marketplaces-root # bumps metadata.version + plugins[].version (Copilot/Cursor)");
|
|
3056
3135
|
process.exit(1);
|
|
3057
3136
|
}
|
|
3058
3137
|
ui.info(` matched ${targets.length} file(s)`);
|
|
@@ -3072,18 +3151,33 @@ var init_bump = __esm(() => {
|
|
|
3072
3151
|
process.exit(1);
|
|
3073
3152
|
}
|
|
3074
3153
|
const relPath = relative(root, t.file);
|
|
3075
|
-
|
|
3154
|
+
const rootUnchanged = current === next;
|
|
3155
|
+
let innerChanged = 0;
|
|
3156
|
+
if (t.kind === "marketplace" && Array.isArray(json.plugins)) {
|
|
3157
|
+
innerChanged = bumpPluginEntriesVersions(json.plugins, rawType);
|
|
3158
|
+
}
|
|
3159
|
+
if (rootUnchanged && innerChanged === 0) {
|
|
3076
3160
|
ui.dim(` \u2022 ${t.label} ${current || "(no version)"} (no change) [${relPath}]`);
|
|
3077
3161
|
continue;
|
|
3078
3162
|
}
|
|
3079
|
-
const
|
|
3080
|
-
|
|
3163
|
+
const didRootUpdate = setVersion(json, next);
|
|
3164
|
+
const didAnyUpdate = didRootUpdate || innerChanged > 0;
|
|
3165
|
+
if (!didAnyUpdate) {
|
|
3081
3166
|
ui.warnItem(`skipped (could not locate version field to update): ${relPath}`);
|
|
3082
3167
|
continue;
|
|
3083
3168
|
}
|
|
3084
3169
|
writeJson(t.file, json);
|
|
3085
|
-
|
|
3170
|
+
if (didRootUpdate && current) {
|
|
3171
|
+
ui.success(`${t.label}: ${import_picocolors11.default.dim(current)} \u2192 ${import_picocolors11.default.green(next)}`);
|
|
3172
|
+
} else if (didRootUpdate) {
|
|
3173
|
+
ui.success(`${t.label}: ${import_picocolors11.default.green(next)}`);
|
|
3174
|
+
} else {
|
|
3175
|
+
ui.success(`${t.label} (no root version)`);
|
|
3176
|
+
}
|
|
3086
3177
|
ui.info(` ${relPath}`);
|
|
3178
|
+
if (innerChanged > 0) {
|
|
3179
|
+
ui.info(` + bumped ${innerChanged} entry version(s) inside plugins[]`);
|
|
3180
|
+
}
|
|
3087
3181
|
bumpedCount++;
|
|
3088
3182
|
}
|
|
3089
3183
|
ui.blank();
|
|
@@ -3102,9 +3196,10 @@ var init_bump = __esm(() => {
|
|
|
3102
3196
|
import { existsSync as existsSync11, readdirSync as readdirSync5 } from "fs";
|
|
3103
3197
|
import { join as join10 } from "path";
|
|
3104
3198
|
function detectContext2(cwd = process.cwd()) {
|
|
3199
|
+
const codexSpec = getProviderSpec("codex");
|
|
3105
3200
|
const hasCodexDir = existsSync11(join10(cwd, ".codex"));
|
|
3106
|
-
const hasPluginManifest = existsSync11(join10(cwd,
|
|
3107
|
-
const hasMarketplace = existsSync11(join10(cwd, ".agents", "plugins", "marketplace.json")) || existsSync11(join10(cwd,
|
|
3201
|
+
const hasPluginManifest = existsSync11(join10(cwd, codexSpec.manifestPath));
|
|
3202
|
+
const hasMarketplace = existsSync11(join10(cwd, ".agents", "plugins", "marketplace.json")) || existsSync11(join10(cwd, codexSpec.manifestPath));
|
|
3108
3203
|
let looseSkillFiles = [];
|
|
3109
3204
|
try {
|
|
3110
3205
|
const files = readdirSync5(cwd);
|
|
@@ -3127,7 +3222,9 @@ function detectContext2(cwd = process.cwd()) {
|
|
|
3127
3222
|
isEmpty
|
|
3128
3223
|
};
|
|
3129
3224
|
}
|
|
3130
|
-
var init_context2 = () => {
|
|
3225
|
+
var init_context2 = __esm(() => {
|
|
3226
|
+
init_spec();
|
|
3227
|
+
});
|
|
3131
3228
|
|
|
3132
3229
|
// src/cli/commands/codex/new.ts
|
|
3133
3230
|
var exports_new2 = {};
|
|
@@ -3136,7 +3233,7 @@ __export(exports_new2, {
|
|
|
3136
3233
|
default: () => new_default2,
|
|
3137
3234
|
decidePath: () => decidePath2
|
|
3138
3235
|
});
|
|
3139
|
-
import { join as join11, basename as basename3 } from "path";
|
|
3236
|
+
import { join as join11, basename as basename3, dirname as dirname3 } from "path";
|
|
3140
3237
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, existsSync as existsSync12 } from "fs";
|
|
3141
3238
|
function decidePath2(ctx, intent, providedName) {
|
|
3142
3239
|
const rawName = providedName || "";
|
|
@@ -3186,6 +3283,8 @@ function scaffold2(decision, ctx, migrateContent) {
|
|
|
3186
3283
|
}
|
|
3187
3284
|
if (path === "plugin") {
|
|
3188
3285
|
const pluginName = basename3(targetDir);
|
|
3286
|
+
const codexSpec = getProviderSpec("codex");
|
|
3287
|
+
const codexManifestDir = dirname3(codexSpec.manifestPath);
|
|
3189
3288
|
const pluginJson = {
|
|
3190
3289
|
name: pluginName,
|
|
3191
3290
|
version: "0.1.0",
|
|
@@ -3197,9 +3296,10 @@ function scaffold2(decision, ctx, migrateContent) {
|
|
|
3197
3296
|
category: "Productivity"
|
|
3198
3297
|
}
|
|
3199
3298
|
};
|
|
3200
|
-
mkdirSync3(join11(targetDir,
|
|
3201
|
-
writeFileSync3(join11(targetDir,
|
|
3202
|
-
|
|
3299
|
+
mkdirSync3(join11(targetDir, codexManifestDir), { recursive: true });
|
|
3300
|
+
writeFileSync3(join11(targetDir, codexSpec.manifestPath), JSON.stringify(pluginJson, null, 2));
|
|
3301
|
+
const marketplaceDir = dirname3(codexSpec.marketplacePath);
|
|
3302
|
+
mkdirSync3(join11(targetDir, marketplaceDir), { recursive: true });
|
|
3203
3303
|
const marketplaceJson = {
|
|
3204
3304
|
name: "local",
|
|
3205
3305
|
interface: {
|
|
@@ -3220,7 +3320,7 @@ function scaffold2(decision, ctx, migrateContent) {
|
|
|
3220
3320
|
}
|
|
3221
3321
|
]
|
|
3222
3322
|
};
|
|
3223
|
-
writeFileSync3(join11(targetDir,
|
|
3323
|
+
writeFileSync3(join11(targetDir, codexSpec.marketplacePath), JSON.stringify(marketplaceJson, null, 2));
|
|
3224
3324
|
const demoSkillName = "doraval";
|
|
3225
3325
|
mkdirSync3(join11(targetDir, "skills", demoSkillName), { recursive: true });
|
|
3226
3326
|
let skillContent;
|
|
@@ -3279,6 +3379,7 @@ var init_new2 = __esm(() => {
|
|
|
3279
3379
|
init_out();
|
|
3280
3380
|
init_context2();
|
|
3281
3381
|
init_prompt();
|
|
3382
|
+
init_spec();
|
|
3282
3383
|
import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
3283
3384
|
new_default2 = defineCommand({
|
|
3284
3385
|
meta: {
|
|
@@ -3336,176 +3437,1560 @@ var init_new2 = __esm(() => {
|
|
|
3336
3437
|
});
|
|
3337
3438
|
});
|
|
3338
3439
|
|
|
3339
|
-
// src/
|
|
3340
|
-
import { existsSync as existsSync13 } from "fs";
|
|
3341
|
-
import {
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
}
|
|
3440
|
+
// src/cli/commands/cursor/context.ts
|
|
3441
|
+
import { existsSync as existsSync13, readdirSync as readdirSync6 } from "fs";
|
|
3442
|
+
import { join as join12 } from "path";
|
|
3443
|
+
function detectContext3(cwd = process.cwd()) {
|
|
3444
|
+
const hasCursorDir = existsSync13(join12(cwd, ".cursor"));
|
|
3445
|
+
const hasPluginManifest = existsSync13(join12(cwd, ".cursor-plugin", "plugin.json"));
|
|
3446
|
+
let looseSkillFiles = [];
|
|
3447
|
+
try {
|
|
3448
|
+
const files = readdirSync6(cwd);
|
|
3449
|
+
looseSkillFiles = files.filter((f) => {
|
|
3450
|
+
if (!f.endsWith(".md") || f.startsWith("."))
|
|
3451
|
+
return false;
|
|
3452
|
+
const lower = f.toLowerCase();
|
|
3453
|
+
if (lower === "readme.md" || lower === "changelog.md" || lower === "license.md" || lower.includes("contributing"))
|
|
3454
|
+
return false;
|
|
3455
|
+
return lower.includes("skill") || lower === "skill.md";
|
|
3456
|
+
});
|
|
3457
|
+
} catch {}
|
|
3458
|
+
const isEmpty = !hasCursorDir && !hasPluginManifest && looseSkillFiles.length === 0;
|
|
3459
|
+
return {
|
|
3460
|
+
cwd,
|
|
3461
|
+
hasCursorDir,
|
|
3462
|
+
hasPluginManifest,
|
|
3463
|
+
looseSkillFiles,
|
|
3464
|
+
isEmpty
|
|
3365
3465
|
};
|
|
3366
|
-
}
|
|
3466
|
+
}
|
|
3467
|
+
var init_context3 = () => {};
|
|
3367
3468
|
|
|
3368
|
-
// src/
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3469
|
+
// src/cli/commands/cursor/new.ts
|
|
3470
|
+
var exports_new3 = {};
|
|
3471
|
+
__export(exports_new3, {
|
|
3472
|
+
scaffold: () => scaffold3,
|
|
3473
|
+
default: () => new_default3,
|
|
3474
|
+
decidePath: () => decidePath3
|
|
3475
|
+
});
|
|
3476
|
+
import { join as join13, basename as basename4, dirname as dirname4 } from "path";
|
|
3477
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, existsSync as existsSync14 } from "fs";
|
|
3478
|
+
function decidePath3(ctx, intent, providedName) {
|
|
3479
|
+
const rawName = providedName || "";
|
|
3480
|
+
let decisionPath = "standalone";
|
|
3481
|
+
let targetDir = ctx.cwd;
|
|
3482
|
+
let shouldCreateDir = false;
|
|
3483
|
+
let migrateExisting = false;
|
|
3484
|
+
const useCurrentDirAsRoot = rawName === "." || rawName === basename4(ctx.cwd) || !rawName;
|
|
3485
|
+
if (intent === "distribute" || intent === "self-later" && ctx.looseSkillFiles.length > 0 && !ctx.hasPluginManifest) {
|
|
3486
|
+
decisionPath = "plugin";
|
|
3487
|
+
if (useCurrentDirAsRoot) {
|
|
3488
|
+
targetDir = ctx.cwd;
|
|
3489
|
+
shouldCreateDir = false;
|
|
3490
|
+
} else {
|
|
3491
|
+
targetDir = join13(ctx.cwd, rawName);
|
|
3492
|
+
shouldCreateDir = true;
|
|
3493
|
+
}
|
|
3494
|
+
migrateExisting = ctx.looseSkillFiles.length > 0;
|
|
3495
|
+
} else if (intent === "self-later" && !ctx.hasPluginManifest) {
|
|
3496
|
+
decisionPath = "plugin";
|
|
3497
|
+
if (useCurrentDirAsRoot) {
|
|
3498
|
+
targetDir = ctx.cwd;
|
|
3499
|
+
shouldCreateDir = false;
|
|
3500
|
+
} else {
|
|
3501
|
+
targetDir = join13(ctx.cwd, rawName);
|
|
3502
|
+
shouldCreateDir = true;
|
|
3503
|
+
}
|
|
3504
|
+
} else if (decisionPath === "standalone") {
|
|
3505
|
+
if (useCurrentDirAsRoot) {
|
|
3506
|
+
targetDir = ctx.cwd;
|
|
3507
|
+
shouldCreateDir = false;
|
|
3508
|
+
} else {
|
|
3509
|
+
targetDir = join13(ctx.cwd, rawName);
|
|
3510
|
+
shouldCreateDir = true;
|
|
3394
3511
|
}
|
|
3395
3512
|
}
|
|
3396
|
-
return
|
|
3513
|
+
return { path: decisionPath, targetDir, shouldCreateDir, migrateExisting };
|
|
3397
3514
|
}
|
|
3398
|
-
function
|
|
3399
|
-
const
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3515
|
+
function scaffold3(decision, ctx, migrateContent) {
|
|
3516
|
+
const { targetDir, path, shouldCreateDir } = decision;
|
|
3517
|
+
if (existsSync14(targetDir) && shouldCreateDir) {
|
|
3518
|
+
ui.fail("Target already exists");
|
|
3519
|
+
process.exit(1);
|
|
3520
|
+
}
|
|
3521
|
+
if (shouldCreateDir) {
|
|
3522
|
+
mkdirSync4(targetDir, { recursive: true });
|
|
3523
|
+
}
|
|
3524
|
+
if (path === "plugin") {
|
|
3525
|
+
const pluginName = basename4(targetDir);
|
|
3526
|
+
const cursorSpec = getProviderSpec("cursor");
|
|
3527
|
+
const cursorManifestDir = dirname4(cursorSpec.manifestPath);
|
|
3528
|
+
const pluginJson = {
|
|
3529
|
+
name: pluginName,
|
|
3530
|
+
version: "0.1.0",
|
|
3531
|
+
description: "Scaffolded by doraval cursor new",
|
|
3532
|
+
skills: "./skills/",
|
|
3533
|
+
displayName: pluginName
|
|
3534
|
+
};
|
|
3535
|
+
mkdirSync4(join13(targetDir, cursorManifestDir), { recursive: true });
|
|
3536
|
+
writeFileSync4(join13(targetDir, cursorSpec.manifestPath), JSON.stringify(pluginJson, null, 2));
|
|
3537
|
+
const marketplaceDir = dirname4(cursorSpec.marketplacePath);
|
|
3538
|
+
mkdirSync4(join13(targetDir, marketplaceDir), { recursive: true });
|
|
3539
|
+
const marketplaceJson = {
|
|
3540
|
+
name: pluginName,
|
|
3541
|
+
version: "0.1.0",
|
|
3542
|
+
description: "Scaffolded by doraval cursor new",
|
|
3543
|
+
author: { name: "" },
|
|
3544
|
+
homepage: "",
|
|
3545
|
+
repository: "",
|
|
3546
|
+
license: "MIT",
|
|
3547
|
+
keywords: ["cursor", "skills", "plugin"]
|
|
3548
|
+
};
|
|
3549
|
+
writeFileSync4(join13(targetDir, cursorSpec.marketplacePath), JSON.stringify(marketplaceJson, null, 2));
|
|
3550
|
+
const demoSkillName = "doraval";
|
|
3551
|
+
mkdirSync4(join13(targetDir, "skills", demoSkillName), { recursive: true });
|
|
3552
|
+
let skillContent;
|
|
3553
|
+
if (migrateContent) {
|
|
3554
|
+
skillContent = migrateContent;
|
|
3555
|
+
} else {
|
|
3556
|
+
skillContent = `---
|
|
3557
|
+
name: ${demoSkillName}
|
|
3558
|
+
description: Use doraval to validate, measure drift, and judge skills and plugins. Use when authoring or reviewing context engineering artifacts for AI coding agents (works for Cursor too).
|
|
3559
|
+
---
|
|
3560
|
+
|
|
3561
|
+
# Use Doraval (Cursor edition)
|
|
3562
|
+
|
|
3563
|
+
Doraval is the context engineering toolkit.
|
|
3564
|
+
|
|
3565
|
+
When you need to check a skill or Cursor plugin:
|
|
3566
|
+
|
|
3567
|
+
- Validate the current directory: \`doraval validate .\`
|
|
3568
|
+
- Validate one skill: \`doraval skill validate ./skills/${demoSkillName}/\`
|
|
3569
|
+
- Check for rubric drift: \`doraval skill drift ./skills/${demoSkillName}/\`
|
|
3570
|
+
- Get an AI quality judgment: \`doraval skill judge ./skills/${demoSkillName}/\`
|
|
3571
|
+
|
|
3572
|
+
Always run \`doraval validate\` before sharing or publishing a plugin.
|
|
3573
|
+
|
|
3574
|
+
This skill demonstrates a complete, self-referential example of using doraval inside a generated Cursor plugin.
|
|
3575
|
+
|
|
3576
|
+
To test in Cursor:
|
|
3577
|
+
1. Open the plugin directory or add via marketplace.
|
|
3578
|
+
2. The demo skill will be available.`;
|
|
3579
|
+
}
|
|
3580
|
+
writeFileSync4(join13(targetDir, "skills", demoSkillName, "SKILL.md"), skillContent);
|
|
3581
|
+
const readmePath = join13(targetDir, "README.md");
|
|
3582
|
+
if (!existsSync14(readmePath)) {
|
|
3583
|
+
writeFileSync4(readmePath, "# " + pluginName + `
|
|
3584
|
+
|
|
3585
|
+
Cursor plugin scaffolded by doraval.`);
|
|
3586
|
+
}
|
|
3587
|
+
} else {
|
|
3588
|
+
mkdirSync4(join13(targetDir, "skills", "doraval"), { recursive: true });
|
|
3589
|
+
const skillBody = migrateContent || `# My Skill
|
|
3590
|
+
|
|
3591
|
+
Basic starter for Cursor.`;
|
|
3592
|
+
writeFileSync4(join13(targetDir, "skills", "doraval", "SKILL.md"), `---
|
|
3593
|
+
name: doraval
|
|
3594
|
+
description: Starter (local skill)
|
|
3595
|
+
---
|
|
3596
|
+
|
|
3597
|
+
${skillBody}`);
|
|
3407
3598
|
}
|
|
3408
|
-
if (lower === "licence")
|
|
3409
|
-
return "license";
|
|
3410
|
-
if (lower === "dependancies" || lower === "deps")
|
|
3411
|
-
return "dependencies";
|
|
3412
|
-
if (lower === "mcp" || lower === "mcpservers")
|
|
3413
|
-
return "mcpServers";
|
|
3414
|
-
if (lower === "lsp")
|
|
3415
|
-
return "lspServers";
|
|
3416
|
-
if (lower === "outputstyles" || lower === "styles")
|
|
3417
|
-
return "outputStyles";
|
|
3418
|
-
if (lower === "userconfig")
|
|
3419
|
-
return "userConfig";
|
|
3420
|
-
return null;
|
|
3421
|
-
}
|
|
3422
|
-
function isRelativePathLike(v) {
|
|
3423
|
-
if (typeof v !== "string")
|
|
3424
|
-
return false;
|
|
3425
|
-
return RELATIVE_PATH_REGEX.test(v) && !v.includes("..");
|
|
3426
3599
|
}
|
|
3427
|
-
var
|
|
3428
|
-
var
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
"repository",
|
|
3440
|
-
"license",
|
|
3441
|
-
"keywords",
|
|
3442
|
-
"defaultEnabled",
|
|
3443
|
-
"skills",
|
|
3444
|
-
"commands",
|
|
3445
|
-
"agents",
|
|
3446
|
-
"hooks",
|
|
3447
|
-
"mcpServers",
|
|
3448
|
-
"outputStyles",
|
|
3449
|
-
"lspServers",
|
|
3450
|
-
"experimental",
|
|
3451
|
-
"userConfig",
|
|
3452
|
-
"channels",
|
|
3453
|
-
"dependencies"
|
|
3454
|
-
]);
|
|
3455
|
-
REPLACES_DEFAULT = new Set(["commands", "agents", "outputStyles", "lspServers"]);
|
|
3456
|
-
claudePluginValidator = {
|
|
3457
|
-
id: "claude:plugin",
|
|
3458
|
-
provider: "claude",
|
|
3459
|
-
name: "Claude Plugin",
|
|
3460
|
-
description: "Validates .claude-plugin/plugin.json manifest (complete schema per Plugins reference), component path rules (replace vs augment), .claude-plugin/ purity, default dirs, single-root-skill layout, unrecognized fields + suggestions, and structure",
|
|
3461
|
-
detect(dir) {
|
|
3462
|
-
return existsSync14(resolve6(dir, ".claude-plugin", "plugin.json"));
|
|
3600
|
+
var import_picocolors13, new_default3;
|
|
3601
|
+
var init_new3 = __esm(() => {
|
|
3602
|
+
init_dist();
|
|
3603
|
+
init_out();
|
|
3604
|
+
init_context3();
|
|
3605
|
+
init_prompt();
|
|
3606
|
+
init_spec();
|
|
3607
|
+
import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
3608
|
+
new_default3 = defineCommand({
|
|
3609
|
+
meta: {
|
|
3610
|
+
name: "new",
|
|
3611
|
+
description: "Create a new skill or plugin following Cursor packaging rules"
|
|
3463
3612
|
},
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3613
|
+
args: {
|
|
3614
|
+
name: {
|
|
3615
|
+
type: "positional",
|
|
3616
|
+
description: "Optional name for the skill or plugin",
|
|
3617
|
+
required: false
|
|
3618
|
+
},
|
|
3619
|
+
yes: {
|
|
3620
|
+
type: "boolean",
|
|
3621
|
+
description: "Skip interactive prompts (use defaults and flags)",
|
|
3622
|
+
default: false
|
|
3623
|
+
},
|
|
3624
|
+
intent: {
|
|
3625
|
+
type: "string",
|
|
3626
|
+
description: 'Intent: "self" | "self-later" | "distribute"',
|
|
3627
|
+
required: false
|
|
3628
|
+
}
|
|
3629
|
+
},
|
|
3630
|
+
run({ args }) {
|
|
3631
|
+
ui.heading("doraval cursor new \u2014 Context-aware scaffolding");
|
|
3632
|
+
const ctx = detectContext3();
|
|
3633
|
+
let intent = args.intent || "self-later";
|
|
3634
|
+
if (!args.yes) {
|
|
3635
|
+
const ans = prompt(" Intent (self | self-later | distribute)", intent);
|
|
3636
|
+
intent = ans || intent;
|
|
3637
|
+
}
|
|
3638
|
+
const decision = decidePath3(ctx, intent, args.name);
|
|
3639
|
+
ui.info(` Decision: path=${decision.path}, target=${decision.targetDir}`);
|
|
3640
|
+
let migrateContent;
|
|
3641
|
+
if (decision.migrateExisting && !args.yes) {
|
|
3642
|
+
migrateContent = "Content from your existing SKILL.md (user-confirmed).";
|
|
3643
|
+
}
|
|
3644
|
+
scaffold3(decision, ctx, migrateContent);
|
|
3645
|
+
ui.write(`
|
|
3646
|
+
${import_picocolors13.default.green("\u2713")} Created ${decision.path} at ${import_picocolors13.default.bold(decision.targetDir)}`);
|
|
3647
|
+
const cmdName = decision.path === "plugin" ? `/${basename4(decision.targetDir)}:doraval` : "/doraval (local skill)";
|
|
3648
|
+
ui.info(` Command: ${cmdName}`);
|
|
3649
|
+
if (decision.path === "plugin") {
|
|
3650
|
+
ui.info(` Cursor manifest: .cursor-plugin/plugin.json`);
|
|
3651
|
+
ui.info(` Marketplace catalog: .cursor-plugin/marketplace.json`);
|
|
3652
|
+
}
|
|
3653
|
+
ui.info(` Test (local): add the plugin dir in Cursor settings or use local skills`);
|
|
3654
|
+
ui.info(` Validate: doraval validate ${decision.targetDir}`);
|
|
3655
|
+
if (decision.path === "plugin" && decision.migrateExisting) {
|
|
3656
|
+
ui.info(" (Existing content migrated where confirmed.)");
|
|
3657
|
+
}
|
|
3658
|
+
process.exit(0);
|
|
3659
|
+
}
|
|
3660
|
+
});
|
|
3661
|
+
});
|
|
3662
|
+
|
|
3663
|
+
// src/cli/commands/copilot/context.ts
|
|
3664
|
+
import { existsSync as existsSync15, readdirSync as readdirSync7 } from "fs";
|
|
3665
|
+
import { join as join14 } from "path";
|
|
3666
|
+
function detectContext4(cwd = process.cwd()) {
|
|
3667
|
+
const hasGithubDir = existsSync15(join14(cwd, ".github"));
|
|
3668
|
+
const hasPluginManifest = existsSync15(join14(cwd, ".github", "plugin", "plugin.json"));
|
|
3669
|
+
let looseSkillFiles = [];
|
|
3670
|
+
try {
|
|
3671
|
+
const files = readdirSync7(cwd);
|
|
3672
|
+
looseSkillFiles = files.filter((f) => {
|
|
3673
|
+
if (!f.endsWith(".md") || f.startsWith("."))
|
|
3674
|
+
return false;
|
|
3675
|
+
const lower = f.toLowerCase();
|
|
3676
|
+
if (lower === "readme.md" || lower === "changelog.md" || lower === "license.md" || lower.includes("contributing"))
|
|
3677
|
+
return false;
|
|
3678
|
+
return lower.includes("skill") || lower === "skill.md";
|
|
3679
|
+
});
|
|
3680
|
+
} catch {}
|
|
3681
|
+
const isEmpty = !hasGithubDir && !hasPluginManifest && looseSkillFiles.length === 0;
|
|
3682
|
+
return {
|
|
3683
|
+
cwd,
|
|
3684
|
+
hasGithubDir,
|
|
3685
|
+
hasPluginManifest,
|
|
3686
|
+
looseSkillFiles,
|
|
3687
|
+
isEmpty
|
|
3688
|
+
};
|
|
3689
|
+
}
|
|
3690
|
+
var init_context4 = () => {};
|
|
3691
|
+
|
|
3692
|
+
// src/cli/commands/copilot/new.ts
|
|
3693
|
+
var exports_new4 = {};
|
|
3694
|
+
__export(exports_new4, {
|
|
3695
|
+
scaffold: () => scaffold4,
|
|
3696
|
+
default: () => new_default4,
|
|
3697
|
+
decidePath: () => decidePath4
|
|
3698
|
+
});
|
|
3699
|
+
import { join as join15, basename as basename5, dirname as dirname5 } from "path";
|
|
3700
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync5, existsSync as existsSync16 } from "fs";
|
|
3701
|
+
function decidePath4(ctx, intent, providedName) {
|
|
3702
|
+
const rawName = providedName || "";
|
|
3703
|
+
let decisionPath = "standalone";
|
|
3704
|
+
let targetDir = ctx.cwd;
|
|
3705
|
+
let shouldCreateDir = false;
|
|
3706
|
+
let migrateExisting = false;
|
|
3707
|
+
const useCurrentDirAsRoot = rawName === "." || rawName === basename5(ctx.cwd) || !rawName;
|
|
3708
|
+
if (intent === "distribute" || intent === "self-later" && ctx.looseSkillFiles.length > 0 && !ctx.hasPluginManifest) {
|
|
3709
|
+
decisionPath = "plugin";
|
|
3710
|
+
if (useCurrentDirAsRoot) {
|
|
3711
|
+
targetDir = ctx.cwd;
|
|
3712
|
+
shouldCreateDir = false;
|
|
3713
|
+
} else {
|
|
3714
|
+
targetDir = join15(ctx.cwd, rawName);
|
|
3715
|
+
shouldCreateDir = true;
|
|
3716
|
+
}
|
|
3717
|
+
migrateExisting = ctx.looseSkillFiles.length > 0;
|
|
3718
|
+
} else if (intent === "self-later" && !ctx.hasPluginManifest) {
|
|
3719
|
+
decisionPath = "plugin";
|
|
3720
|
+
if (useCurrentDirAsRoot) {
|
|
3721
|
+
targetDir = ctx.cwd;
|
|
3722
|
+
shouldCreateDir = false;
|
|
3723
|
+
} else {
|
|
3724
|
+
targetDir = join15(ctx.cwd, rawName);
|
|
3725
|
+
shouldCreateDir = true;
|
|
3726
|
+
}
|
|
3727
|
+
} else if (decisionPath === "standalone") {
|
|
3728
|
+
if (useCurrentDirAsRoot) {
|
|
3729
|
+
targetDir = ctx.cwd;
|
|
3730
|
+
shouldCreateDir = false;
|
|
3731
|
+
} else {
|
|
3732
|
+
targetDir = join15(ctx.cwd, rawName);
|
|
3733
|
+
shouldCreateDir = true;
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
return { path: decisionPath, targetDir, shouldCreateDir, migrateExisting };
|
|
3737
|
+
}
|
|
3738
|
+
function scaffold4(decision, ctx, migrateContent) {
|
|
3739
|
+
const { targetDir, path, shouldCreateDir } = decision;
|
|
3740
|
+
if (existsSync16(targetDir) && shouldCreateDir) {
|
|
3741
|
+
ui.fail("Target already exists");
|
|
3742
|
+
process.exit(1);
|
|
3743
|
+
}
|
|
3744
|
+
if (shouldCreateDir) {
|
|
3745
|
+
mkdirSync5(targetDir, { recursive: true });
|
|
3746
|
+
}
|
|
3747
|
+
if (path === "plugin") {
|
|
3748
|
+
const pluginName = basename5(targetDir);
|
|
3749
|
+
const copilotSpec = getProviderSpec("copilot");
|
|
3750
|
+
const copilotManifestDir = dirname5(copilotSpec.manifestPath);
|
|
3751
|
+
const pluginJson = {
|
|
3752
|
+
name: pluginName,
|
|
3753
|
+
version: "0.1.0",
|
|
3754
|
+
description: "Scaffolded by doraval copilot new",
|
|
3755
|
+
skills: ["./skills/doraval"],
|
|
3756
|
+
displayName: pluginName
|
|
3757
|
+
};
|
|
3758
|
+
mkdirSync5(join15(targetDir, copilotManifestDir), { recursive: true });
|
|
3759
|
+
writeFileSync5(join15(targetDir, copilotSpec.manifestPath), JSON.stringify(pluginJson, null, 2));
|
|
3760
|
+
const marketplaceDir = dirname5(copilotSpec.marketplacePath);
|
|
3761
|
+
mkdirSync5(join15(targetDir, marketplaceDir), { recursive: true });
|
|
3762
|
+
const marketplaceJson = {
|
|
3763
|
+
name: "local",
|
|
3764
|
+
plugins: [
|
|
3765
|
+
{
|
|
3766
|
+
name: pluginName,
|
|
3767
|
+
source: {
|
|
3768
|
+
source: "local",
|
|
3769
|
+
path: "."
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
]
|
|
3773
|
+
};
|
|
3774
|
+
writeFileSync5(join15(targetDir, copilotSpec.marketplacePath), JSON.stringify(marketplaceJson, null, 2));
|
|
3775
|
+
const demoSkillName = "doraval";
|
|
3776
|
+
mkdirSync5(join15(targetDir, "skills", demoSkillName), { recursive: true });
|
|
3777
|
+
let skillContent;
|
|
3778
|
+
if (migrateContent) {
|
|
3779
|
+
skillContent = migrateContent;
|
|
3780
|
+
} else {
|
|
3781
|
+
skillContent = `---
|
|
3782
|
+
name: ${demoSkillName}
|
|
3783
|
+
description: Use doraval to validate, measure drift, and judge skills and plugins. Use when authoring or reviewing context engineering artifacts for AI coding agents (works for Copilot too).
|
|
3784
|
+
---
|
|
3785
|
+
|
|
3786
|
+
# Use Doraval (Copilot edition)
|
|
3787
|
+
|
|
3788
|
+
Doraval is the context engineering toolkit.
|
|
3789
|
+
|
|
3790
|
+
When you need to check a skill or Copilot plugin:
|
|
3791
|
+
|
|
3792
|
+
- Validate the current directory: \`doraval validate .\`
|
|
3793
|
+
- Validate one skill: \`doraval skill validate ./skills/${demoSkillName}/\`
|
|
3794
|
+
- Check for rubric drift: \`doraval skill drift ./skills/${demoSkillName}/\`
|
|
3795
|
+
- Get an AI quality judgment: \`doraval skill judge ./skills/${demoSkillName}/\`
|
|
3796
|
+
|
|
3797
|
+
Always run \`doraval validate\` before sharing or publishing a plugin.
|
|
3798
|
+
|
|
3799
|
+
This skill demonstrates a complete, self-referential example of using doraval inside a generated Copilot plugin.
|
|
3800
|
+
|
|
3801
|
+
To test in Copilot:
|
|
3802
|
+
1. Configure the .github/plugin as local source.
|
|
3803
|
+
2. Restart/reload and invoke the skill.`;
|
|
3804
|
+
}
|
|
3805
|
+
writeFileSync5(join15(targetDir, "skills", demoSkillName, "SKILL.md"), skillContent);
|
|
3806
|
+
const readmePath = join15(targetDir, "README.md");
|
|
3807
|
+
if (!existsSync16(readmePath)) {
|
|
3808
|
+
writeFileSync5(readmePath, "# " + pluginName + `
|
|
3809
|
+
|
|
3810
|
+
Copilot plugin scaffolded by doraval.`);
|
|
3811
|
+
}
|
|
3812
|
+
} else {
|
|
3813
|
+
mkdirSync5(join15(targetDir, "skills", "doraval"), { recursive: true });
|
|
3814
|
+
const skillBody = migrateContent || `# My Skill
|
|
3815
|
+
|
|
3816
|
+
Basic starter for Copilot.`;
|
|
3817
|
+
writeFileSync5(join15(targetDir, "skills", "doraval", "SKILL.md"), `---
|
|
3818
|
+
name: doraval
|
|
3819
|
+
description: Starter (local skill)
|
|
3820
|
+
---
|
|
3821
|
+
|
|
3822
|
+
${skillBody}`);
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
var import_picocolors14, new_default4;
|
|
3826
|
+
var init_new4 = __esm(() => {
|
|
3827
|
+
init_dist();
|
|
3828
|
+
init_out();
|
|
3829
|
+
init_context4();
|
|
3830
|
+
init_prompt();
|
|
3831
|
+
init_spec();
|
|
3832
|
+
import_picocolors14 = __toESM(require_picocolors(), 1);
|
|
3833
|
+
new_default4 = defineCommand({
|
|
3834
|
+
meta: {
|
|
3835
|
+
name: "new",
|
|
3836
|
+
description: "Create a new skill or plugin following Copilot packaging rules"
|
|
3837
|
+
},
|
|
3838
|
+
args: {
|
|
3839
|
+
name: {
|
|
3840
|
+
type: "positional",
|
|
3841
|
+
description: "Optional name for the skill or plugin",
|
|
3842
|
+
required: false
|
|
3843
|
+
},
|
|
3844
|
+
yes: {
|
|
3845
|
+
type: "boolean",
|
|
3846
|
+
description: "Skip interactive prompts (use defaults and flags)",
|
|
3847
|
+
default: false
|
|
3848
|
+
},
|
|
3849
|
+
intent: {
|
|
3850
|
+
type: "string",
|
|
3851
|
+
description: 'Intent: "self" | "self-later" | "distribute"',
|
|
3852
|
+
required: false
|
|
3853
|
+
}
|
|
3854
|
+
},
|
|
3855
|
+
run({ args }) {
|
|
3856
|
+
ui.heading("doraval copilot new \u2014 Context-aware scaffolding");
|
|
3857
|
+
const ctx = detectContext4();
|
|
3858
|
+
let intent = args.intent || "self-later";
|
|
3859
|
+
if (!args.yes) {
|
|
3860
|
+
const ans = prompt(" Intent (self | self-later | distribute)", intent);
|
|
3861
|
+
intent = ans || intent;
|
|
3862
|
+
}
|
|
3863
|
+
const decision = decidePath4(ctx, intent, args.name);
|
|
3864
|
+
ui.info(` Decision: path=${decision.path}, target=${decision.targetDir}`);
|
|
3865
|
+
let migrateContent;
|
|
3866
|
+
if (decision.migrateExisting && !args.yes) {
|
|
3867
|
+
migrateContent = "Content from your existing SKILL.md (user-confirmed).";
|
|
3868
|
+
}
|
|
3869
|
+
scaffold4(decision, ctx, migrateContent);
|
|
3870
|
+
ui.write(`
|
|
3871
|
+
${import_picocolors14.default.green("\u2713")} Created ${decision.path} at ${import_picocolors14.default.bold(decision.targetDir)}`);
|
|
3872
|
+
const cmdName = decision.path === "plugin" ? `/${basename5(decision.targetDir)}:doraval` : "/doraval (local skill)";
|
|
3873
|
+
ui.info(` Command: ${cmdName}`);
|
|
3874
|
+
if (decision.path === "plugin") {
|
|
3875
|
+
ui.info(` Copilot manifest: .github/plugin/plugin.json`);
|
|
3876
|
+
ui.info(` Marketplace catalog: .github/plugin/marketplace.json`);
|
|
3877
|
+
}
|
|
3878
|
+
ui.info(` Test (local): configure local plugin source in Copilot and reload`);
|
|
3879
|
+
ui.info(` Validate: doraval validate ${decision.targetDir}`);
|
|
3880
|
+
if (decision.path === "plugin" && decision.migrateExisting) {
|
|
3881
|
+
ui.info(" (Existing content migrated where confirmed.)");
|
|
3882
|
+
}
|
|
3883
|
+
process.exit(0);
|
|
3884
|
+
}
|
|
3885
|
+
});
|
|
3886
|
+
});
|
|
3887
|
+
|
|
3888
|
+
// src/validators/claude/skill.ts
|
|
3889
|
+
import { existsSync as existsSync17 } from "fs";
|
|
3890
|
+
import { resolve as resolve5 } from "path";
|
|
3891
|
+
var claudeSkillValidator;
|
|
3892
|
+
var init_skill = __esm(() => {
|
|
3893
|
+
init_skill_validate();
|
|
3894
|
+
claudeSkillValidator = {
|
|
3895
|
+
id: "claude:skill",
|
|
3896
|
+
provider: "claude",
|
|
3897
|
+
name: "Claude Skill",
|
|
3898
|
+
description: "Validates SKILL.md per current Claude Code spec: frontmatter (name/description relaxed to recommended; directory name usually provides the /command), body, supporting files, dynamic injection (!`cmd`), substitutions ($ARGUMENTS, ${CLAUDE_*}), and advanced fields (allowed-tools, context, disable-model-invocation, when_to_use, etc.)",
|
|
3899
|
+
detect(dir) {
|
|
3900
|
+
return existsSync17(resolve5(dir, "SKILL.md"));
|
|
3901
|
+
},
|
|
3902
|
+
async validate(dir, _opts) {
|
|
3903
|
+
const loaded = await loadSkill(dir);
|
|
3904
|
+
if (!loaded.ok) {
|
|
3905
|
+
return {
|
|
3906
|
+
errors: [loaded.error],
|
|
3907
|
+
warnings: [],
|
|
3908
|
+
passes: []
|
|
3909
|
+
};
|
|
3910
|
+
}
|
|
3911
|
+
const { model, existingDirs } = loaded;
|
|
3912
|
+
return validateSkillModel(model, { existingDirs: [...existingDirs] });
|
|
3913
|
+
}
|
|
3914
|
+
};
|
|
3915
|
+
});
|
|
3916
|
+
|
|
3917
|
+
// src/validators/claude/plugin.ts
|
|
3918
|
+
import { existsSync as existsSync18, readdirSync as readdirSync8 } from "fs";
|
|
3919
|
+
import { resolve as resolve6, join as join16 } from "path";
|
|
3920
|
+
function levenshtein(a, b) {
|
|
3921
|
+
if (a === b)
|
|
3922
|
+
return 0;
|
|
3923
|
+
const m = a.length, n = b.length;
|
|
3924
|
+
if (m === 0)
|
|
3925
|
+
return n;
|
|
3926
|
+
if (n === 0)
|
|
3927
|
+
return m;
|
|
3928
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
3929
|
+
for (let i = 0;i <= m; i++) {
|
|
3930
|
+
const row = dp[i];
|
|
3931
|
+
row[0] = i;
|
|
3932
|
+
}
|
|
3933
|
+
for (let j = 0;j <= n; j++) {
|
|
3934
|
+
const row = dp[0];
|
|
3935
|
+
row[j] = j;
|
|
3936
|
+
}
|
|
3937
|
+
for (let i = 1;i <= m; i++) {
|
|
3938
|
+
const row = dp[i];
|
|
3939
|
+
const prev = dp[i - 1];
|
|
3940
|
+
for (let j = 1;j <= n; j++) {
|
|
3941
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
3942
|
+
row[j] = Math.min((prev[j] ?? 0) + 1, (row[j - 1] ?? 0) + 1, (prev[j - 1] ?? 0) + cost);
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
return dp[m][n];
|
|
3946
|
+
}
|
|
3947
|
+
function suggestField(unknown) {
|
|
3948
|
+
const lower = unknown.toLowerCase();
|
|
3949
|
+
for (const k of KNOWN_FIELDS2) {
|
|
3950
|
+
if (k.toLowerCase() === lower)
|
|
3951
|
+
return k;
|
|
3952
|
+
if (levenshtein(k.toLowerCase(), lower) <= 1)
|
|
3953
|
+
return k;
|
|
3954
|
+
if (k.toLowerCase().startsWith(lower.slice(0, 3)) && lower.length > 3)
|
|
3955
|
+
return k;
|
|
3956
|
+
}
|
|
3957
|
+
if (lower === "licence")
|
|
3958
|
+
return "license";
|
|
3959
|
+
if (lower === "dependancies" || lower === "deps")
|
|
3960
|
+
return "dependencies";
|
|
3961
|
+
if (lower === "mcp" || lower === "mcpservers")
|
|
3962
|
+
return "mcpServers";
|
|
3963
|
+
if (lower === "lsp")
|
|
3964
|
+
return "lspServers";
|
|
3965
|
+
if (lower === "outputstyles" || lower === "styles")
|
|
3966
|
+
return "outputStyles";
|
|
3967
|
+
if (lower === "userconfig")
|
|
3968
|
+
return "userConfig";
|
|
3969
|
+
return null;
|
|
3970
|
+
}
|
|
3971
|
+
function isRelativePathLike(v) {
|
|
3972
|
+
if (typeof v !== "string")
|
|
3973
|
+
return false;
|
|
3974
|
+
return RELATIVE_PATH_REGEX.test(v) && !v.includes("..");
|
|
3975
|
+
}
|
|
3976
|
+
var NAME_REGEX2, RELATIVE_PATH_REGEX, KNOWN_FIELDS2, REPLACES_DEFAULT, claudePluginValidator;
|
|
3977
|
+
var init_plugin = __esm(() => {
|
|
3978
|
+
NAME_REGEX2 = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
3979
|
+
RELATIVE_PATH_REGEX = /^\.\//;
|
|
3980
|
+
KNOWN_FIELDS2 = new Set([
|
|
3981
|
+
"$schema",
|
|
3982
|
+
"name",
|
|
3983
|
+
"displayName",
|
|
3984
|
+
"version",
|
|
3985
|
+
"description",
|
|
3986
|
+
"author",
|
|
3987
|
+
"homepage",
|
|
3988
|
+
"repository",
|
|
3989
|
+
"license",
|
|
3990
|
+
"keywords",
|
|
3991
|
+
"defaultEnabled",
|
|
3992
|
+
"skills",
|
|
3993
|
+
"commands",
|
|
3994
|
+
"agents",
|
|
3995
|
+
"hooks",
|
|
3996
|
+
"mcpServers",
|
|
3997
|
+
"outputStyles",
|
|
3998
|
+
"lspServers",
|
|
3999
|
+
"experimental",
|
|
4000
|
+
"userConfig",
|
|
4001
|
+
"channels",
|
|
4002
|
+
"dependencies"
|
|
4003
|
+
]);
|
|
4004
|
+
REPLACES_DEFAULT = new Set(["commands", "agents", "outputStyles", "lspServers"]);
|
|
4005
|
+
claudePluginValidator = {
|
|
4006
|
+
id: "claude:plugin",
|
|
4007
|
+
provider: "claude",
|
|
4008
|
+
name: "Claude Plugin",
|
|
4009
|
+
description: "Validates .claude-plugin/plugin.json manifest (complete schema per Plugins reference), component path rules (replace vs augment), .claude-plugin/ purity, default dirs, single-root-skill layout, unrecognized fields + suggestions, and structure",
|
|
4010
|
+
detect(dir) {
|
|
4011
|
+
return existsSync18(resolve6(dir, ".claude-plugin", "plugin.json"));
|
|
4012
|
+
},
|
|
4013
|
+
async validate(dir, _opts) {
|
|
4014
|
+
const errors = [];
|
|
4015
|
+
const warnings = [];
|
|
4016
|
+
const passes = [];
|
|
4017
|
+
const manifestPath = resolve6(dir, ".claude-plugin", "plugin.json");
|
|
4018
|
+
const dotClaudePluginDir = resolve6(dir, ".claude-plugin");
|
|
4019
|
+
let manifest;
|
|
4020
|
+
try {
|
|
4021
|
+
const raw = await Bun.file(manifestPath).text();
|
|
4022
|
+
manifest = JSON.parse(raw);
|
|
4023
|
+
passes.push(".claude-plugin/plugin.json is valid JSON");
|
|
4024
|
+
} catch {
|
|
4025
|
+
errors.push(".claude-plugin/plugin.json is missing or invalid JSON");
|
|
4026
|
+
return { errors, warnings, passes };
|
|
4027
|
+
}
|
|
4028
|
+
try {
|
|
4029
|
+
const entries = readdirSync8(dotClaudePluginDir);
|
|
4030
|
+
const unexpected = entries.filter((e) => e !== "plugin.json");
|
|
3482
4031
|
if (unexpected.length > 0) {
|
|
3483
4032
|
for (const e of unexpected) {
|
|
3484
4033
|
warnings.push(`Unexpected item "${e}" inside .claude-plugin/ \u2014 only plugin.json belongs here. Move component directories and files (skills/, commands/, agents/, hooks/, .mcp.json etc.) to the plugin root.`);
|
|
3485
4034
|
}
|
|
3486
|
-
} else if (entries.length === 1) {
|
|
3487
|
-
passes.push(".claude-plugin/ contains only plugin.json (correct layout)");
|
|
4035
|
+
} else if (entries.length === 1) {
|
|
4036
|
+
passes.push(".claude-plugin/ contains only plugin.json (correct layout)");
|
|
4037
|
+
}
|
|
4038
|
+
} catch {}
|
|
4039
|
+
if (!manifest.name) {
|
|
4040
|
+
errors.push('Missing required field: "name"');
|
|
4041
|
+
} else {
|
|
4042
|
+
const name = String(manifest.name);
|
|
4043
|
+
if (!NAME_REGEX2.test(name)) {
|
|
4044
|
+
errors.push(`Invalid name format: "${name}" \u2014 must be kebab-case (a-z, 0-9, hyphens)`);
|
|
4045
|
+
} else {
|
|
4046
|
+
passes.push(`name: "${name}"`);
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
if (manifest.version !== undefined) {
|
|
4050
|
+
const v = String(manifest.version);
|
|
4051
|
+
if (!/^\d+\.\d+\.\d+/.test(v)) {
|
|
4052
|
+
errors.push(`Invalid version format: "${v}" \u2014 must look like semver (MAJOR.MINOR.PATCH) when using explicit versioning`);
|
|
4053
|
+
} else {
|
|
4054
|
+
passes.push(`version: "${v}" (explicit \u2014 bump on every release to publish updates)`);
|
|
4055
|
+
}
|
|
4056
|
+
} else {
|
|
4057
|
+
passes.push("version omitted (git commit SHA used as version key \u2014 every commit becomes an available update)");
|
|
4058
|
+
}
|
|
4059
|
+
if (manifest.description !== undefined) {
|
|
4060
|
+
const desc = String(manifest.description);
|
|
4061
|
+
if (desc.length < 10) {
|
|
4062
|
+
warnings.push(`Description is very short (${desc.length} chars) \u2014 50-200 chars recommended`);
|
|
4063
|
+
} else {
|
|
4064
|
+
passes.push("description field present");
|
|
4065
|
+
}
|
|
4066
|
+
} else {
|
|
4067
|
+
warnings.push('Missing "description" (recommended for UI, marketplace listings, and auto-discovery)');
|
|
4068
|
+
}
|
|
4069
|
+
if (manifest.displayName !== undefined) {
|
|
4070
|
+
passes.push(`displayName: "${manifest.displayName}" (human UI label; falls back to name)`);
|
|
4071
|
+
}
|
|
4072
|
+
if (manifest.author !== undefined) {
|
|
4073
|
+
const a = manifest.author;
|
|
4074
|
+
if (a && typeof a === "object" && a.name) {
|
|
4075
|
+
passes.push("author present");
|
|
4076
|
+
} else {
|
|
4077
|
+
warnings.push('author should be an object like {"name": "...", "email?": "..."}');
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
if (manifest.license !== undefined) {
|
|
4081
|
+
passes.push(`license: "${manifest.license}"`);
|
|
4082
|
+
}
|
|
4083
|
+
if (manifest.keywords !== undefined) {
|
|
4084
|
+
if (Array.isArray(manifest.keywords)) {
|
|
4085
|
+
passes.push(`keywords: [${manifest.keywords.join(", ")}]`);
|
|
4086
|
+
} else {
|
|
4087
|
+
errors.push("keywords must be an array of strings");
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
if (manifest.defaultEnabled !== undefined) {
|
|
4091
|
+
passes.push(`defaultEnabled: ${manifest.defaultEnabled}`);
|
|
4092
|
+
}
|
|
4093
|
+
if (manifest.homepage)
|
|
4094
|
+
passes.push("homepage present");
|
|
4095
|
+
if (manifest.repository)
|
|
4096
|
+
passes.push("repository present");
|
|
4097
|
+
const unknown = Object.keys(manifest).filter((k) => !KNOWN_FIELDS2.has(k));
|
|
4098
|
+
for (const k of unknown) {
|
|
4099
|
+
const sug = suggestField(k);
|
|
4100
|
+
const hint = sug ? ` (did you mean "${sug}"?)` : "";
|
|
4101
|
+
warnings.push(`Unrecognized top-level field "${k}"${hint} \u2014 will be ignored at runtime (allowed for cross-tool manifest compatibility).`);
|
|
4102
|
+
}
|
|
4103
|
+
const handleField = (field, val) => {
|
|
4104
|
+
if (val === undefined || val === null)
|
|
4105
|
+
return;
|
|
4106
|
+
if (isRelativePathLike(val) || Array.isArray(val) && val.every(isRelativePathLike)) {
|
|
4107
|
+
const arr = Array.isArray(val) ? val : [val];
|
|
4108
|
+
for (const p of arr) {
|
|
4109
|
+
const s = String(p);
|
|
4110
|
+
if (!RELATIVE_PATH_REGEX.test(s)) {
|
|
4111
|
+
errors.push(`${field}: path "${s}" must start with "./"`);
|
|
4112
|
+
} else if (s.includes("..")) {
|
|
4113
|
+
errors.push(`${field}: path "${s}" must not use ".." (paths are confined to the plugin tree after cache copy)`);
|
|
4114
|
+
} else if (existsSync18(resolve6(dir, s))) {
|
|
4115
|
+
passes.push(`${field}: path "${s}" exists`);
|
|
4116
|
+
} else {
|
|
4117
|
+
warnings.push(`${field}: path "${s}" does not exist on disk`);
|
|
4118
|
+
}
|
|
4119
|
+
}
|
|
4120
|
+
if (field === "skills") {
|
|
4121
|
+
passes.push(`${field}: augments the default skills/ (both are scanned)`);
|
|
4122
|
+
} else if (REPLACES_DEFAULT.has(field)) {
|
|
4123
|
+
passes.push(`${field}: custom path replaces default ${field}/ scan`);
|
|
4124
|
+
} else {
|
|
4125
|
+
passes.push(`${field}: custom path or config (merge rules apply)`);
|
|
4126
|
+
}
|
|
4127
|
+
} else if (typeof val === "object") {
|
|
4128
|
+
passes.push(`${field}: inline ${field} config present`);
|
|
4129
|
+
}
|
|
4130
|
+
};
|
|
4131
|
+
["skills", "commands", "agents", "hooks", "mcpServers", "outputStyles", "lspServers"].forEach((f) => {
|
|
4132
|
+
if (manifest[f] !== undefined)
|
|
4133
|
+
handleField(f, manifest[f]);
|
|
4134
|
+
});
|
|
4135
|
+
if (manifest.experimental && typeof manifest.experimental === "object") {
|
|
4136
|
+
const exp = manifest.experimental;
|
|
4137
|
+
if (exp.themes !== undefined)
|
|
4138
|
+
handleField("experimental.themes", exp.themes);
|
|
4139
|
+
if (exp.monitors !== undefined)
|
|
4140
|
+
handleField("experimental.monitors", exp.monitors);
|
|
4141
|
+
passes.push("experimental section present (themes and monitors are experimental components)");
|
|
4142
|
+
}
|
|
4143
|
+
if (manifest.userConfig && typeof manifest.userConfig === "object") {
|
|
4144
|
+
const keys = Object.keys(manifest.userConfig);
|
|
4145
|
+
passes.push(`userConfig: ${keys.length} user-configurable value(s) declared`);
|
|
4146
|
+
for (const k of keys) {
|
|
4147
|
+
const opt = manifest.userConfig[k];
|
|
4148
|
+
if (!opt || !opt.type || !opt.title) {
|
|
4149
|
+
warnings.push(`userConfig.${k} is missing required "type" and/or "title"`);
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
if (Array.isArray(manifest.channels)) {
|
|
4154
|
+
passes.push(`channels: ${manifest.channels.length} channel(s) (each binds to an mcpServer)`);
|
|
4155
|
+
manifest.channels.forEach((ch, i) => {
|
|
4156
|
+
if (!ch?.server)
|
|
4157
|
+
warnings.push(`channels[${i}]: "server" is required and must match an mcpServers key`);
|
|
4158
|
+
});
|
|
4159
|
+
}
|
|
4160
|
+
if (Array.isArray(manifest.dependencies)) {
|
|
4161
|
+
passes.push(`dependencies: declares ${manifest.dependencies.length} plugin dependency/ies`);
|
|
4162
|
+
}
|
|
4163
|
+
const skillsDir = resolve6(dir, "skills");
|
|
4164
|
+
if (existsSync18(skillsDir)) {
|
|
4165
|
+
const entries = readdirSync8(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
4166
|
+
for (const e of entries) {
|
|
4167
|
+
const md = join16(skillsDir, e.name, "SKILL.md");
|
|
4168
|
+
if (existsSync18(md)) {
|
|
4169
|
+
passes.push(`skills/${e.name}/SKILL.md exists`);
|
|
4170
|
+
} else {
|
|
4171
|
+
errors.push(`skills/${e.name}/ is missing SKILL.md`);
|
|
4172
|
+
}
|
|
4173
|
+
}
|
|
4174
|
+
if (manifest.skills !== undefined) {
|
|
4175
|
+
warnings.push('Default skills/ dir co-exists with manifest "skills" \u2014 manifest path is authoritative; default folder ignored for loading');
|
|
4176
|
+
}
|
|
4177
|
+
}
|
|
4178
|
+
const commandsDir = resolve6(dir, "commands");
|
|
4179
|
+
if (existsSync18(commandsDir)) {
|
|
4180
|
+
const mds = readdirSync8(commandsDir).filter((f) => f.endsWith(".md"));
|
|
4181
|
+
if (mds.length) {
|
|
4182
|
+
passes.push(`commands/ has ${mds.length} .md file(s)`);
|
|
4183
|
+
}
|
|
4184
|
+
if (manifest.commands !== undefined) {
|
|
4185
|
+
warnings.push('commands/ co-exists with manifest "commands" \u2014 manifest replaces default (dir ignored)');
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
const agentsDir = resolve6(dir, "agents");
|
|
4189
|
+
if (existsSync18(agentsDir)) {
|
|
4190
|
+
const mds = readdirSync8(agentsDir).filter((f) => f.endsWith(".md"));
|
|
4191
|
+
if (mds.length) {
|
|
4192
|
+
passes.push(`agents/ has ${mds.length} .md file(s)`);
|
|
4193
|
+
}
|
|
4194
|
+
if (manifest.agents !== undefined) {
|
|
4195
|
+
warnings.push('agents/ co-exists with manifest "agents" \u2014 manifest replaces default (dir ignored)');
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4198
|
+
if (existsSync18(resolve6(dir, "output-styles"))) {
|
|
4199
|
+
passes.push("output-styles/ directory present");
|
|
4200
|
+
if (manifest.outputStyles)
|
|
4201
|
+
warnings.push("output-styles/ co-exists with manifest outputStyles \u2014 manifest wins");
|
|
4202
|
+
}
|
|
4203
|
+
if (existsSync18(resolve6(dir, "themes")))
|
|
4204
|
+
passes.push("themes/ present (experimental)");
|
|
4205
|
+
if (existsSync18(resolve6(dir, "monitors")) || manifest.experimental?.monitors) {
|
|
4206
|
+
passes.push("monitors config present (experimental)");
|
|
4207
|
+
}
|
|
4208
|
+
if (existsSync18(resolve6(dir, "bin")))
|
|
4209
|
+
passes.push("bin/ present (adds executables to Bash tool $PATH)");
|
|
4210
|
+
if (existsSync18(resolve6(dir, "settings.json")))
|
|
4211
|
+
passes.push("settings.json present (plugin defaults for agent/statusline)");
|
|
4212
|
+
if (existsSync18(resolve6(dir, "README.md")))
|
|
4213
|
+
passes.push("README.md present");
|
|
4214
|
+
if (existsSync18(resolve6(dir, ".mcp.json")))
|
|
4215
|
+
passes.push(".mcp.json present (validated by claude:mcp)");
|
|
4216
|
+
if (existsSync18(resolve6(dir, ".lsp.json")))
|
|
4217
|
+
passes.push(".lsp.json present (validated by claude:lsp when registered)");
|
|
4218
|
+
if (existsSync18(resolve6(dir, "hooks/hooks.json")) || existsSync18(resolve6(dir, "hooks.json"))) {
|
|
4219
|
+
passes.push("hooks config present (validated by claude:hooks)");
|
|
4220
|
+
}
|
|
4221
|
+
if (existsSync18(resolve6(dir, "SKILL.md")) && !existsSync18(skillsDir) && manifest.skills === undefined) {
|
|
4222
|
+
passes.push('Root SKILL.md detected \u2014 plugin will be treated as a single-skill plugin (prefer frontmatter "name" for stable /command)');
|
|
4223
|
+
}
|
|
4224
|
+
return { errors, warnings, passes };
|
|
4225
|
+
}
|
|
4226
|
+
};
|
|
4227
|
+
});
|
|
4228
|
+
|
|
4229
|
+
// src/validators/claude/marketplace.ts
|
|
4230
|
+
import { existsSync as existsSync19, readdirSync as readdirSync9 } from "fs";
|
|
4231
|
+
import { resolve as resolve7, join as join17 } from "path";
|
|
4232
|
+
var claudeMarketplaceValidator;
|
|
4233
|
+
var init_marketplace = __esm(() => {
|
|
4234
|
+
claudeMarketplaceValidator = {
|
|
4235
|
+
id: "claude:marketplace",
|
|
4236
|
+
provider: "claude",
|
|
4237
|
+
name: "Claude Plugin Marketplace",
|
|
4238
|
+
description: "Validates .claude-plugin/marketplace.json or plugins/ marketplace layouts (plugins array with sources)",
|
|
4239
|
+
detect(dir) {
|
|
4240
|
+
if (existsSync19(resolve7(dir, ".claude-plugin", "marketplace.json")))
|
|
4241
|
+
return true;
|
|
4242
|
+
const pluginsDir = resolve7(dir, "plugins");
|
|
4243
|
+
if (!existsSync19(pluginsDir))
|
|
4244
|
+
return false;
|
|
4245
|
+
try {
|
|
4246
|
+
const entries = readdirSync9(pluginsDir, { withFileTypes: true });
|
|
4247
|
+
for (const entry of entries) {
|
|
4248
|
+
if (!entry.isDirectory())
|
|
4249
|
+
continue;
|
|
4250
|
+
const hasSkills = existsSync19(join17(pluginsDir, entry.name, "skills"));
|
|
4251
|
+
const hasManifest = existsSync19(join17(pluginsDir, entry.name, ".claude-plugin", "plugin.json"));
|
|
4252
|
+
if (hasSkills || hasManifest)
|
|
4253
|
+
return true;
|
|
4254
|
+
}
|
|
4255
|
+
} catch {}
|
|
4256
|
+
return false;
|
|
4257
|
+
},
|
|
4258
|
+
async validate(dir, _opts) {
|
|
4259
|
+
const errors = [];
|
|
4260
|
+
const warnings = [];
|
|
4261
|
+
const passes = [];
|
|
4262
|
+
const claudeMktPath = resolve7(dir, ".claude-plugin", "marketplace.json");
|
|
4263
|
+
const hasClaudeMkt = existsSync19(claudeMktPath);
|
|
4264
|
+
const pluginsDir = resolve7(dir, "plugins");
|
|
4265
|
+
const hasPluginsDirLayout = existsSync19(pluginsDir);
|
|
4266
|
+
if (!hasClaudeMkt && !hasPluginsDirLayout) {
|
|
4267
|
+
errors.push("Missing .claude-plugin/marketplace.json or plugins/ directory");
|
|
4268
|
+
return { errors, warnings, passes };
|
|
4269
|
+
}
|
|
4270
|
+
if (hasClaudeMkt) {
|
|
4271
|
+
let mkt;
|
|
4272
|
+
try {
|
|
4273
|
+
const raw = await Bun.file(claudeMktPath).text();
|
|
4274
|
+
mkt = JSON.parse(raw);
|
|
4275
|
+
passes.push(".claude-plugin/marketplace.json is valid JSON");
|
|
4276
|
+
} catch {
|
|
4277
|
+
errors.push(".claude-plugin/marketplace.json is missing or invalid JSON");
|
|
4278
|
+
return { errors, warnings, passes };
|
|
4279
|
+
}
|
|
4280
|
+
if (mkt.name) {
|
|
4281
|
+
passes.push(`name: "${mkt.name}"`);
|
|
4282
|
+
} else {
|
|
4283
|
+
warnings.push('Missing "name" at marketplace root');
|
|
4284
|
+
}
|
|
4285
|
+
if (mkt.description) {
|
|
4286
|
+
passes.push("description present");
|
|
4287
|
+
}
|
|
4288
|
+
if (mkt.owner) {
|
|
4289
|
+
passes.push("owner present");
|
|
4290
|
+
}
|
|
4291
|
+
if (!Array.isArray(mkt.plugins) || mkt.plugins.length === 0) {
|
|
4292
|
+
errors.push('"plugins" must be a non-empty array');
|
|
4293
|
+
return { errors, warnings, passes };
|
|
4294
|
+
}
|
|
4295
|
+
passes.push(`${mkt.plugins.length} plugin(s) declared`);
|
|
4296
|
+
for (const [i, p] of mkt.plugins.entries()) {
|
|
4297
|
+
if (!p || typeof p !== "object") {
|
|
4298
|
+
errors.push(`plugins[${i}]: must be an object`);
|
|
4299
|
+
continue;
|
|
4300
|
+
}
|
|
4301
|
+
if (p.name) {
|
|
4302
|
+
passes.push(`plugins[${i}].name: "${p.name}"`);
|
|
4303
|
+
} else {
|
|
4304
|
+
errors.push(`plugins[${i}]: missing "name"`);
|
|
4305
|
+
}
|
|
4306
|
+
if (p.source) {
|
|
4307
|
+
const src = String(p.source);
|
|
4308
|
+
passes.push(`plugins[${i}].source: "${src}"`);
|
|
4309
|
+
const srcDir = resolve7(dir, src);
|
|
4310
|
+
if (existsSync19(srcDir)) {
|
|
4311
|
+
const hasManifest = existsSync19(resolve7(srcDir, ".claude-plugin", "plugin.json"));
|
|
4312
|
+
const hasSkills = existsSync19(resolve7(srcDir, "skills"));
|
|
4313
|
+
if (hasManifest || hasSkills) {
|
|
4314
|
+
passes.push(`plugins[${i}]: source exists (${hasManifest ? "manifest" : "skills/"})`);
|
|
4315
|
+
} else {
|
|
4316
|
+
warnings.push(`plugins[${i}].source "${src}" exists but lacks plugin markers`);
|
|
4317
|
+
}
|
|
4318
|
+
} else {
|
|
4319
|
+
warnings.push(`plugins[${i}].source path "${src}" does not exist`);
|
|
4320
|
+
}
|
|
4321
|
+
} else {
|
|
4322
|
+
errors.push(`plugins[${i}]: missing "source"`);
|
|
4323
|
+
}
|
|
4324
|
+
if (p.category) {
|
|
4325
|
+
passes.push(`plugins[${i}].category: "${p.category}"`);
|
|
4326
|
+
}
|
|
4327
|
+
}
|
|
4328
|
+
if (existsSync19(resolve7(dir, "README.md"))) {
|
|
4329
|
+
passes.push("README.md exists at marketplace root");
|
|
4330
|
+
} else {
|
|
4331
|
+
warnings.push("No README.md at marketplace root \u2014 recommended for discoverability");
|
|
4332
|
+
}
|
|
4333
|
+
if (existsSync19(resolve7(dir, "LICENSE"))) {
|
|
4334
|
+
passes.push("LICENSE exists at marketplace root");
|
|
4335
|
+
} else {
|
|
4336
|
+
warnings.push("No LICENSE at marketplace root \u2014 recommended");
|
|
4337
|
+
}
|
|
4338
|
+
return { errors, warnings, passes };
|
|
4339
|
+
}
|
|
4340
|
+
if (hasPluginsDirLayout) {
|
|
4341
|
+
passes.push("plugins/ directory exists");
|
|
4342
|
+
const pluginEntries = readdirSync9(pluginsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
4343
|
+
if (pluginEntries.length === 0) {
|
|
4344
|
+
errors.push("plugins/ directory is empty \u2014 expected at least one plugin");
|
|
4345
|
+
return { errors, warnings, passes };
|
|
4346
|
+
}
|
|
4347
|
+
passes.push(`${pluginEntries.length} plugin(s) found`);
|
|
4348
|
+
if (existsSync19(resolve7(dir, "README.md"))) {
|
|
4349
|
+
passes.push("README.md exists at marketplace root");
|
|
4350
|
+
} else {
|
|
4351
|
+
warnings.push("No README.md at marketplace root \u2014 recommended for discoverability");
|
|
4352
|
+
}
|
|
4353
|
+
if (existsSync19(resolve7(dir, "LICENSE"))) {
|
|
4354
|
+
passes.push("LICENSE exists at marketplace root");
|
|
4355
|
+
} else {
|
|
4356
|
+
warnings.push("No LICENSE at marketplace root \u2014 recommended");
|
|
4357
|
+
}
|
|
4358
|
+
for (const plugin of pluginEntries) {
|
|
4359
|
+
const pluginPath = join17(pluginsDir, plugin.name);
|
|
4360
|
+
const hasSkills = existsSync19(join17(pluginPath, "skills"));
|
|
4361
|
+
const hasManifest = existsSync19(join17(pluginPath, ".claude-plugin", "plugin.json"));
|
|
4362
|
+
const hasReadme = existsSync19(join17(pluginPath, "README.md"));
|
|
4363
|
+
if (hasManifest || hasSkills) {
|
|
4364
|
+
passes.push(`Plugin "${plugin.name}" has ${hasManifest ? "manifest" : "skills/"}`);
|
|
4365
|
+
} else {
|
|
4366
|
+
warnings.push(`Plugin "${plugin.name}" has neither .claude-plugin/plugin.json nor skills/`);
|
|
4367
|
+
}
|
|
4368
|
+
if (!hasReadme) {
|
|
4369
|
+
warnings.push(`Plugin "${plugin.name}" has no README.md`);
|
|
4370
|
+
}
|
|
4371
|
+
}
|
|
4372
|
+
return { errors, warnings, passes };
|
|
4373
|
+
}
|
|
4374
|
+
return { errors, warnings, passes };
|
|
4375
|
+
}
|
|
4376
|
+
};
|
|
4377
|
+
});
|
|
4378
|
+
|
|
4379
|
+
// src/validators/claude/hooks.ts
|
|
4380
|
+
import { existsSync as existsSync20 } from "fs";
|
|
4381
|
+
import { resolve as resolve8 } from "path";
|
|
4382
|
+
var KNOWN_EVENTS, claudeHooksValidator;
|
|
4383
|
+
var init_hooks = __esm(() => {
|
|
4384
|
+
KNOWN_EVENTS = [
|
|
4385
|
+
"SessionStart",
|
|
4386
|
+
"Setup",
|
|
4387
|
+
"UserPromptSubmit",
|
|
4388
|
+
"UserPromptExpansion",
|
|
4389
|
+
"PreToolUse",
|
|
4390
|
+
"PermissionRequest",
|
|
4391
|
+
"PermissionDenied",
|
|
4392
|
+
"PostToolUse",
|
|
4393
|
+
"PostToolUseFailure",
|
|
4394
|
+
"PostToolBatch",
|
|
4395
|
+
"Notification",
|
|
4396
|
+
"MessageDisplay",
|
|
4397
|
+
"SubagentStart",
|
|
4398
|
+
"SubagentStop",
|
|
4399
|
+
"TaskCreated",
|
|
4400
|
+
"TaskCompleted",
|
|
4401
|
+
"Stop",
|
|
4402
|
+
"StopFailure",
|
|
4403
|
+
"TeammateIdle",
|
|
4404
|
+
"InstructionsLoaded",
|
|
4405
|
+
"ConfigChange",
|
|
4406
|
+
"CwdChanged",
|
|
4407
|
+
"FileChanged",
|
|
4408
|
+
"WorktreeCreate",
|
|
4409
|
+
"WorktreeRemove",
|
|
4410
|
+
"PreCompact",
|
|
4411
|
+
"PostCompact",
|
|
4412
|
+
"Elicitation",
|
|
4413
|
+
"ElicitationResult",
|
|
4414
|
+
"SessionEnd"
|
|
4415
|
+
];
|
|
4416
|
+
claudeHooksValidator = {
|
|
4417
|
+
id: "claude:hooks",
|
|
4418
|
+
provider: "claude",
|
|
4419
|
+
name: "Claude Hooks",
|
|
4420
|
+
description: "Validates hooks/hooks.json (or root hooks.json): all lifecycle events per Plugins reference, hook group structure (matcher + hooks[]), supported hook types (command, http, mcp_tool, prompt, agent)",
|
|
4421
|
+
detect(dir) {
|
|
4422
|
+
return existsSync20(resolve8(dir, "hooks", "hooks.json")) || existsSync20(resolve8(dir, "hooks.json"));
|
|
4423
|
+
},
|
|
4424
|
+
async validate(dir, _opts) {
|
|
4425
|
+
const errors = [];
|
|
4426
|
+
const warnings = [];
|
|
4427
|
+
const passes = [];
|
|
4428
|
+
const hooksPath = existsSync20(resolve8(dir, "hooks", "hooks.json")) ? resolve8(dir, "hooks", "hooks.json") : resolve8(dir, "hooks.json");
|
|
4429
|
+
let config;
|
|
4430
|
+
try {
|
|
4431
|
+
const raw = await Bun.file(hooksPath).text();
|
|
4432
|
+
config = JSON.parse(raw);
|
|
4433
|
+
passes.push("hooks.json is valid JSON");
|
|
4434
|
+
} catch {
|
|
4435
|
+
errors.push("hooks.json is missing or invalid JSON");
|
|
4436
|
+
return { errors, warnings, passes };
|
|
4437
|
+
}
|
|
4438
|
+
const eventNames = Object.keys(config);
|
|
4439
|
+
for (const name of eventNames) {
|
|
4440
|
+
if (KNOWN_EVENTS.includes(name)) {
|
|
4441
|
+
passes.push(`Event "${name}" is a known lifecycle event`);
|
|
4442
|
+
} else {
|
|
4443
|
+
warnings.push(`Unknown event name: "${name}" \u2014 see full list in Plugins reference (SessionStart, PreToolUse, PostToolUse, Stop, ...)`);
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
for (const [event, groups] of Object.entries(config)) {
|
|
4447
|
+
if (!Array.isArray(groups)) {
|
|
4448
|
+
errors.push(`Event "${event}": value must be an array of hook groups`);
|
|
4449
|
+
continue;
|
|
4450
|
+
}
|
|
4451
|
+
groups.forEach((group, gi) => {
|
|
4452
|
+
if (!group || typeof group !== "object") {
|
|
4453
|
+
errors.push(`${event}[${gi}]: hook group must be an object`);
|
|
4454
|
+
return;
|
|
4455
|
+
}
|
|
4456
|
+
if (group.matcher !== undefined && typeof group.matcher !== "string") {
|
|
4457
|
+
warnings.push(`${event}[${gi}]: "matcher" should be a string (e.g. "Write|Edit" or glob)`);
|
|
4458
|
+
}
|
|
4459
|
+
const hooksArr = group.hooks;
|
|
4460
|
+
if (!Array.isArray(hooksArr)) {
|
|
4461
|
+
errors.push(`${event}[${gi}]: missing or invalid "hooks" array`);
|
|
4462
|
+
return;
|
|
4463
|
+
}
|
|
4464
|
+
hooksArr.forEach((h, hi) => {
|
|
4465
|
+
if (!h || typeof h !== "object" || !h.type) {
|
|
4466
|
+
errors.push(`${event}[${gi}].hooks[${hi}]: must have "type"`);
|
|
4467
|
+
return;
|
|
4468
|
+
}
|
|
4469
|
+
const t = String(h.type);
|
|
4470
|
+
if (!["command", "http", "mcp_tool", "prompt", "agent"].includes(t)) {
|
|
4471
|
+
warnings.push(`${event}[${gi}].hooks[${hi}]: unknown type "${t}" (valid: command, http, mcp_tool, prompt, agent)`);
|
|
4472
|
+
}
|
|
4473
|
+
if (t === "command" && !h.command) {
|
|
4474
|
+
errors.push(`${event}[${gi}].hooks[${hi}]: type=command requires "command"`);
|
|
4475
|
+
}
|
|
4476
|
+
if (t === "http" && !h.url) {
|
|
4477
|
+
errors.push(`${event}[${gi}].hooks[${hi}]: type=http requires "url"`);
|
|
4478
|
+
}
|
|
4479
|
+
if (h.command && typeof h.command === "string" && /\$\{CLAUDE_/.test(h.command)) {
|
|
4480
|
+
passes.push(`${event}[${gi}].hooks[${hi}]: uses plugin env substitution`);
|
|
4481
|
+
}
|
|
4482
|
+
});
|
|
4483
|
+
if (hooksArr.length > 0) {
|
|
4484
|
+
passes.push(`Event "${event}" has ${hooksArr.length} hook action(s)`);
|
|
4485
|
+
}
|
|
4486
|
+
});
|
|
4487
|
+
}
|
|
4488
|
+
return { errors, warnings, passes };
|
|
4489
|
+
}
|
|
4490
|
+
};
|
|
4491
|
+
});
|
|
4492
|
+
|
|
4493
|
+
// src/validators/claude/mcp.ts
|
|
4494
|
+
import { existsSync as existsSync21 } from "fs";
|
|
4495
|
+
import { resolve as resolve9 } from "path";
|
|
4496
|
+
var claudeMcpValidator;
|
|
4497
|
+
var init_mcp = __esm(() => {
|
|
4498
|
+
claudeMcpValidator = {
|
|
4499
|
+
id: "claude:mcp",
|
|
4500
|
+
provider: "claude",
|
|
4501
|
+
name: "Claude MCP Config",
|
|
4502
|
+
description: "Validates .mcp.json (or inline via plugin.json mcpServers): server entries (stdio: command+args, or url), env, cwd, ${CLAUDE_PLUGIN_ROOT} etc. substitutions per Plugins reference",
|
|
4503
|
+
detect(dir) {
|
|
4504
|
+
return existsSync21(resolve9(dir, ".mcp.json"));
|
|
4505
|
+
},
|
|
4506
|
+
async validate(dir, _opts) {
|
|
4507
|
+
const errors = [];
|
|
4508
|
+
const warnings = [];
|
|
4509
|
+
const passes = [];
|
|
4510
|
+
const mcpPath = resolve9(dir, ".mcp.json");
|
|
4511
|
+
let config;
|
|
4512
|
+
try {
|
|
4513
|
+
const raw = await Bun.file(mcpPath).text();
|
|
4514
|
+
config = JSON.parse(raw);
|
|
4515
|
+
passes.push(".mcp.json is valid JSON");
|
|
4516
|
+
} catch {
|
|
4517
|
+
errors.push(".mcp.json is missing or invalid JSON");
|
|
4518
|
+
return { errors, warnings, passes };
|
|
4519
|
+
}
|
|
4520
|
+
if (typeof config !== "object" || Array.isArray(config)) {
|
|
4521
|
+
errors.push(".mcp.json must be a JSON object with server name keys");
|
|
4522
|
+
return { errors, warnings, passes };
|
|
4523
|
+
}
|
|
4524
|
+
const serverNames = Object.keys(config);
|
|
4525
|
+
if (serverNames.length === 0) {
|
|
4526
|
+
warnings.push(".mcp.json is empty \u2014 no servers defined");
|
|
4527
|
+
return { errors, warnings, passes };
|
|
4528
|
+
}
|
|
4529
|
+
passes.push(`${serverNames.length} server(s) defined`);
|
|
4530
|
+
for (const [name, entry] of Object.entries(config)) {
|
|
4531
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
4532
|
+
errors.push(`mcp server "${name}": definition must be an object`);
|
|
4533
|
+
continue;
|
|
4534
|
+
}
|
|
4535
|
+
const e = entry;
|
|
4536
|
+
const hasCommand = typeof e.command === "string";
|
|
4537
|
+
const hasUrl = typeof e.url === "string";
|
|
4538
|
+
if (!hasCommand && !hasUrl) {
|
|
4539
|
+
errors.push(`mcp server "${name}": must have either "command" (for stdio) or "url" (for SSE/HTTP)`);
|
|
4540
|
+
}
|
|
4541
|
+
if (hasCommand && !Array.isArray(e.args)) {
|
|
4542
|
+
warnings.push(`mcp server "${name}": "command" present but no "args" array (ok for some servers)`);
|
|
4543
|
+
}
|
|
4544
|
+
if (hasUrl && hasCommand) {
|
|
4545
|
+
warnings.push(`mcp server "${name}": both "command" and "url" present \u2014 usually one or the other`);
|
|
4546
|
+
}
|
|
4547
|
+
if (e.env && typeof e.env === "object") {
|
|
4548
|
+
passes.push(`mcp server "${name}": has env`);
|
|
4549
|
+
}
|
|
4550
|
+
if (typeof e.cwd === "string") {
|
|
4551
|
+
passes.push(`mcp server "${name}": has cwd`);
|
|
4552
|
+
}
|
|
4553
|
+
const hasSubs = JSON.stringify(e).match(/\$\{CLAUDE_PLUGIN_(ROOT|DATA)|CLAUDE_PROJECT_DIR|user_config\.|ENV_VAR\}/);
|
|
4554
|
+
if (hasSubs) {
|
|
4555
|
+
passes.push(`mcp server "${name}": uses \${CLAUDE_PLUGIN_*} / user_config / env substitution`);
|
|
4556
|
+
}
|
|
4557
|
+
}
|
|
4558
|
+
return { errors, warnings, passes };
|
|
4559
|
+
}
|
|
4560
|
+
};
|
|
4561
|
+
});
|
|
4562
|
+
|
|
4563
|
+
// src/validators/claude/subagent.ts
|
|
4564
|
+
import { existsSync as existsSync22, readdirSync as readdirSync10 } from "fs";
|
|
4565
|
+
import { resolve as resolve10, join as join18 } from "path";
|
|
4566
|
+
var claudeSubagentValidator;
|
|
4567
|
+
var init_subagent = __esm(() => {
|
|
4568
|
+
init_frontmatter();
|
|
4569
|
+
claudeSubagentValidator = {
|
|
4570
|
+
id: "claude:subagent",
|
|
4571
|
+
provider: "claude",
|
|
4572
|
+
name: "Claude Subagents",
|
|
4573
|
+
description: "Validates agents/*.md (plugin subagents): frontmatter per spec (name, description, model, effort, maxTurns, tools, disallowedTools, skills, memory, background, isolation=worktree), body; warns on disallowed fields (hooks, mcpServers, permissionMode) for security",
|
|
4574
|
+
detect(dir) {
|
|
4575
|
+
const agentsDir = resolve10(dir, "agents");
|
|
4576
|
+
if (!existsSync22(agentsDir))
|
|
4577
|
+
return false;
|
|
4578
|
+
try {
|
|
4579
|
+
return readdirSync10(agentsDir).some((f) => f.endsWith(".md"));
|
|
4580
|
+
} catch {
|
|
4581
|
+
return false;
|
|
4582
|
+
}
|
|
4583
|
+
},
|
|
4584
|
+
async validate(dir, _opts) {
|
|
4585
|
+
const errors = [];
|
|
4586
|
+
const warnings = [];
|
|
4587
|
+
const passes = [];
|
|
4588
|
+
const agentsDir = resolve10(dir, "agents");
|
|
4589
|
+
const mdFiles = readdirSync10(agentsDir).filter((f) => f.endsWith(".md"));
|
|
4590
|
+
if (mdFiles.length === 0) {
|
|
4591
|
+
errors.push("agents/ directory has no .md files");
|
|
4592
|
+
return { errors, warnings, passes };
|
|
4593
|
+
}
|
|
4594
|
+
passes.push(`${mdFiles.length} agent definition(s) found`);
|
|
4595
|
+
const SUPPORTED = new Set([
|
|
4596
|
+
"name",
|
|
4597
|
+
"description",
|
|
4598
|
+
"model",
|
|
4599
|
+
"effort",
|
|
4600
|
+
"maxTurns",
|
|
4601
|
+
"tools",
|
|
4602
|
+
"disallowedTools",
|
|
4603
|
+
"skills",
|
|
4604
|
+
"memory",
|
|
4605
|
+
"background",
|
|
4606
|
+
"isolation"
|
|
4607
|
+
]);
|
|
4608
|
+
const DISALLOWED = new Set(["hooks", "mcpServers", "permissionMode"]);
|
|
4609
|
+
for (const file of mdFiles) {
|
|
4610
|
+
const filePath = join18(agentsDir, file);
|
|
4611
|
+
const raw = await Bun.file(filePath).text();
|
|
4612
|
+
try {
|
|
4613
|
+
const parsed = parseFrontmatter(raw);
|
|
4614
|
+
const fm = parsed.data;
|
|
4615
|
+
if (Object.keys(fm).length === 0) {
|
|
4616
|
+
warnings.push(`${file}: no YAML frontmatter (description recommended so Claude knows when to invoke)`);
|
|
4617
|
+
} else {
|
|
4618
|
+
if (fm.description) {
|
|
4619
|
+
passes.push(`${file}: has frontmatter with description`);
|
|
4620
|
+
} else {
|
|
4621
|
+
warnings.push(`${file}: missing "description" in frontmatter`);
|
|
4622
|
+
}
|
|
4623
|
+
const usedSupported = [];
|
|
4624
|
+
Object.keys(fm).forEach((k) => {
|
|
4625
|
+
if (SUPPORTED.has(k))
|
|
4626
|
+
usedSupported.push(k);
|
|
4627
|
+
if (DISALLOWED.has(k)) {
|
|
4628
|
+
errors.push(`${file}: frontmatter "${k}" is not supported for plugin-shipped agents (security restriction)`);
|
|
4629
|
+
}
|
|
4630
|
+
});
|
|
4631
|
+
if (usedSupported.length) {
|
|
4632
|
+
passes.push(`${file}: frontmatter fields: ${usedSupported.join(", ")}`);
|
|
4633
|
+
}
|
|
4634
|
+
if (fm.isolation !== undefined && fm.isolation !== "worktree") {
|
|
4635
|
+
errors.push(`${file}: "isolation" must be "worktree" if present (only supported value for plugin agents)`);
|
|
4636
|
+
}
|
|
4637
|
+
if (fm.name && typeof fm.name === "string") {
|
|
4638
|
+
passes.push(`${file}: name: "${fm.name}"`);
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
if (!parsed.content.trim()) {
|
|
4642
|
+
errors.push(`${file}: body is empty`);
|
|
4643
|
+
} else {
|
|
4644
|
+
passes.push(`${file}: has agent system prompt body`);
|
|
4645
|
+
}
|
|
4646
|
+
} catch {
|
|
4647
|
+
errors.push(`${file}: failed to parse`);
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4650
|
+
return { errors, warnings, passes };
|
|
4651
|
+
}
|
|
4652
|
+
};
|
|
4653
|
+
});
|
|
4654
|
+
|
|
4655
|
+
// src/validators/claude/command.ts
|
|
4656
|
+
import { existsSync as existsSync23, readdirSync as readdirSync11 } from "fs";
|
|
4657
|
+
import { resolve as resolve11, join as join19 } from "path";
|
|
4658
|
+
var claudeCommandValidator;
|
|
4659
|
+
var init_command = __esm(() => {
|
|
4660
|
+
init_frontmatter();
|
|
4661
|
+
claudeCommandValidator = {
|
|
4662
|
+
id: "claude:command",
|
|
4663
|
+
provider: "claude",
|
|
4664
|
+
name: "Claude Commands",
|
|
4665
|
+
description: "Validates commands/ (or legacy .claude/commands/) .md files: frontmatter (including rich skill fields), description, body",
|
|
4666
|
+
detect(dir) {
|
|
4667
|
+
const commandsDir = resolve11(dir, "commands");
|
|
4668
|
+
if (!existsSync23(commandsDir))
|
|
4669
|
+
return false;
|
|
4670
|
+
try {
|
|
4671
|
+
return readdirSync11(commandsDir).some((f) => f.endsWith(".md"));
|
|
4672
|
+
} catch {
|
|
4673
|
+
return false;
|
|
4674
|
+
}
|
|
4675
|
+
},
|
|
4676
|
+
async validate(dir, _opts) {
|
|
4677
|
+
const errors = [];
|
|
4678
|
+
const warnings = [];
|
|
4679
|
+
const passes = [];
|
|
4680
|
+
const commandsDir = resolve11(dir, "commands");
|
|
4681
|
+
const mdFiles = readdirSync11(commandsDir).filter((f) => f.endsWith(".md"));
|
|
4682
|
+
if (mdFiles.length === 0) {
|
|
4683
|
+
errors.push("commands/ directory has no .md files");
|
|
4684
|
+
return { errors, warnings, passes };
|
|
4685
|
+
}
|
|
4686
|
+
passes.push(`${mdFiles.length} command definition(s) found`);
|
|
4687
|
+
for (const file of mdFiles) {
|
|
4688
|
+
const filePath = join19(commandsDir, file);
|
|
4689
|
+
const raw = await Bun.file(filePath).text();
|
|
4690
|
+
try {
|
|
4691
|
+
const parsed = parseFrontmatter(raw);
|
|
4692
|
+
if (Object.keys(parsed.data).length === 0) {
|
|
4693
|
+
warnings.push(`${file}: no YAML frontmatter`);
|
|
4694
|
+
} else if (!parsed.data.description) {
|
|
4695
|
+
warnings.push(`${file}: missing "description" in frontmatter`);
|
|
4696
|
+
} else {
|
|
4697
|
+
passes.push(`${file}: has frontmatter with description`);
|
|
4698
|
+
}
|
|
4699
|
+
if (!parsed.content.trim()) {
|
|
4700
|
+
errors.push(`${file}: body is empty`);
|
|
4701
|
+
}
|
|
4702
|
+
const advancedKeys = ["allowed-tools", "disallowed-tools", "context", "when_to_use", "disable-model-invocation", "user-invocable", "arguments", "argument-hint", "shell", "paths", "hooks"];
|
|
4703
|
+
const foundAdvanced = advancedKeys.filter((k) => parsed.data[k] !== undefined);
|
|
4704
|
+
if (foundAdvanced.length > 0) {
|
|
4705
|
+
passes.push(`${file}: advanced frontmatter: ${foundAdvanced.join(", ")}`);
|
|
4706
|
+
}
|
|
4707
|
+
} catch {
|
|
4708
|
+
errors.push(`${file}: failed to parse`);
|
|
4709
|
+
}
|
|
4710
|
+
}
|
|
4711
|
+
return { errors, warnings, passes };
|
|
4712
|
+
}
|
|
4713
|
+
};
|
|
4714
|
+
});
|
|
4715
|
+
|
|
4716
|
+
// src/validators/claude/memory.ts
|
|
4717
|
+
import { existsSync as existsSync24 } from "fs";
|
|
4718
|
+
import { resolve as resolve12 } from "path";
|
|
4719
|
+
var claudeMemoryValidator;
|
|
4720
|
+
var init_memory = __esm(() => {
|
|
4721
|
+
claudeMemoryValidator = {
|
|
4722
|
+
id: "claude:memory",
|
|
4723
|
+
provider: "claude",
|
|
4724
|
+
name: "Claude CLAUDE.md",
|
|
4725
|
+
description: "Validates CLAUDE.md: non-empty, length recommendations, @path imports",
|
|
4726
|
+
detect(dir) {
|
|
4727
|
+
return existsSync24(resolve12(dir, "CLAUDE.md"));
|
|
4728
|
+
},
|
|
4729
|
+
async validate(dir, _opts) {
|
|
4730
|
+
const errors = [];
|
|
4731
|
+
const warnings = [];
|
|
4732
|
+
const passes = [];
|
|
4733
|
+
const filePath = resolve12(dir, "CLAUDE.md");
|
|
4734
|
+
const raw = await Bun.file(filePath).text();
|
|
4735
|
+
if (!raw.trim()) {
|
|
4736
|
+
errors.push("CLAUDE.md is empty");
|
|
4737
|
+
return { errors, warnings, passes };
|
|
4738
|
+
}
|
|
4739
|
+
passes.push("CLAUDE.md is non-empty");
|
|
4740
|
+
const lines = raw.split(`
|
|
4741
|
+
`);
|
|
4742
|
+
if (lines.length > 200) {
|
|
4743
|
+
warnings.push(`CLAUDE.md is ${lines.length} lines \u2014 official recommendation is under 200. Move reference content to skills.`);
|
|
4744
|
+
} else {
|
|
4745
|
+
passes.push(`CLAUDE.md is ${lines.length} lines (under 200 recommended limit)`);
|
|
4746
|
+
}
|
|
4747
|
+
const importRegex = /^@([^\s]+)\s*$/gm;
|
|
4748
|
+
let match;
|
|
4749
|
+
while ((match = importRegex.exec(raw)) !== null) {
|
|
4750
|
+
const importPath = match[1];
|
|
4751
|
+
const resolvedImport = resolve12(dir, importPath);
|
|
4752
|
+
if (existsSync24(resolvedImport)) {
|
|
4753
|
+
passes.push(`@import "${importPath}" exists`);
|
|
4754
|
+
} else {
|
|
4755
|
+
warnings.push(`@import "${importPath}" \u2014 file not found at ${resolvedImport}`);
|
|
4756
|
+
}
|
|
4757
|
+
}
|
|
4758
|
+
return { errors, warnings, passes };
|
|
4759
|
+
}
|
|
4760
|
+
};
|
|
4761
|
+
});
|
|
4762
|
+
|
|
4763
|
+
// src/validators/claude/lsp.ts
|
|
4764
|
+
import { existsSync as existsSync25 } from "fs";
|
|
4765
|
+
import { resolve as resolve13 } from "path";
|
|
4766
|
+
var claudeLspValidator;
|
|
4767
|
+
var init_lsp = __esm(() => {
|
|
4768
|
+
claudeLspValidator = {
|
|
4769
|
+
id: "claude:lsp",
|
|
4770
|
+
provider: "claude",
|
|
4771
|
+
name: "Claude LSP Servers",
|
|
4772
|
+
description: "Validates .lsp.json (or plugin.json lspServers): language server configs with required command + extensionToLanguage; optional transport, env, settings, diagnostics etc. (binaries installed separately)",
|
|
4773
|
+
detect(dir) {
|
|
4774
|
+
return existsSync25(resolve13(dir, ".lsp.json")) || existsSync25(resolve13(dir, ".claude-plugin", "plugin.json"));
|
|
4775
|
+
},
|
|
4776
|
+
async validate(dir, _opts) {
|
|
4777
|
+
const errors = [];
|
|
4778
|
+
const warnings = [];
|
|
4779
|
+
const passes = [];
|
|
4780
|
+
let cfg = null;
|
|
4781
|
+
const lspPath = resolve13(dir, ".lsp.json");
|
|
4782
|
+
if (existsSync25(lspPath)) {
|
|
4783
|
+
try {
|
|
4784
|
+
cfg = JSON.parse(await Bun.file(lspPath).text());
|
|
4785
|
+
passes.push(".lsp.json is valid JSON");
|
|
4786
|
+
} catch {
|
|
4787
|
+
errors.push(".lsp.json is invalid JSON");
|
|
4788
|
+
return { errors, warnings, passes };
|
|
4789
|
+
}
|
|
4790
|
+
} else {
|
|
4791
|
+
const manifestPath = resolve13(dir, ".claude-plugin", "plugin.json");
|
|
4792
|
+
if (existsSync25(manifestPath)) {
|
|
4793
|
+
try {
|
|
4794
|
+
const m = JSON.parse(await Bun.file(manifestPath).text());
|
|
4795
|
+
if (m && m.lspServers && typeof m.lspServers === "object") {
|
|
4796
|
+
cfg = m.lspServers;
|
|
4797
|
+
passes.push("lspServers present inline in plugin.json");
|
|
4798
|
+
}
|
|
4799
|
+
} catch {}
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
if (!cfg) {
|
|
4803
|
+
if (!existsSync25(lspPath)) {
|
|
4804
|
+
return { errors, warnings, passes };
|
|
4805
|
+
}
|
|
4806
|
+
}
|
|
4807
|
+
if (cfg && typeof cfg === "object") {
|
|
4808
|
+
const langs = Object.keys(cfg);
|
|
4809
|
+
passes.push(`${langs.length} language server(s) configured`);
|
|
4810
|
+
for (const lang of langs) {
|
|
4811
|
+
const entry = cfg[lang];
|
|
4812
|
+
if (!entry || !entry.command) {
|
|
4813
|
+
errors.push(`lsp "${lang}": "command" (the LSP binary) is required`);
|
|
4814
|
+
}
|
|
4815
|
+
if (!entry.extensionToLanguage || typeof entry.extensionToLanguage !== "object") {
|
|
4816
|
+
errors.push(`lsp "${lang}": "extensionToLanguage" map is required (e.g. { ".ts": "typescript" })`);
|
|
4817
|
+
} else {
|
|
4818
|
+
passes.push(`lsp "${lang}": has extensionToLanguage mapping`);
|
|
4819
|
+
}
|
|
4820
|
+
if (entry.diagnostics === false) {
|
|
4821
|
+
passes.push(`lsp "${lang}": diagnostics disabled (navigation only)`);
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4825
|
+
warnings.push('Reminder: the actual language server binary (gopls, pyright, etc.) must be installed separately on PATH. See /plugin errors tab if "Executable not found".');
|
|
4826
|
+
return { errors, warnings, passes };
|
|
4827
|
+
}
|
|
4828
|
+
};
|
|
4829
|
+
});
|
|
4830
|
+
|
|
4831
|
+
// src/validators/claude/monitors.ts
|
|
4832
|
+
import { existsSync as existsSync26 } from "fs";
|
|
4833
|
+
import { resolve as resolve14 } from "path";
|
|
4834
|
+
var claudeMonitorsValidator;
|
|
4835
|
+
var init_monitors = __esm(() => {
|
|
4836
|
+
claudeMonitorsValidator = {
|
|
4837
|
+
id: "claude:monitors",
|
|
4838
|
+
provider: "claude",
|
|
4839
|
+
name: "Claude Monitors (experimental)",
|
|
4840
|
+
description: "Validates monitors/monitors.json (or experimental.monitors): array of {name, command, description, when?}; commands support ${CLAUDE_PLUGIN_*} subs. Monitors run only in interactive CLI sessions.",
|
|
4841
|
+
detect(dir) {
|
|
4842
|
+
return existsSync26(resolve14(dir, "monitors", "monitors.json")) || existsSync26(resolve14(dir, "monitors.json")) || existsSync26(resolve14(dir, ".claude-plugin", "plugin.json"));
|
|
4843
|
+
},
|
|
4844
|
+
async validate(dir, _opts) {
|
|
4845
|
+
const errors = [];
|
|
4846
|
+
const warnings = [];
|
|
4847
|
+
const passes = [];
|
|
4848
|
+
let arr = null;
|
|
4849
|
+
const candidates = [
|
|
4850
|
+
resolve14(dir, "monitors", "monitors.json"),
|
|
4851
|
+
resolve14(dir, "monitors.json")
|
|
4852
|
+
];
|
|
4853
|
+
for (const p of candidates) {
|
|
4854
|
+
if (existsSync26(p)) {
|
|
4855
|
+
try {
|
|
4856
|
+
const parsed = JSON.parse(await Bun.file(p).text());
|
|
4857
|
+
if (Array.isArray(parsed)) {
|
|
4858
|
+
arr = parsed;
|
|
4859
|
+
passes.push("monitors config is valid JSON array");
|
|
4860
|
+
}
|
|
4861
|
+
break;
|
|
4862
|
+
} catch {
|
|
4863
|
+
errors.push("monitors config is invalid JSON");
|
|
4864
|
+
return { errors, warnings, passes };
|
|
4865
|
+
}
|
|
4866
|
+
}
|
|
4867
|
+
}
|
|
4868
|
+
if (!arr) {
|
|
4869
|
+
const mp = resolve14(dir, ".claude-plugin", "plugin.json");
|
|
4870
|
+
if (existsSync26(mp)) {
|
|
4871
|
+
try {
|
|
4872
|
+
const m = JSON.parse(await Bun.file(mp).text());
|
|
4873
|
+
const exp = m?.experimental;
|
|
4874
|
+
const inline = typeof exp === "string" ? null : exp?.monitors;
|
|
4875
|
+
if (Array.isArray(inline))
|
|
4876
|
+
arr = inline;
|
|
4877
|
+
else if (typeof inline === "string") {
|
|
4878
|
+
passes.push("experimental.monitors declared as path in manifest (content not validated here)");
|
|
4879
|
+
}
|
|
4880
|
+
} catch {}
|
|
3488
4881
|
}
|
|
3489
|
-
}
|
|
4882
|
+
}
|
|
4883
|
+
if (!arr) {
|
|
4884
|
+
return { errors, warnings, passes };
|
|
4885
|
+
}
|
|
4886
|
+
if (!Array.isArray(arr)) {
|
|
4887
|
+
errors.push("monitors config must be a JSON array");
|
|
4888
|
+
return { errors, warnings, passes };
|
|
4889
|
+
}
|
|
4890
|
+
const seen = new Set;
|
|
4891
|
+
arr.forEach((mon, i) => {
|
|
4892
|
+
if (!mon || typeof mon !== "object") {
|
|
4893
|
+
errors.push(`monitors[${i}]: entry must be an object`);
|
|
4894
|
+
return;
|
|
4895
|
+
}
|
|
4896
|
+
if (!mon.name || typeof mon.name !== "string") {
|
|
4897
|
+
errors.push(`monitors[${i}]: "name" (unique id) is required`);
|
|
4898
|
+
} else {
|
|
4899
|
+
if (seen.has(mon.name))
|
|
4900
|
+
errors.push(`monitors: duplicate name "${mon.name}"`);
|
|
4901
|
+
seen.add(mon.name);
|
|
4902
|
+
}
|
|
4903
|
+
if (!mon.command || typeof mon.command !== "string") {
|
|
4904
|
+
errors.push(`monitors[${i}]: "command" (shell command) is required`);
|
|
4905
|
+
} else if (/\$\{CLAUDE_/.test(mon.command)) {
|
|
4906
|
+
passes.push(`monitors[${i}] "${mon.name || i}": uses CLAUDE_PLUGIN_* substitution`);
|
|
4907
|
+
}
|
|
4908
|
+
if (!mon.description) {
|
|
4909
|
+
warnings.push(`monitors[${i}]: "description" recommended (shown in task panel)`);
|
|
4910
|
+
}
|
|
4911
|
+
if (mon.when && !/^always$|^on-skill-invoke:/.test(String(mon.when))) {
|
|
4912
|
+
warnings.push(`monitors[${i}]: "when" should be "always" (default) or "on-skill-invoke:<skill>"`);
|
|
4913
|
+
}
|
|
4914
|
+
});
|
|
4915
|
+
passes.push(`${arr.length} monitor(s) declared`);
|
|
4916
|
+
warnings.push("Note: monitors are experimental, run only for interactive CLI sessions, and are skipped on some hosts. They do not stop automatically if the plugin is disabled mid-session.");
|
|
4917
|
+
return { errors, warnings, passes };
|
|
4918
|
+
}
|
|
4919
|
+
};
|
|
4920
|
+
});
|
|
4921
|
+
|
|
4922
|
+
// src/validators/codex/plugin.ts
|
|
4923
|
+
import { existsSync as existsSync27, readdirSync as readdirSync12 } from "fs";
|
|
4924
|
+
import { resolve as resolve15, join as join20 } from "path";
|
|
4925
|
+
var NAME_REGEX3, codexPluginValidator;
|
|
4926
|
+
var init_plugin2 = __esm(() => {
|
|
4927
|
+
NAME_REGEX3 = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
4928
|
+
codexPluginValidator = {
|
|
4929
|
+
id: "codex:plugin",
|
|
4930
|
+
provider: "codex",
|
|
4931
|
+
name: "Codex Plugin",
|
|
4932
|
+
description: "Validates .codex-plugin/plugin.json manifest (requires interface block and skills as directory string per Codex packaging)",
|
|
4933
|
+
detect(dir) {
|
|
4934
|
+
return existsSync27(resolve15(dir, ".codex-plugin", "plugin.json"));
|
|
4935
|
+
},
|
|
4936
|
+
async validate(dir, _opts) {
|
|
4937
|
+
const errors = [];
|
|
4938
|
+
const warnings = [];
|
|
4939
|
+
const passes = [];
|
|
4940
|
+
const manifestPath = resolve15(dir, ".codex-plugin", "plugin.json");
|
|
4941
|
+
let manifest;
|
|
4942
|
+
try {
|
|
4943
|
+
const raw = await Bun.file(manifestPath).text();
|
|
4944
|
+
manifest = JSON.parse(raw);
|
|
4945
|
+
passes.push(".codex-plugin/plugin.json is valid JSON");
|
|
4946
|
+
} catch {
|
|
4947
|
+
errors.push(".codex-plugin/plugin.json is missing or invalid JSON");
|
|
4948
|
+
return { errors, warnings, passes };
|
|
4949
|
+
}
|
|
3490
4950
|
if (!manifest.name) {
|
|
3491
4951
|
errors.push('Missing required field: "name"');
|
|
3492
4952
|
} else {
|
|
3493
4953
|
const name = String(manifest.name);
|
|
3494
|
-
if (!
|
|
4954
|
+
if (!NAME_REGEX3.test(name)) {
|
|
3495
4955
|
errors.push(`Invalid name format: "${name}" \u2014 must be kebab-case (a-z, 0-9, hyphens)`);
|
|
3496
4956
|
} else {
|
|
3497
4957
|
passes.push(`name: "${name}"`);
|
|
3498
4958
|
}
|
|
3499
4959
|
}
|
|
4960
|
+
if (manifest.skills === undefined) {
|
|
4961
|
+
errors.push('Missing required field: "skills" (must be a directory string like "./skills/")');
|
|
4962
|
+
} else if (typeof manifest.skills !== "string") {
|
|
4963
|
+
errors.push('"skills" must be a string directory path');
|
|
4964
|
+
} else {
|
|
4965
|
+
const s = manifest.skills;
|
|
4966
|
+
if (!s.startsWith("./")) {
|
|
4967
|
+
warnings.push('"skills" should start with "./"');
|
|
4968
|
+
}
|
|
4969
|
+
passes.push(`skills: "${s}" (directory string)`);
|
|
4970
|
+
}
|
|
4971
|
+
if (!manifest.interface || typeof manifest.interface !== "object") {
|
|
4972
|
+
errors.push('Missing required "interface" object (Codex uses it for displayName, shortDescription, category, etc.)');
|
|
4973
|
+
} else {
|
|
4974
|
+
const iface = manifest.interface;
|
|
4975
|
+
if (iface.displayName) {
|
|
4976
|
+
passes.push(`interface.displayName: "${iface.displayName}"`);
|
|
4977
|
+
} else {
|
|
4978
|
+
warnings.push("interface.displayName recommended");
|
|
4979
|
+
}
|
|
4980
|
+
if (iface.category) {
|
|
4981
|
+
passes.push(`interface.category: "${iface.category}"`);
|
|
4982
|
+
}
|
|
4983
|
+
passes.push("interface block present");
|
|
4984
|
+
}
|
|
3500
4985
|
if (manifest.version !== undefined) {
|
|
3501
4986
|
const v = String(manifest.version);
|
|
3502
4987
|
if (!/^\d+\.\d+\.\d+/.test(v)) {
|
|
3503
|
-
|
|
4988
|
+
warnings.push(`version "${v}" should look like semver for explicit versioning`);
|
|
3504
4989
|
} else {
|
|
3505
|
-
passes.push(`version: "${v}"
|
|
4990
|
+
passes.push(`version: "${v}"`);
|
|
3506
4991
|
}
|
|
3507
4992
|
} else {
|
|
3508
|
-
passes.push("version omitted (git commit SHA used as version key
|
|
4993
|
+
passes.push("version omitted (git commit SHA used as version key)");
|
|
3509
4994
|
}
|
|
3510
4995
|
if (manifest.description !== undefined) {
|
|
3511
4996
|
const desc = String(manifest.description);
|
|
@@ -3515,189 +5000,401 @@ var init_plugin = __esm(() => {
|
|
|
3515
5000
|
passes.push("description field present");
|
|
3516
5001
|
}
|
|
3517
5002
|
} else {
|
|
3518
|
-
warnings.push('Missing "description" (recommended
|
|
5003
|
+
warnings.push('Missing "description" (recommended)');
|
|
3519
5004
|
}
|
|
3520
|
-
|
|
3521
|
-
|
|
5005
|
+
const skillsDir = resolve15(dir, "skills");
|
|
5006
|
+
if (existsSync27(skillsDir)) {
|
|
5007
|
+
const entries = readdirSync12(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
5008
|
+
for (const e of entries) {
|
|
5009
|
+
const md = join20(skillsDir, e.name, "SKILL.md");
|
|
5010
|
+
if (existsSync27(md)) {
|
|
5011
|
+
passes.push(`skills/${e.name}/SKILL.md exists`);
|
|
5012
|
+
} else {
|
|
5013
|
+
errors.push(`skills/${e.name}/ is missing SKILL.md`);
|
|
5014
|
+
}
|
|
5015
|
+
}
|
|
3522
5016
|
}
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
5017
|
+
const known = new Set(["name", "version", "description", "skills", "interface", "author", "homepage", "repository", "license", "keywords"]);
|
|
5018
|
+
const unknown = Object.keys(manifest).filter((k) => !known.has(k));
|
|
5019
|
+
for (const k of unknown) {
|
|
5020
|
+
warnings.push(`Unrecognized field "${k}" \u2014 will be ignored (for compatibility)`);
|
|
5021
|
+
}
|
|
5022
|
+
return { errors, warnings, passes };
|
|
5023
|
+
}
|
|
5024
|
+
};
|
|
5025
|
+
});
|
|
5026
|
+
|
|
5027
|
+
// src/validators/codex/marketplace.ts
|
|
5028
|
+
import { existsSync as existsSync28 } from "fs";
|
|
5029
|
+
import { resolve as resolve16 } from "path";
|
|
5030
|
+
var codexMarketplaceValidator;
|
|
5031
|
+
var init_marketplace2 = __esm(() => {
|
|
5032
|
+
codexMarketplaceValidator = {
|
|
5033
|
+
id: "codex:marketplace",
|
|
5034
|
+
provider: "codex",
|
|
5035
|
+
name: "Codex Plugin Marketplace",
|
|
5036
|
+
description: "Validates .agents/plugins/marketplace.json (Codex convention: object source + policy blocks)",
|
|
5037
|
+
detect(dir) {
|
|
5038
|
+
if (existsSync28(resolve16(dir, ".agents", "plugins", "marketplace.json")))
|
|
5039
|
+
return true;
|
|
5040
|
+
if (existsSync28(resolve16(dir, ".agents", "plugins", "marketplace.json")))
|
|
5041
|
+
return true;
|
|
5042
|
+
return false;
|
|
5043
|
+
},
|
|
5044
|
+
async validate(dir, _opts) {
|
|
5045
|
+
const errors = [];
|
|
5046
|
+
const warnings = [];
|
|
5047
|
+
const passes = [];
|
|
5048
|
+
const marketplacePath = resolve16(dir, ".agents", "plugins", "marketplace.json");
|
|
5049
|
+
if (!existsSync28(marketplacePath)) {
|
|
5050
|
+
errors.push("Missing .agents/plugins/marketplace.json");
|
|
5051
|
+
return { errors, warnings, passes };
|
|
5052
|
+
}
|
|
5053
|
+
let marketplace;
|
|
5054
|
+
try {
|
|
5055
|
+
const raw = await Bun.file(marketplacePath).text();
|
|
5056
|
+
marketplace = JSON.parse(raw);
|
|
5057
|
+
passes.push(".agents/plugins/marketplace.json is valid JSON");
|
|
5058
|
+
} catch {
|
|
5059
|
+
errors.push(".agents/plugins/marketplace.json is missing or invalid JSON");
|
|
5060
|
+
return { errors, warnings, passes };
|
|
5061
|
+
}
|
|
5062
|
+
if (marketplace.name) {
|
|
5063
|
+
passes.push(`name: "${marketplace.name}"`);
|
|
5064
|
+
} else {
|
|
5065
|
+
warnings.push('Missing "name" at marketplace root');
|
|
5066
|
+
}
|
|
5067
|
+
if (marketplace.interface && typeof marketplace.interface === "object") {
|
|
5068
|
+
const iface = marketplace.interface;
|
|
5069
|
+
if (iface.displayName) {
|
|
5070
|
+
passes.push(`interface.displayName: "${iface.displayName}"`);
|
|
5071
|
+
}
|
|
5072
|
+
passes.push("interface block present");
|
|
5073
|
+
} else {
|
|
5074
|
+
warnings.push('Recommended: "interface" with displayName at marketplace root');
|
|
5075
|
+
}
|
|
5076
|
+
if (!Array.isArray(marketplace.plugins) || marketplace.plugins.length === 0) {
|
|
5077
|
+
errors.push('"plugins" must be a non-empty array');
|
|
5078
|
+
return { errors, warnings, passes };
|
|
5079
|
+
}
|
|
5080
|
+
passes.push(`${marketplace.plugins.length} plugin(s) declared`);
|
|
5081
|
+
for (const [i, p] of marketplace.plugins.entries()) {
|
|
5082
|
+
if (!p || typeof p !== "object") {
|
|
5083
|
+
errors.push(`plugins[${i}]: must be an object`);
|
|
5084
|
+
continue;
|
|
5085
|
+
}
|
|
5086
|
+
if (!p.name) {
|
|
5087
|
+
errors.push(`plugins[${i}]: missing "name"`);
|
|
3527
5088
|
} else {
|
|
3528
|
-
|
|
5089
|
+
passes.push(`plugins[${i}].name: "${p.name}"`);
|
|
5090
|
+
}
|
|
5091
|
+
if (!p.source || typeof p.source !== "object") {
|
|
5092
|
+
errors.push(`plugins[${i}].source: must be an object like { "source": "local", "path": "..." }`);
|
|
5093
|
+
} else {
|
|
5094
|
+
if (p.source.source) {
|
|
5095
|
+
passes.push(`plugins[${i}].source.source: "${p.source.source}"`);
|
|
5096
|
+
} else {
|
|
5097
|
+
warnings.push(`plugins[${i}].source: missing "source"`);
|
|
5098
|
+
}
|
|
5099
|
+
if (p.source.path) {
|
|
5100
|
+
const pathStr = String(p.source.path);
|
|
5101
|
+
if (!pathStr.startsWith("./") && !pathStr.startsWith("../")) {
|
|
5102
|
+
warnings.push(`plugins[${i}].source.path: "${pathStr}" should be relative (./ or ../)`);
|
|
5103
|
+
}
|
|
5104
|
+
passes.push(`plugins[${i}].source.path: "${pathStr}"`);
|
|
5105
|
+
} else {
|
|
5106
|
+
errors.push(`plugins[${i}].source: missing "path"`);
|
|
5107
|
+
}
|
|
5108
|
+
}
|
|
5109
|
+
if (p.policy && typeof p.policy === "object") {
|
|
5110
|
+
passes.push(`plugins[${i}].policy present`);
|
|
5111
|
+
if (p.policy.installation) {
|
|
5112
|
+
passes.push(`plugins[${i}].policy.installation: "${p.policy.installation}"`);
|
|
5113
|
+
}
|
|
5114
|
+
if (p.policy.authentication) {
|
|
5115
|
+
passes.push(`plugins[${i}].policy.authentication: "${p.policy.authentication}"`);
|
|
5116
|
+
}
|
|
5117
|
+
} else {
|
|
5118
|
+
warnings.push(`plugins[${i}]: "policy" recommended (installation/authentication)`);
|
|
5119
|
+
}
|
|
5120
|
+
if (p.category) {
|
|
5121
|
+
passes.push(`plugins[${i}].category: "${p.category}"`);
|
|
3529
5122
|
}
|
|
3530
5123
|
}
|
|
3531
|
-
if (
|
|
3532
|
-
passes.push(
|
|
5124
|
+
if (existsSync28(resolve16(dir, "README.md"))) {
|
|
5125
|
+
passes.push("README.md exists at marketplace root");
|
|
5126
|
+
} else {
|
|
5127
|
+
warnings.push("No README.md at marketplace root \u2014 recommended");
|
|
3533
5128
|
}
|
|
3534
|
-
if (
|
|
3535
|
-
|
|
3536
|
-
|
|
5129
|
+
if (existsSync28(resolve16(dir, "LICENSE"))) {
|
|
5130
|
+
passes.push("LICENSE exists at marketplace root");
|
|
5131
|
+
} else {
|
|
5132
|
+
warnings.push("No LICENSE at marketplace root \u2014 recommended");
|
|
5133
|
+
}
|
|
5134
|
+
return { errors, warnings, passes };
|
|
5135
|
+
}
|
|
5136
|
+
};
|
|
5137
|
+
});
|
|
5138
|
+
|
|
5139
|
+
// src/validators/codex/mcp.ts
|
|
5140
|
+
import { existsSync as existsSync29 } from "fs";
|
|
5141
|
+
import { resolve as resolve17 } from "path";
|
|
5142
|
+
var codexMcpValidator;
|
|
5143
|
+
var init_mcp2 = __esm(() => {
|
|
5144
|
+
codexMcpValidator = {
|
|
5145
|
+
id: "codex:mcp",
|
|
5146
|
+
provider: "codex",
|
|
5147
|
+
name: "Codex MCP Config",
|
|
5148
|
+
description: "Validates .mcp.json (or inline via plugin.json mcpServers): server entries (stdio: command+args, or url), env, cwd, substitutions per Codex MCP support",
|
|
5149
|
+
detect(dir) {
|
|
5150
|
+
return existsSync29(resolve17(dir, ".mcp.json"));
|
|
5151
|
+
},
|
|
5152
|
+
async validate(dir, _opts) {
|
|
5153
|
+
const errors = [];
|
|
5154
|
+
const warnings = [];
|
|
5155
|
+
const passes = [];
|
|
5156
|
+
const mcpPath = resolve17(dir, ".mcp.json");
|
|
5157
|
+
let config;
|
|
5158
|
+
try {
|
|
5159
|
+
const raw = await Bun.file(mcpPath).text();
|
|
5160
|
+
config = JSON.parse(raw);
|
|
5161
|
+
passes.push(".mcp.json is valid JSON");
|
|
5162
|
+
} catch {
|
|
5163
|
+
errors.push(".mcp.json is missing or invalid JSON");
|
|
5164
|
+
return { errors, warnings, passes };
|
|
5165
|
+
}
|
|
5166
|
+
if (typeof config !== "object" || Array.isArray(config)) {
|
|
5167
|
+
errors.push(".mcp.json must be a JSON object with server name keys");
|
|
5168
|
+
return { errors, warnings, passes };
|
|
5169
|
+
}
|
|
5170
|
+
const serverNames = Object.keys(config);
|
|
5171
|
+
if (serverNames.length === 0) {
|
|
5172
|
+
warnings.push(".mcp.json is empty \u2014 no servers defined");
|
|
5173
|
+
return { errors, warnings, passes };
|
|
5174
|
+
}
|
|
5175
|
+
passes.push(`${serverNames.length} server(s) defined`);
|
|
5176
|
+
for (const [name, entry] of Object.entries(config)) {
|
|
5177
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
5178
|
+
errors.push(`mcp server "${name}": definition must be an object`);
|
|
5179
|
+
continue;
|
|
5180
|
+
}
|
|
5181
|
+
const e = entry;
|
|
5182
|
+
const hasCommand = typeof e.command === "string";
|
|
5183
|
+
const hasUrl = typeof e.url === "string";
|
|
5184
|
+
if (!hasCommand && !hasUrl) {
|
|
5185
|
+
errors.push(`mcp server "${name}": must have either "command" (for stdio) or "url" (for SSE/HTTP)`);
|
|
5186
|
+
}
|
|
5187
|
+
if (hasCommand && !Array.isArray(e.args)) {
|
|
5188
|
+
warnings.push(`mcp server "${name}": "command" present but no "args" array (ok for some servers)`);
|
|
5189
|
+
}
|
|
5190
|
+
if (hasUrl && hasCommand) {
|
|
5191
|
+
warnings.push(`mcp server "${name}": both "command" and "url" present \u2014 usually one or the other`);
|
|
5192
|
+
}
|
|
5193
|
+
if (e.env && typeof e.env === "object") {
|
|
5194
|
+
passes.push(`mcp server "${name}": has env`);
|
|
5195
|
+
}
|
|
5196
|
+
if (typeof e.cwd === "string") {
|
|
5197
|
+
passes.push(`mcp server "${name}": has cwd`);
|
|
5198
|
+
}
|
|
5199
|
+
const hasSubs = JSON.stringify(e).match(/\$\{CODEX_|CLAUDE_PLUGIN_|user_config\.|ENV_VAR\}/);
|
|
5200
|
+
if (hasSubs) {
|
|
5201
|
+
passes.push(`mcp server "${name}": uses substitutions (e.g. \${CODEX_*} or env)`);
|
|
5202
|
+
}
|
|
5203
|
+
}
|
|
5204
|
+
return { errors, warnings, passes };
|
|
5205
|
+
}
|
|
5206
|
+
};
|
|
5207
|
+
});
|
|
5208
|
+
|
|
5209
|
+
// src/validators/codex/skill.ts
|
|
5210
|
+
import { existsSync as existsSync30 } from "fs";
|
|
5211
|
+
import { resolve as resolve18 } from "path";
|
|
5212
|
+
var codexSkillValidator;
|
|
5213
|
+
var init_skill2 = __esm(() => {
|
|
5214
|
+
init_skill_validate();
|
|
5215
|
+
codexSkillValidator = {
|
|
5216
|
+
id: "codex:skill",
|
|
5217
|
+
provider: "codex",
|
|
5218
|
+
name: "Codex Skill",
|
|
5219
|
+
description: "Validates SKILL.md (shared format): frontmatter (name/description), body, supporting files, substitutions. Codex uses the same SKILL.md spec as other providers.",
|
|
5220
|
+
detect(dir) {
|
|
5221
|
+
return existsSync30(resolve18(dir, "SKILL.md"));
|
|
5222
|
+
},
|
|
5223
|
+
async validate(dir, _opts) {
|
|
5224
|
+
const loaded = await loadSkill(dir);
|
|
5225
|
+
if (!loaded.ok) {
|
|
5226
|
+
return {
|
|
5227
|
+
errors: [loaded.error],
|
|
5228
|
+
warnings: [],
|
|
5229
|
+
passes: []
|
|
5230
|
+
};
|
|
5231
|
+
}
|
|
5232
|
+
const { model, existingDirs } = loaded;
|
|
5233
|
+
return validateSkillModel(model, { existingDirs: [...existingDirs] });
|
|
5234
|
+
}
|
|
5235
|
+
};
|
|
5236
|
+
});
|
|
5237
|
+
|
|
5238
|
+
// src/validators/cursor/plugin.ts
|
|
5239
|
+
import { existsSync as existsSync31, readdirSync as readdirSync14 } from "fs";
|
|
5240
|
+
import { resolve as resolve19, join as join22 } from "path";
|
|
5241
|
+
var NAME_REGEX4, cursorPluginValidator;
|
|
5242
|
+
var init_plugin3 = __esm(() => {
|
|
5243
|
+
NAME_REGEX4 = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
5244
|
+
cursorPluginValidator = {
|
|
5245
|
+
id: "cursor:plugin",
|
|
5246
|
+
provider: "cursor",
|
|
5247
|
+
name: "Cursor Plugin",
|
|
5248
|
+
description: "Validates .cursor-plugin/plugin.json manifest (skills as directory string; mcpServers support)",
|
|
5249
|
+
detect(dir) {
|
|
5250
|
+
return existsSync31(resolve19(dir, ".cursor-plugin", "plugin.json"));
|
|
5251
|
+
},
|
|
5252
|
+
async validate(dir, _opts) {
|
|
5253
|
+
const errors = [];
|
|
5254
|
+
const warnings = [];
|
|
5255
|
+
const passes = [];
|
|
5256
|
+
const manifestPath = resolve19(dir, ".cursor-plugin", "plugin.json");
|
|
5257
|
+
let manifest;
|
|
5258
|
+
try {
|
|
5259
|
+
const raw = await Bun.file(manifestPath).text();
|
|
5260
|
+
manifest = JSON.parse(raw);
|
|
5261
|
+
passes.push(".cursor-plugin/plugin.json is valid JSON");
|
|
5262
|
+
} catch {
|
|
5263
|
+
errors.push(".cursor-plugin/plugin.json is missing or invalid JSON");
|
|
5264
|
+
return { errors, warnings, passes };
|
|
5265
|
+
}
|
|
5266
|
+
if (!manifest.name) {
|
|
5267
|
+
errors.push('Missing required field: "name"');
|
|
5268
|
+
} else {
|
|
5269
|
+
const name = String(manifest.name);
|
|
5270
|
+
if (!NAME_REGEX4.test(name)) {
|
|
5271
|
+
errors.push(`Invalid name format: "${name}" \u2014 must be kebab-case (a-z, 0-9, hyphens)`);
|
|
5272
|
+
} else {
|
|
5273
|
+
passes.push(`name: "${name}"`);
|
|
5274
|
+
}
|
|
5275
|
+
}
|
|
5276
|
+
if (manifest.skills === undefined) {
|
|
5277
|
+
errors.push('Missing required field: "skills" (must be a directory string like "./skills")');
|
|
5278
|
+
} else if (typeof manifest.skills !== "string") {
|
|
5279
|
+
errors.push('"skills" must be a string directory path');
|
|
5280
|
+
} else {
|
|
5281
|
+
const s = manifest.skills;
|
|
5282
|
+
if (!s.startsWith("./")) {
|
|
5283
|
+
warnings.push('"skills" should start with "./"');
|
|
5284
|
+
}
|
|
5285
|
+
passes.push(`skills: "${s}" (directory string)`);
|
|
5286
|
+
}
|
|
5287
|
+
if (manifest.mcpServers !== undefined) {
|
|
5288
|
+
if (typeof manifest.mcpServers === "string") {
|
|
5289
|
+
passes.push(`mcpServers: "${manifest.mcpServers}"`);
|
|
5290
|
+
} else {
|
|
5291
|
+
warnings.push('"mcpServers" should be a string path when present');
|
|
5292
|
+
}
|
|
5293
|
+
}
|
|
5294
|
+
if (manifest.displayName) {
|
|
5295
|
+
passes.push(`displayName: "${manifest.displayName}"`);
|
|
5296
|
+
} else {
|
|
5297
|
+
warnings.push("displayName recommended for Cursor UI");
|
|
5298
|
+
}
|
|
5299
|
+
if (manifest.version !== undefined) {
|
|
5300
|
+
const v = String(manifest.version);
|
|
5301
|
+
if (!/^\d+\.\d+\.\d+/.test(v)) {
|
|
5302
|
+
warnings.push(`version "${v}" should look like semver`);
|
|
3537
5303
|
} else {
|
|
3538
|
-
|
|
5304
|
+
passes.push(`version: "${v}"`);
|
|
3539
5305
|
}
|
|
5306
|
+
} else {
|
|
5307
|
+
passes.push("version omitted (git commit SHA used as version key)");
|
|
3540
5308
|
}
|
|
3541
|
-
if (manifest.
|
|
3542
|
-
|
|
5309
|
+
if (manifest.description !== undefined) {
|
|
5310
|
+
const desc = String(manifest.description);
|
|
5311
|
+
if (desc.length < 10) {
|
|
5312
|
+
warnings.push(`Description is very short (${desc.length} chars) \u2014 50-200 chars recommended`);
|
|
5313
|
+
} else {
|
|
5314
|
+
passes.push("description field present");
|
|
5315
|
+
}
|
|
5316
|
+
} else {
|
|
5317
|
+
warnings.push('Missing "description" (recommended)');
|
|
3543
5318
|
}
|
|
5319
|
+
if (manifest.author)
|
|
5320
|
+
passes.push("author present");
|
|
5321
|
+
if (manifest.license)
|
|
5322
|
+
passes.push(`license: "${manifest.license}"`);
|
|
3544
5323
|
if (manifest.homepage)
|
|
3545
5324
|
passes.push("homepage present");
|
|
3546
5325
|
if (manifest.repository)
|
|
3547
5326
|
passes.push("repository present");
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
const sug = suggestField(k);
|
|
3551
|
-
const hint = sug ? ` (did you mean "${sug}"?)` : "";
|
|
3552
|
-
warnings.push(`Unrecognized top-level field "${k}"${hint} \u2014 will be ignored at runtime (allowed for cross-tool manifest compatibility).`);
|
|
3553
|
-
}
|
|
3554
|
-
const handleField = (field, val) => {
|
|
3555
|
-
if (val === undefined || val === null)
|
|
3556
|
-
return;
|
|
3557
|
-
if (isRelativePathLike(val) || Array.isArray(val) && val.every(isRelativePathLike)) {
|
|
3558
|
-
const arr = Array.isArray(val) ? val : [val];
|
|
3559
|
-
for (const p of arr) {
|
|
3560
|
-
const s = String(p);
|
|
3561
|
-
if (!RELATIVE_PATH_REGEX.test(s)) {
|
|
3562
|
-
errors.push(`${field}: path "${s}" must start with "./"`);
|
|
3563
|
-
} else if (s.includes("..")) {
|
|
3564
|
-
errors.push(`${field}: path "${s}" must not use ".." (paths are confined to the plugin tree after cache copy)`);
|
|
3565
|
-
} else if (existsSync14(resolve6(dir, s))) {
|
|
3566
|
-
passes.push(`${field}: path "${s}" exists`);
|
|
3567
|
-
} else {
|
|
3568
|
-
warnings.push(`${field}: path "${s}" does not exist on disk`);
|
|
3569
|
-
}
|
|
3570
|
-
}
|
|
3571
|
-
if (field === "skills") {
|
|
3572
|
-
passes.push(`${field}: augments the default skills/ (both are scanned)`);
|
|
3573
|
-
} else if (REPLACES_DEFAULT.has(field)) {
|
|
3574
|
-
passes.push(`${field}: custom path replaces default ${field}/ scan`);
|
|
3575
|
-
} else {
|
|
3576
|
-
passes.push(`${field}: custom path or config (merge rules apply)`);
|
|
3577
|
-
}
|
|
3578
|
-
} else if (typeof val === "object") {
|
|
3579
|
-
passes.push(`${field}: inline ${field} config present`);
|
|
3580
|
-
}
|
|
3581
|
-
};
|
|
3582
|
-
["skills", "commands", "agents", "hooks", "mcpServers", "outputStyles", "lspServers"].forEach((f) => {
|
|
3583
|
-
if (manifest[f] !== undefined)
|
|
3584
|
-
handleField(f, manifest[f]);
|
|
3585
|
-
});
|
|
3586
|
-
if (manifest.experimental && typeof manifest.experimental === "object") {
|
|
3587
|
-
const exp = manifest.experimental;
|
|
3588
|
-
if (exp.themes !== undefined)
|
|
3589
|
-
handleField("experimental.themes", exp.themes);
|
|
3590
|
-
if (exp.monitors !== undefined)
|
|
3591
|
-
handleField("experimental.monitors", exp.monitors);
|
|
3592
|
-
passes.push("experimental section present (themes and monitors are experimental components)");
|
|
3593
|
-
}
|
|
3594
|
-
if (manifest.userConfig && typeof manifest.userConfig === "object") {
|
|
3595
|
-
const keys = Object.keys(manifest.userConfig);
|
|
3596
|
-
passes.push(`userConfig: ${keys.length} user-configurable value(s) declared`);
|
|
3597
|
-
for (const k of keys) {
|
|
3598
|
-
const opt = manifest.userConfig[k];
|
|
3599
|
-
if (!opt || !opt.type || !opt.title) {
|
|
3600
|
-
warnings.push(`userConfig.${k} is missing required "type" and/or "title"`);
|
|
3601
|
-
}
|
|
3602
|
-
}
|
|
3603
|
-
}
|
|
3604
|
-
if (Array.isArray(manifest.channels)) {
|
|
3605
|
-
passes.push(`channels: ${manifest.channels.length} channel(s) (each binds to an mcpServer)`);
|
|
3606
|
-
manifest.channels.forEach((ch, i) => {
|
|
3607
|
-
if (!ch?.server)
|
|
3608
|
-
warnings.push(`channels[${i}]: "server" is required and must match an mcpServers key`);
|
|
3609
|
-
});
|
|
5327
|
+
if (Array.isArray(manifest.keywords)) {
|
|
5328
|
+
passes.push(`keywords: [${manifest.keywords.join(", ")}]`);
|
|
3610
5329
|
}
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
const skillsDir = resolve6(dir, "skills");
|
|
3615
|
-
if (existsSync14(skillsDir)) {
|
|
3616
|
-
const entries = readdirSync6(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
5330
|
+
const skillsDir = resolve19(dir, "skills");
|
|
5331
|
+
if (existsSync31(skillsDir)) {
|
|
5332
|
+
const entries = readdirSync14(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
3617
5333
|
for (const e of entries) {
|
|
3618
|
-
const md =
|
|
3619
|
-
if (
|
|
5334
|
+
const md = join22(skillsDir, e.name, "SKILL.md");
|
|
5335
|
+
if (existsSync31(md)) {
|
|
3620
5336
|
passes.push(`skills/${e.name}/SKILL.md exists`);
|
|
3621
5337
|
} else {
|
|
3622
5338
|
errors.push(`skills/${e.name}/ is missing SKILL.md`);
|
|
3623
5339
|
}
|
|
3624
5340
|
}
|
|
3625
|
-
if (manifest.skills !== undefined) {
|
|
3626
|
-
warnings.push('Default skills/ dir co-exists with manifest "skills" \u2014 manifest path is authoritative; default folder ignored for loading');
|
|
3627
|
-
}
|
|
3628
|
-
}
|
|
3629
|
-
const commandsDir = resolve6(dir, "commands");
|
|
3630
|
-
if (existsSync14(commandsDir)) {
|
|
3631
|
-
const mds = readdirSync6(commandsDir).filter((f) => f.endsWith(".md"));
|
|
3632
|
-
if (mds.length) {
|
|
3633
|
-
passes.push(`commands/ has ${mds.length} .md file(s)`);
|
|
3634
|
-
}
|
|
3635
|
-
if (manifest.commands !== undefined) {
|
|
3636
|
-
warnings.push('commands/ co-exists with manifest "commands" \u2014 manifest replaces default (dir ignored)');
|
|
3637
|
-
}
|
|
3638
5341
|
}
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
5342
|
+
if (typeof manifest.mcpServers === "string") {
|
|
5343
|
+
const mcpRef = manifest.mcpServers;
|
|
5344
|
+
if (mcpRef.startsWith("./") || mcpRef.startsWith("../")) {
|
|
5345
|
+
const mcpPath = resolve19(dir, mcpRef);
|
|
5346
|
+
if (existsSync31(mcpPath)) {
|
|
5347
|
+
passes.push(`mcpServers file exists at ${mcpRef}`);
|
|
5348
|
+
} else {
|
|
5349
|
+
warnings.push(`mcpServers path "${mcpRef}" does not exist on disk`);
|
|
5350
|
+
}
|
|
3647
5351
|
}
|
|
3648
5352
|
}
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
if (existsSync14(resolve6(dir, ".mcp.json")))
|
|
3666
|
-
passes.push(".mcp.json present (validated by claude:mcp)");
|
|
3667
|
-
if (existsSync14(resolve6(dir, ".lsp.json")))
|
|
3668
|
-
passes.push(".lsp.json present (validated by claude:lsp when registered)");
|
|
3669
|
-
if (existsSync14(resolve6(dir, "hooks/hooks.json")) || existsSync14(resolve6(dir, "hooks.json"))) {
|
|
3670
|
-
passes.push("hooks config present (validated by claude:hooks)");
|
|
3671
|
-
}
|
|
3672
|
-
if (existsSync14(resolve6(dir, "SKILL.md")) && !existsSync14(skillsDir) && manifest.skills === undefined) {
|
|
3673
|
-
passes.push('Root SKILL.md detected \u2014 plugin will be treated as a single-skill plugin (prefer frontmatter "name" for stable /command)');
|
|
5353
|
+
const known = new Set([
|
|
5354
|
+
"name",
|
|
5355
|
+
"displayName",
|
|
5356
|
+
"version",
|
|
5357
|
+
"description",
|
|
5358
|
+
"author",
|
|
5359
|
+
"homepage",
|
|
5360
|
+
"repository",
|
|
5361
|
+
"license",
|
|
5362
|
+
"keywords",
|
|
5363
|
+
"skills",
|
|
5364
|
+
"mcpServers"
|
|
5365
|
+
]);
|
|
5366
|
+
const unknown = Object.keys(manifest).filter((k) => !known.has(k));
|
|
5367
|
+
for (const k of unknown) {
|
|
5368
|
+
warnings.push(`Unrecognized field "${k}" \u2014 will be ignored (for compatibility)`);
|
|
3674
5369
|
}
|
|
3675
5370
|
return { errors, warnings, passes };
|
|
3676
5371
|
}
|
|
3677
5372
|
};
|
|
3678
5373
|
});
|
|
3679
5374
|
|
|
3680
|
-
// src/validators/
|
|
3681
|
-
import { existsSync as
|
|
3682
|
-
import { resolve as
|
|
3683
|
-
var
|
|
3684
|
-
var
|
|
3685
|
-
|
|
3686
|
-
id: "
|
|
3687
|
-
provider: "
|
|
3688
|
-
name: "
|
|
3689
|
-
description: "Validates marketplace
|
|
5375
|
+
// src/validators/cursor/marketplace.ts
|
|
5376
|
+
import { existsSync as existsSync32, readdirSync as readdirSync15 } from "fs";
|
|
5377
|
+
import { resolve as resolve20, join as join23 } from "path";
|
|
5378
|
+
var cursorMarketplaceValidator;
|
|
5379
|
+
var init_marketplace3 = __esm(() => {
|
|
5380
|
+
cursorMarketplaceValidator = {
|
|
5381
|
+
id: "cursor:marketplace",
|
|
5382
|
+
provider: "cursor",
|
|
5383
|
+
name: "Cursor Plugin Marketplace",
|
|
5384
|
+
description: "Validates .cursor-plugin/marketplace.json (string sources + metadata.pluginRoot)",
|
|
3690
5385
|
detect(dir) {
|
|
3691
|
-
|
|
3692
|
-
|
|
5386
|
+
if (existsSync32(resolve20(dir, ".cursor-plugin", "marketplace.json")))
|
|
5387
|
+
return true;
|
|
5388
|
+
const pluginsDir = resolve20(dir, "plugins");
|
|
5389
|
+
if (!existsSync32(pluginsDir))
|
|
3693
5390
|
return false;
|
|
3694
5391
|
try {
|
|
3695
|
-
const entries =
|
|
5392
|
+
const entries = readdirSync15(pluginsDir, { withFileTypes: true });
|
|
3696
5393
|
for (const entry of entries) {
|
|
3697
5394
|
if (!entry.isDirectory())
|
|
3698
5395
|
continue;
|
|
3699
|
-
const hasSkills =
|
|
3700
|
-
const hasManifest =
|
|
5396
|
+
const hasSkills = existsSync32(join23(pluginsDir, entry.name, "skills"));
|
|
5397
|
+
const hasManifest = existsSync32(join23(pluginsDir, entry.name, ".cursor-plugin", "plugin.json"));
|
|
3701
5398
|
if (hasSkills || hasManifest)
|
|
3702
5399
|
return true;
|
|
3703
5400
|
}
|
|
@@ -3708,195 +5405,171 @@ var init_marketplace = __esm(() => {
|
|
|
3708
5405
|
const errors = [];
|
|
3709
5406
|
const warnings = [];
|
|
3710
5407
|
const passes = [];
|
|
3711
|
-
const
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
const pluginEntries = readdirSync7(pluginsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
3718
|
-
if (pluginEntries.length === 0) {
|
|
3719
|
-
errors.push("plugins/ directory is empty \u2014 expected at least one plugin");
|
|
5408
|
+
const cursorMktPath = resolve20(dir, ".cursor-plugin", "marketplace.json");
|
|
5409
|
+
const hasCursorMkt = existsSync32(cursorMktPath);
|
|
5410
|
+
const pluginsDir = resolve20(dir, "plugins");
|
|
5411
|
+
const hasPluginsDirLayout = existsSync32(pluginsDir);
|
|
5412
|
+
if (!hasCursorMkt && !hasPluginsDirLayout) {
|
|
5413
|
+
errors.push("Missing .cursor-plugin/marketplace.json or plugins/ directory");
|
|
3720
5414
|
return { errors, warnings, passes };
|
|
3721
5415
|
}
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
warnings.push("No LICENSE at marketplace root \u2014 recommended");
|
|
3732
|
-
}
|
|
3733
|
-
for (const plugin of pluginEntries) {
|
|
3734
|
-
const pluginPath = join13(pluginsDir, plugin.name);
|
|
3735
|
-
const hasSkills = existsSync15(join13(pluginPath, "skills"));
|
|
3736
|
-
const hasManifest = existsSync15(join13(pluginPath, ".claude-plugin", "plugin.json"));
|
|
3737
|
-
const hasReadme = existsSync15(join13(pluginPath, "README.md"));
|
|
3738
|
-
if (hasManifest || hasSkills) {
|
|
3739
|
-
passes.push(`Plugin "${plugin.name}" has ${hasManifest ? "manifest" : "skills/"}`);
|
|
3740
|
-
} else {
|
|
3741
|
-
warnings.push(`Plugin "${plugin.name}" has neither .claude-plugin/plugin.json nor skills/`);
|
|
5416
|
+
if (hasCursorMkt) {
|
|
5417
|
+
let mkt;
|
|
5418
|
+
try {
|
|
5419
|
+
const raw = await Bun.file(cursorMktPath).text();
|
|
5420
|
+
mkt = JSON.parse(raw);
|
|
5421
|
+
passes.push(".cursor-plugin/marketplace.json is valid JSON");
|
|
5422
|
+
} catch {
|
|
5423
|
+
errors.push(".cursor-plugin/marketplace.json is missing or invalid JSON");
|
|
5424
|
+
return { errors, warnings, passes };
|
|
3742
5425
|
}
|
|
3743
|
-
if (
|
|
3744
|
-
|
|
5426
|
+
if (mkt.name) {
|
|
5427
|
+
passes.push(`name: "${mkt.name}"`);
|
|
5428
|
+
} else {
|
|
5429
|
+
warnings.push('Missing "name" at marketplace root');
|
|
3745
5430
|
}
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
}
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
import { resolve as resolve8 } from "path";
|
|
3755
|
-
var KNOWN_EVENTS, claudeHooksValidator;
|
|
3756
|
-
var init_hooks = __esm(() => {
|
|
3757
|
-
KNOWN_EVENTS = [
|
|
3758
|
-
"SessionStart",
|
|
3759
|
-
"Setup",
|
|
3760
|
-
"UserPromptSubmit",
|
|
3761
|
-
"UserPromptExpansion",
|
|
3762
|
-
"PreToolUse",
|
|
3763
|
-
"PermissionRequest",
|
|
3764
|
-
"PermissionDenied",
|
|
3765
|
-
"PostToolUse",
|
|
3766
|
-
"PostToolUseFailure",
|
|
3767
|
-
"PostToolBatch",
|
|
3768
|
-
"Notification",
|
|
3769
|
-
"MessageDisplay",
|
|
3770
|
-
"SubagentStart",
|
|
3771
|
-
"SubagentStop",
|
|
3772
|
-
"TaskCreated",
|
|
3773
|
-
"TaskCompleted",
|
|
3774
|
-
"Stop",
|
|
3775
|
-
"StopFailure",
|
|
3776
|
-
"TeammateIdle",
|
|
3777
|
-
"InstructionsLoaded",
|
|
3778
|
-
"ConfigChange",
|
|
3779
|
-
"CwdChanged",
|
|
3780
|
-
"FileChanged",
|
|
3781
|
-
"WorktreeCreate",
|
|
3782
|
-
"WorktreeRemove",
|
|
3783
|
-
"PreCompact",
|
|
3784
|
-
"PostCompact",
|
|
3785
|
-
"Elicitation",
|
|
3786
|
-
"ElicitationResult",
|
|
3787
|
-
"SessionEnd"
|
|
3788
|
-
];
|
|
3789
|
-
claudeHooksValidator = {
|
|
3790
|
-
id: "claude:hooks",
|
|
3791
|
-
provider: "claude",
|
|
3792
|
-
name: "Claude Hooks",
|
|
3793
|
-
description: "Validates hooks/hooks.json (or root hooks.json): all lifecycle events per Plugins reference, hook group structure (matcher + hooks[]), supported hook types (command, http, mcp_tool, prompt, agent)",
|
|
3794
|
-
detect(dir) {
|
|
3795
|
-
return existsSync16(resolve8(dir, "hooks", "hooks.json")) || existsSync16(resolve8(dir, "hooks.json"));
|
|
3796
|
-
},
|
|
3797
|
-
async validate(dir, _opts) {
|
|
3798
|
-
const errors = [];
|
|
3799
|
-
const warnings = [];
|
|
3800
|
-
const passes = [];
|
|
3801
|
-
const hooksPath = existsSync16(resolve8(dir, "hooks", "hooks.json")) ? resolve8(dir, "hooks", "hooks.json") : resolve8(dir, "hooks.json");
|
|
3802
|
-
let config;
|
|
3803
|
-
try {
|
|
3804
|
-
const raw = await Bun.file(hooksPath).text();
|
|
3805
|
-
config = JSON.parse(raw);
|
|
3806
|
-
passes.push("hooks.json is valid JSON");
|
|
3807
|
-
} catch {
|
|
3808
|
-
errors.push("hooks.json is missing or invalid JSON");
|
|
3809
|
-
return { errors, warnings, passes };
|
|
3810
|
-
}
|
|
3811
|
-
const eventNames = Object.keys(config);
|
|
3812
|
-
for (const name of eventNames) {
|
|
3813
|
-
if (KNOWN_EVENTS.includes(name)) {
|
|
3814
|
-
passes.push(`Event "${name}" is a known lifecycle event`);
|
|
5431
|
+
if (mkt.metadata && typeof mkt.metadata === "object") {
|
|
5432
|
+
passes.push("metadata present");
|
|
5433
|
+
if (mkt.metadata.pluginRoot) {
|
|
5434
|
+
passes.push(`metadata.pluginRoot: "${mkt.metadata.pluginRoot}"`);
|
|
5435
|
+
}
|
|
5436
|
+
if (mkt.metadata.description) {
|
|
5437
|
+
passes.push("metadata.description present");
|
|
5438
|
+
}
|
|
3815
5439
|
} else {
|
|
3816
|
-
warnings.push(
|
|
5440
|
+
warnings.push('Recommended: "metadata" with pluginRoot and description');
|
|
3817
5441
|
}
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
if (!Array.isArray(groups)) {
|
|
3821
|
-
errors.push(`Event "${event}": value must be an array of hook groups`);
|
|
3822
|
-
continue;
|
|
5442
|
+
if (mkt.owner) {
|
|
5443
|
+
passes.push("owner present");
|
|
3823
5444
|
}
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
5445
|
+
if (!Array.isArray(mkt.plugins) || mkt.plugins.length === 0) {
|
|
5446
|
+
errors.push('"plugins" must be a non-empty array');
|
|
5447
|
+
return { errors, warnings, passes };
|
|
5448
|
+
}
|
|
5449
|
+
passes.push(`${mkt.plugins.length} plugin(s) declared`);
|
|
5450
|
+
const pluginRoot = mkt.metadata && mkt.metadata.pluginRoot ? String(mkt.metadata.pluginRoot) : ".";
|
|
5451
|
+
for (const [i, p] of mkt.plugins.entries()) {
|
|
5452
|
+
if (!p || typeof p !== "object") {
|
|
5453
|
+
errors.push(`plugins[${i}]: must be an object`);
|
|
5454
|
+
continue;
|
|
3831
5455
|
}
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
5456
|
+
if (p.name) {
|
|
5457
|
+
passes.push(`plugins[${i}].name: "${p.name}"`);
|
|
5458
|
+
} else {
|
|
5459
|
+
errors.push(`plugins[${i}]: missing "name"`);
|
|
3836
5460
|
}
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
}
|
|
3852
|
-
|
|
3853
|
-
|
|
5461
|
+
if (p.source !== undefined) {
|
|
5462
|
+
const src = String(p.source);
|
|
5463
|
+
passes.push(`plugins[${i}].source: "${src}"`);
|
|
5464
|
+
const srcDir = resolve20(dir, pluginRoot, src);
|
|
5465
|
+
if (existsSync32(srcDir)) {
|
|
5466
|
+
const hasManifest = existsSync32(resolve20(srcDir, ".cursor-plugin", "plugin.json"));
|
|
5467
|
+
const hasSkills = existsSync32(resolve20(srcDir, "skills"));
|
|
5468
|
+
if (hasManifest || hasSkills) {
|
|
5469
|
+
passes.push(`plugins[${i}]: source exists (${hasManifest ? "manifest" : "skills/"})`);
|
|
5470
|
+
} else {
|
|
5471
|
+
warnings.push(`plugins[${i}].source "${src}" exists but lacks plugin markers`);
|
|
5472
|
+
}
|
|
5473
|
+
} else {
|
|
5474
|
+
warnings.push(`plugins[${i}].source path "${src}" (under ${pluginRoot}) does not exist`);
|
|
5475
|
+
}
|
|
5476
|
+
} else {
|
|
5477
|
+
const implicitSrc = resolve20(dir, pluginRoot, p.name || "");
|
|
5478
|
+
if (p.name && existsSync32(implicitSrc)) {
|
|
5479
|
+
passes.push(`plugins[${i}]: implicit source via name under ${pluginRoot}`);
|
|
5480
|
+
} else {
|
|
5481
|
+
warnings.push(`plugins[${i}]: missing "source" (and no implicit dir)`);
|
|
3854
5482
|
}
|
|
3855
|
-
});
|
|
3856
|
-
if (hooksArr.length > 0) {
|
|
3857
|
-
passes.push(`Event "${event}" has ${hooksArr.length} hook action(s)`);
|
|
3858
5483
|
}
|
|
3859
|
-
|
|
5484
|
+
if (p.description)
|
|
5485
|
+
passes.push(`plugins[${i}].description present`);
|
|
5486
|
+
if (p.category) {
|
|
5487
|
+
passes.push(`plugins[${i}].category: "${p.category}"`);
|
|
5488
|
+
}
|
|
5489
|
+
if (p.homepage) {
|
|
5490
|
+
passes.push(`plugins[${i}].homepage present`);
|
|
5491
|
+
}
|
|
5492
|
+
}
|
|
5493
|
+
if (existsSync32(resolve20(dir, "README.md"))) {
|
|
5494
|
+
passes.push("README.md exists at marketplace root");
|
|
5495
|
+
} else {
|
|
5496
|
+
warnings.push("No README.md at marketplace root \u2014 recommended for discoverability");
|
|
5497
|
+
}
|
|
5498
|
+
if (existsSync32(resolve20(dir, "LICENSE"))) {
|
|
5499
|
+
passes.push("LICENSE exists at marketplace root");
|
|
5500
|
+
} else {
|
|
5501
|
+
warnings.push("No LICENSE at marketplace root \u2014 recommended");
|
|
5502
|
+
}
|
|
5503
|
+
return { errors, warnings, passes };
|
|
5504
|
+
}
|
|
5505
|
+
if (hasPluginsDirLayout) {
|
|
5506
|
+
passes.push("plugins/ directory exists");
|
|
5507
|
+
const pluginEntries = readdirSync15(pluginsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
5508
|
+
if (pluginEntries.length === 0) {
|
|
5509
|
+
errors.push("plugins/ directory is empty \u2014 expected at least one plugin");
|
|
5510
|
+
return { errors, warnings, passes };
|
|
5511
|
+
}
|
|
5512
|
+
passes.push(`${pluginEntries.length} plugin(s) found`);
|
|
5513
|
+
if (existsSync32(resolve20(dir, "README.md"))) {
|
|
5514
|
+
passes.push("README.md exists at marketplace root");
|
|
5515
|
+
} else {
|
|
5516
|
+
warnings.push("No README.md at marketplace root \u2014 recommended");
|
|
5517
|
+
}
|
|
5518
|
+
for (const plugin of pluginEntries) {
|
|
5519
|
+
const pluginPath = join23(pluginsDir, plugin.name);
|
|
5520
|
+
const hasSkills = existsSync32(join23(pluginPath, "skills"));
|
|
5521
|
+
const hasManifest = existsSync32(join23(pluginPath, ".cursor-plugin", "plugin.json"));
|
|
5522
|
+
if (hasManifest || hasSkills) {
|
|
5523
|
+
passes.push(`Plugin "${plugin.name}" has ${hasManifest ? "manifest" : "skills/"}`);
|
|
5524
|
+
}
|
|
5525
|
+
}
|
|
5526
|
+
return { errors, warnings, passes };
|
|
3860
5527
|
}
|
|
3861
5528
|
return { errors, warnings, passes };
|
|
3862
5529
|
}
|
|
3863
5530
|
};
|
|
3864
5531
|
});
|
|
3865
5532
|
|
|
3866
|
-
// src/validators/
|
|
3867
|
-
import { existsSync as
|
|
3868
|
-
import { resolve as
|
|
3869
|
-
var
|
|
3870
|
-
var
|
|
3871
|
-
|
|
3872
|
-
id: "
|
|
3873
|
-
provider: "
|
|
3874
|
-
name: "
|
|
3875
|
-
description: "Validates
|
|
5533
|
+
// src/validators/cursor/mcp.ts
|
|
5534
|
+
import { existsSync as existsSync33 } from "fs";
|
|
5535
|
+
import { resolve as resolve21 } from "path";
|
|
5536
|
+
var cursorMcpValidator;
|
|
5537
|
+
var init_mcp3 = __esm(() => {
|
|
5538
|
+
cursorMcpValidator = {
|
|
5539
|
+
id: "cursor:mcp",
|
|
5540
|
+
provider: "cursor",
|
|
5541
|
+
name: "Cursor MCP Config",
|
|
5542
|
+
description: "Validates mcp.json (Cursor uses no leading dot; supports mcpServers wrapper or direct server map)",
|
|
3876
5543
|
detect(dir) {
|
|
3877
|
-
return
|
|
5544
|
+
return existsSync33(resolve21(dir, "mcp.json"));
|
|
3878
5545
|
},
|
|
3879
5546
|
async validate(dir, _opts) {
|
|
3880
5547
|
const errors = [];
|
|
3881
5548
|
const warnings = [];
|
|
3882
5549
|
const passes = [];
|
|
3883
|
-
const mcpPath =
|
|
3884
|
-
let
|
|
5550
|
+
const mcpPath = resolve21(dir, "mcp.json");
|
|
5551
|
+
let rawConfig;
|
|
3885
5552
|
try {
|
|
3886
5553
|
const raw = await Bun.file(mcpPath).text();
|
|
3887
|
-
|
|
3888
|
-
passes.push("
|
|
5554
|
+
rawConfig = JSON.parse(raw);
|
|
5555
|
+
passes.push("mcp.json is valid JSON");
|
|
3889
5556
|
} catch {
|
|
3890
|
-
errors.push("
|
|
5557
|
+
errors.push("mcp.json is missing or invalid JSON");
|
|
3891
5558
|
return { errors, warnings, passes };
|
|
3892
5559
|
}
|
|
3893
|
-
|
|
3894
|
-
|
|
5560
|
+
let config;
|
|
5561
|
+
if (rawConfig && typeof rawConfig === "object" && !Array.isArray(rawConfig) && rawConfig.mcpServers && typeof rawConfig.mcpServers === "object") {
|
|
5562
|
+
config = rawConfig.mcpServers;
|
|
5563
|
+
passes.push("mcp.json uses mcpServers wrapper (normalized)");
|
|
5564
|
+
} else if (typeof rawConfig === "object" && !Array.isArray(rawConfig)) {
|
|
5565
|
+
config = rawConfig;
|
|
5566
|
+
} else {
|
|
5567
|
+
errors.push("mcp.json must be an object (or contain mcpServers object)");
|
|
3895
5568
|
return { errors, warnings, passes };
|
|
3896
5569
|
}
|
|
3897
5570
|
const serverNames = Object.keys(config);
|
|
3898
5571
|
if (serverNames.length === 0) {
|
|
3899
|
-
warnings.push("
|
|
5572
|
+
warnings.push("mcp.json is empty \u2014 no servers defined");
|
|
3900
5573
|
return { errors, warnings, passes };
|
|
3901
5574
|
}
|
|
3902
5575
|
passes.push(`${serverNames.length} server(s) defined`);
|
|
@@ -3923,9 +5596,9 @@ var init_mcp = __esm(() => {
|
|
|
3923
5596
|
if (typeof e.cwd === "string") {
|
|
3924
5597
|
passes.push(`mcp server "${name}": has cwd`);
|
|
3925
5598
|
}
|
|
3926
|
-
const hasSubs = JSON.stringify(e).match(/\$\{CLAUDE_PLUGIN_
|
|
5599
|
+
const hasSubs = JSON.stringify(e).match(/\$\{CODEX_|CLAUDE_PLUGIN_|CURSOR_|user_config\.|ENV_VAR\}/);
|
|
3927
5600
|
if (hasSubs) {
|
|
3928
|
-
passes.push(`mcp server "${name}": uses
|
|
5601
|
+
passes.push(`mcp server "${name}": uses substitutions`);
|
|
3929
5602
|
}
|
|
3930
5603
|
}
|
|
3931
5604
|
return { errors, warnings, passes };
|
|
@@ -3933,365 +5606,468 @@ var init_mcp = __esm(() => {
|
|
|
3933
5606
|
};
|
|
3934
5607
|
});
|
|
3935
5608
|
|
|
3936
|
-
// src/validators/
|
|
3937
|
-
import { existsSync as
|
|
3938
|
-
import { resolve as
|
|
3939
|
-
var
|
|
3940
|
-
var
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
id: "
|
|
3944
|
-
provider: "
|
|
3945
|
-
name: "
|
|
3946
|
-
description: "Validates
|
|
5609
|
+
// src/validators/cursor/skill.ts
|
|
5610
|
+
import { existsSync as existsSync34 } from "fs";
|
|
5611
|
+
import { resolve as resolve22 } from "path";
|
|
5612
|
+
var cursorSkillValidator;
|
|
5613
|
+
var init_skill3 = __esm(() => {
|
|
5614
|
+
init_skill_validate();
|
|
5615
|
+
cursorSkillValidator = {
|
|
5616
|
+
id: "cursor:skill",
|
|
5617
|
+
provider: "cursor",
|
|
5618
|
+
name: "Cursor Skill",
|
|
5619
|
+
description: "Validates SKILL.md (shared format): frontmatter (name/description), body, supporting files, substitutions. Cursor uses the same SKILL.md spec as other providers.",
|
|
3947
5620
|
detect(dir) {
|
|
3948
|
-
|
|
3949
|
-
if (!existsSync18(agentsDir))
|
|
3950
|
-
return false;
|
|
3951
|
-
try {
|
|
3952
|
-
return readdirSync8(agentsDir).some((f) => f.endsWith(".md"));
|
|
3953
|
-
} catch {
|
|
3954
|
-
return false;
|
|
3955
|
-
}
|
|
5621
|
+
return existsSync34(resolve22(dir, "SKILL.md"));
|
|
3956
5622
|
},
|
|
3957
5623
|
async validate(dir, _opts) {
|
|
3958
|
-
const
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
return { errors, warnings, passes };
|
|
3966
|
-
}
|
|
3967
|
-
passes.push(`${mdFiles.length} agent definition(s) found`);
|
|
3968
|
-
const SUPPORTED = new Set([
|
|
3969
|
-
"name",
|
|
3970
|
-
"description",
|
|
3971
|
-
"model",
|
|
3972
|
-
"effort",
|
|
3973
|
-
"maxTurns",
|
|
3974
|
-
"tools",
|
|
3975
|
-
"disallowedTools",
|
|
3976
|
-
"skills",
|
|
3977
|
-
"memory",
|
|
3978
|
-
"background",
|
|
3979
|
-
"isolation"
|
|
3980
|
-
]);
|
|
3981
|
-
const DISALLOWED = new Set(["hooks", "mcpServers", "permissionMode"]);
|
|
3982
|
-
for (const file of mdFiles) {
|
|
3983
|
-
const filePath = join14(agentsDir, file);
|
|
3984
|
-
const raw = await Bun.file(filePath).text();
|
|
3985
|
-
try {
|
|
3986
|
-
const parsed = parseFrontmatter(raw);
|
|
3987
|
-
const fm = parsed.data;
|
|
3988
|
-
if (Object.keys(fm).length === 0) {
|
|
3989
|
-
warnings.push(`${file}: no YAML frontmatter (description recommended so Claude knows when to invoke)`);
|
|
3990
|
-
} else {
|
|
3991
|
-
if (fm.description) {
|
|
3992
|
-
passes.push(`${file}: has frontmatter with description`);
|
|
3993
|
-
} else {
|
|
3994
|
-
warnings.push(`${file}: missing "description" in frontmatter`);
|
|
3995
|
-
}
|
|
3996
|
-
const usedSupported = [];
|
|
3997
|
-
Object.keys(fm).forEach((k) => {
|
|
3998
|
-
if (SUPPORTED.has(k))
|
|
3999
|
-
usedSupported.push(k);
|
|
4000
|
-
if (DISALLOWED.has(k)) {
|
|
4001
|
-
errors.push(`${file}: frontmatter "${k}" is not supported for plugin-shipped agents (security restriction)`);
|
|
4002
|
-
}
|
|
4003
|
-
});
|
|
4004
|
-
if (usedSupported.length) {
|
|
4005
|
-
passes.push(`${file}: frontmatter fields: ${usedSupported.join(", ")}`);
|
|
4006
|
-
}
|
|
4007
|
-
if (fm.isolation !== undefined && fm.isolation !== "worktree") {
|
|
4008
|
-
errors.push(`${file}: "isolation" must be "worktree" if present (only supported value for plugin agents)`);
|
|
4009
|
-
}
|
|
4010
|
-
if (fm.name && typeof fm.name === "string") {
|
|
4011
|
-
passes.push(`${file}: name: "${fm.name}"`);
|
|
4012
|
-
}
|
|
4013
|
-
}
|
|
4014
|
-
if (!parsed.content.trim()) {
|
|
4015
|
-
errors.push(`${file}: body is empty`);
|
|
4016
|
-
} else {
|
|
4017
|
-
passes.push(`${file}: has agent system prompt body`);
|
|
4018
|
-
}
|
|
4019
|
-
} catch {
|
|
4020
|
-
errors.push(`${file}: failed to parse`);
|
|
4021
|
-
}
|
|
5624
|
+
const loaded = await loadSkill(dir);
|
|
5625
|
+
if (!loaded.ok) {
|
|
5626
|
+
return {
|
|
5627
|
+
errors: [loaded.error],
|
|
5628
|
+
warnings: [],
|
|
5629
|
+
passes: []
|
|
5630
|
+
};
|
|
4022
5631
|
}
|
|
4023
|
-
|
|
5632
|
+
const { model, existingDirs } = loaded;
|
|
5633
|
+
return validateSkillModel(model, { existingDirs: [...existingDirs] });
|
|
4024
5634
|
}
|
|
4025
5635
|
};
|
|
4026
5636
|
});
|
|
4027
5637
|
|
|
4028
|
-
// src/validators/
|
|
4029
|
-
import { existsSync as
|
|
4030
|
-
import { resolve as
|
|
4031
|
-
var
|
|
4032
|
-
var
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
id: "
|
|
4036
|
-
provider: "
|
|
4037
|
-
name: "
|
|
4038
|
-
description: "Validates
|
|
5638
|
+
// src/validators/copilot/plugin.ts
|
|
5639
|
+
import { existsSync as existsSync35 } from "fs";
|
|
5640
|
+
import { resolve as resolve23 } from "path";
|
|
5641
|
+
var NAME_REGEX5, copilotPluginValidator;
|
|
5642
|
+
var init_plugin4 = __esm(() => {
|
|
5643
|
+
NAME_REGEX5 = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
5644
|
+
copilotPluginValidator = {
|
|
5645
|
+
id: "copilot:plugin",
|
|
5646
|
+
provider: "copilot",
|
|
5647
|
+
name: "Copilot Plugin",
|
|
5648
|
+
description: "Validates .github/plugin/plugin.json (skills as array of paths, mcpServers support)",
|
|
4039
5649
|
detect(dir) {
|
|
4040
|
-
|
|
4041
|
-
if (!existsSync19(commandsDir))
|
|
4042
|
-
return false;
|
|
4043
|
-
try {
|
|
4044
|
-
return readdirSync9(commandsDir).some((f) => f.endsWith(".md"));
|
|
4045
|
-
} catch {
|
|
4046
|
-
return false;
|
|
4047
|
-
}
|
|
5650
|
+
return existsSync35(resolve23(dir, ".github", "plugin", "plugin.json"));
|
|
4048
5651
|
},
|
|
4049
5652
|
async validate(dir, _opts) {
|
|
4050
5653
|
const errors = [];
|
|
4051
5654
|
const warnings = [];
|
|
4052
5655
|
const passes = [];
|
|
4053
|
-
const
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
5656
|
+
const manifestPath = resolve23(dir, ".github", "plugin", "plugin.json");
|
|
5657
|
+
let manifest;
|
|
5658
|
+
try {
|
|
5659
|
+
const raw = await Bun.file(manifestPath).text();
|
|
5660
|
+
manifest = JSON.parse(raw);
|
|
5661
|
+
passes.push(".github/plugin/plugin.json is valid JSON");
|
|
5662
|
+
} catch {
|
|
5663
|
+
errors.push(".github/plugin/plugin.json is missing or invalid JSON");
|
|
4057
5664
|
return { errors, warnings, passes };
|
|
4058
5665
|
}
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
const
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
5666
|
+
if (!manifest.name) {
|
|
5667
|
+
errors.push('Missing required field: "name"');
|
|
5668
|
+
} else {
|
|
5669
|
+
const name = String(manifest.name);
|
|
5670
|
+
if (!NAME_REGEX5.test(name)) {
|
|
5671
|
+
errors.push(`Invalid name format: "${name}" \u2014 must be kebab-case (a-z, 0-9, hyphens)`);
|
|
5672
|
+
} else {
|
|
5673
|
+
passes.push(`name: "${name}"`);
|
|
5674
|
+
}
|
|
5675
|
+
}
|
|
5676
|
+
if (manifest.skills === undefined) {
|
|
5677
|
+
errors.push('Missing required field: "skills" (must be an array of paths like ["./skills/foo"])');
|
|
5678
|
+
} else if (!Array.isArray(manifest.skills)) {
|
|
5679
|
+
errors.push('"skills" must be an array of relative paths');
|
|
5680
|
+
} else {
|
|
5681
|
+
const skillsArr = manifest.skills;
|
|
5682
|
+
passes.push(`skills: array with ${skillsArr.length} path(s)`);
|
|
5683
|
+
for (const [i, p] of skillsArr.entries()) {
|
|
5684
|
+
if (typeof p !== "string") {
|
|
5685
|
+
errors.push(`skills[${i}]: must be a string path`);
|
|
5686
|
+
continue;
|
|
4071
5687
|
}
|
|
4072
|
-
if (!
|
|
4073
|
-
|
|
5688
|
+
if (!p.startsWith("./") && !p.startsWith("../")) {
|
|
5689
|
+
warnings.push(`skills[${i}]: "${p}" should be relative (./ or ../)`);
|
|
4074
5690
|
}
|
|
4075
|
-
const
|
|
4076
|
-
const
|
|
4077
|
-
if (
|
|
4078
|
-
passes.push(
|
|
5691
|
+
const skillDir = resolve23(dir, p);
|
|
5692
|
+
const skillMd = resolve23(skillDir, "SKILL.md");
|
|
5693
|
+
if (existsSync35(skillMd)) {
|
|
5694
|
+
passes.push(`skills[${i}]: ${p}/SKILL.md exists`);
|
|
5695
|
+
} else if (existsSync35(skillDir)) {
|
|
5696
|
+
warnings.push(`skills[${i}]: directory exists but no SKILL.md inside`);
|
|
5697
|
+
} else {
|
|
5698
|
+
warnings.push(`skills[${i}]: path "${p}" does not exist`);
|
|
4079
5699
|
}
|
|
4080
|
-
} catch {
|
|
4081
|
-
errors.push(`${file}: failed to parse`);
|
|
4082
5700
|
}
|
|
4083
5701
|
}
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
name: "Claude CLAUDE.md",
|
|
4098
|
-
description: "Validates CLAUDE.md: non-empty, length recommendations, @path imports",
|
|
4099
|
-
detect(dir) {
|
|
4100
|
-
return existsSync20(resolve12(dir, "CLAUDE.md"));
|
|
4101
|
-
},
|
|
4102
|
-
async validate(dir, _opts) {
|
|
4103
|
-
const errors = [];
|
|
4104
|
-
const warnings = [];
|
|
4105
|
-
const passes = [];
|
|
4106
|
-
const filePath = resolve12(dir, "CLAUDE.md");
|
|
4107
|
-
const raw = await Bun.file(filePath).text();
|
|
4108
|
-
if (!raw.trim()) {
|
|
4109
|
-
errors.push("CLAUDE.md is empty");
|
|
4110
|
-
return { errors, warnings, passes };
|
|
4111
|
-
}
|
|
4112
|
-
passes.push("CLAUDE.md is non-empty");
|
|
4113
|
-
const lines = raw.split(`
|
|
4114
|
-
`);
|
|
4115
|
-
if (lines.length > 200) {
|
|
4116
|
-
warnings.push(`CLAUDE.md is ${lines.length} lines \u2014 official recommendation is under 200. Move reference content to skills.`);
|
|
4117
|
-
} else {
|
|
4118
|
-
passes.push(`CLAUDE.md is ${lines.length} lines (under 200 recommended limit)`);
|
|
5702
|
+
if (manifest.mcpServers !== undefined) {
|
|
5703
|
+
if (typeof manifest.mcpServers === "string") {
|
|
5704
|
+
passes.push(`mcpServers: "${manifest.mcpServers}"`);
|
|
5705
|
+
const mcpRef = String(manifest.mcpServers);
|
|
5706
|
+
const mcpPath = resolve23(dir, mcpRef);
|
|
5707
|
+
if (existsSync35(mcpPath)) {
|
|
5708
|
+
passes.push(`mcpServers file exists at ${mcpRef}`);
|
|
5709
|
+
} else {
|
|
5710
|
+
warnings.push(`mcpServers path "${mcpRef}" does not exist on disk`);
|
|
5711
|
+
}
|
|
5712
|
+
} else {
|
|
5713
|
+
warnings.push('"mcpServers" should be a string path when present');
|
|
5714
|
+
}
|
|
4119
5715
|
}
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
const resolvedImport = resolve12(dir, importPath);
|
|
4125
|
-
if (existsSync20(resolvedImport)) {
|
|
4126
|
-
passes.push(`@import "${importPath}" exists`);
|
|
5716
|
+
if (manifest.description) {
|
|
5717
|
+
const desc = String(manifest.description);
|
|
5718
|
+
if (desc.length < 10) {
|
|
5719
|
+
warnings.push(`Description is very short (${desc.length} chars)`);
|
|
4127
5720
|
} else {
|
|
4128
|
-
|
|
5721
|
+
passes.push("description field present");
|
|
4129
5722
|
}
|
|
5723
|
+
} else {
|
|
5724
|
+
warnings.push('Missing "description" (recommended)');
|
|
5725
|
+
}
|
|
5726
|
+
if (manifest.version)
|
|
5727
|
+
passes.push(`version: "${manifest.version}"`);
|
|
5728
|
+
if (manifest.author)
|
|
5729
|
+
passes.push("author present");
|
|
5730
|
+
if (manifest.license)
|
|
5731
|
+
passes.push(`license: "${manifest.license}"`);
|
|
5732
|
+
if (manifest.homepage)
|
|
5733
|
+
passes.push("homepage present");
|
|
5734
|
+
if (manifest.repository)
|
|
5735
|
+
passes.push("repository present");
|
|
5736
|
+
if (Array.isArray(manifest.keywords)) {
|
|
5737
|
+
passes.push(`keywords: [${manifest.keywords.join(", ")}]`);
|
|
5738
|
+
}
|
|
5739
|
+
const known = new Set([
|
|
5740
|
+
"name",
|
|
5741
|
+
"version",
|
|
5742
|
+
"description",
|
|
5743
|
+
"author",
|
|
5744
|
+
"homepage",
|
|
5745
|
+
"repository",
|
|
5746
|
+
"license",
|
|
5747
|
+
"keywords",
|
|
5748
|
+
"skills",
|
|
5749
|
+
"mcpServers"
|
|
5750
|
+
]);
|
|
5751
|
+
const unknown = Object.keys(manifest).filter((k) => !known.has(k));
|
|
5752
|
+
for (const k of unknown) {
|
|
5753
|
+
warnings.push(`Unrecognized field "${k}" \u2014 will be ignored (for compatibility)`);
|
|
4130
5754
|
}
|
|
4131
5755
|
return { errors, warnings, passes };
|
|
4132
5756
|
}
|
|
4133
5757
|
};
|
|
4134
5758
|
});
|
|
4135
5759
|
|
|
4136
|
-
// src/validators/
|
|
4137
|
-
import { existsSync as
|
|
4138
|
-
import { resolve as
|
|
4139
|
-
var
|
|
4140
|
-
var
|
|
4141
|
-
|
|
4142
|
-
id: "
|
|
4143
|
-
provider: "
|
|
4144
|
-
name: "
|
|
4145
|
-
description: "Validates .
|
|
5760
|
+
// src/validators/copilot/marketplace.ts
|
|
5761
|
+
import { existsSync as existsSync36 } from "fs";
|
|
5762
|
+
import { resolve as resolve24 } from "path";
|
|
5763
|
+
var copilotMarketplaceValidator;
|
|
5764
|
+
var init_marketplace4 = __esm(() => {
|
|
5765
|
+
copilotMarketplaceValidator = {
|
|
5766
|
+
id: "copilot:marketplace",
|
|
5767
|
+
provider: "copilot",
|
|
5768
|
+
name: "Copilot Plugin Marketplace",
|
|
5769
|
+
description: "Validates .github/plugin/marketplace.json (string sources)",
|
|
4146
5770
|
detect(dir) {
|
|
4147
|
-
return
|
|
5771
|
+
return existsSync36(resolve24(dir, ".github", "plugin", "marketplace.json"));
|
|
4148
5772
|
},
|
|
4149
5773
|
async validate(dir, _opts) {
|
|
4150
5774
|
const errors = [];
|
|
4151
5775
|
const warnings = [];
|
|
4152
5776
|
const passes = [];
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
5777
|
+
const mktPath = resolve24(dir, ".github", "plugin", "marketplace.json");
|
|
5778
|
+
let mkt;
|
|
5779
|
+
try {
|
|
5780
|
+
const raw = await Bun.file(mktPath).text();
|
|
5781
|
+
mkt = JSON.parse(raw);
|
|
5782
|
+
passes.push(".github/plugin/marketplace.json is valid JSON");
|
|
5783
|
+
} catch {
|
|
5784
|
+
errors.push(".github/plugin/marketplace.json is missing or invalid JSON");
|
|
5785
|
+
return { errors, warnings, passes };
|
|
5786
|
+
}
|
|
5787
|
+
if (mkt.name) {
|
|
5788
|
+
passes.push(`name: "${mkt.name}"`);
|
|
4163
5789
|
} else {
|
|
4164
|
-
|
|
4165
|
-
if (existsSync21(manifestPath)) {
|
|
4166
|
-
try {
|
|
4167
|
-
const m = JSON.parse(await Bun.file(manifestPath).text());
|
|
4168
|
-
if (m && m.lspServers && typeof m.lspServers === "object") {
|
|
4169
|
-
cfg = m.lspServers;
|
|
4170
|
-
passes.push("lspServers present inline in plugin.json");
|
|
4171
|
-
}
|
|
4172
|
-
} catch {}
|
|
4173
|
-
}
|
|
5790
|
+
warnings.push('Missing "name" at marketplace root');
|
|
4174
5791
|
}
|
|
4175
|
-
if (
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
5792
|
+
if (mkt.metadata && typeof mkt.metadata === "object") {
|
|
5793
|
+
passes.push("metadata present");
|
|
5794
|
+
if (mkt.metadata.description)
|
|
5795
|
+
passes.push("metadata.description present");
|
|
5796
|
+
if (mkt.metadata.version)
|
|
5797
|
+
passes.push(`metadata.version: "${mkt.metadata.version}"`);
|
|
4179
5798
|
}
|
|
4180
|
-
if (
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
5799
|
+
if (mkt.owner) {
|
|
5800
|
+
passes.push("owner present");
|
|
5801
|
+
}
|
|
5802
|
+
if (!Array.isArray(mkt.plugins) || mkt.plugins.length === 0) {
|
|
5803
|
+
errors.push('"plugins" must be a non-empty array');
|
|
5804
|
+
return { errors, warnings, passes };
|
|
5805
|
+
}
|
|
5806
|
+
passes.push(`${mkt.plugins.length} plugin(s) declared`);
|
|
5807
|
+
for (const [i, p] of mkt.plugins.entries()) {
|
|
5808
|
+
if (!p || typeof p !== "object") {
|
|
5809
|
+
errors.push(`plugins[${i}]: must be an object`);
|
|
5810
|
+
continue;
|
|
5811
|
+
}
|
|
5812
|
+
if (p.name) {
|
|
5813
|
+
passes.push(`plugins[${i}].name: "${p.name}"`);
|
|
5814
|
+
} else {
|
|
5815
|
+
errors.push(`plugins[${i}]: missing "name"`);
|
|
5816
|
+
}
|
|
5817
|
+
if (p.source) {
|
|
5818
|
+
const src = String(p.source);
|
|
5819
|
+
passes.push(`plugins[${i}].source: "${src}"`);
|
|
5820
|
+
const srcDir = resolve24(dir, src);
|
|
5821
|
+
if (existsSync36(srcDir)) {
|
|
5822
|
+
const hasManifest = existsSync36(resolve24(srcDir, ".github", "plugin", "plugin.json"));
|
|
5823
|
+
const hasSkills = existsSync36(resolve24(srcDir, "skills"));
|
|
5824
|
+
if (hasManifest || hasSkills) {
|
|
5825
|
+
passes.push(`plugins[${i}]: source exists (${hasManifest ? "manifest" : "skills/"})`);
|
|
5826
|
+
} else {
|
|
5827
|
+
warnings.push(`plugins[${i}].source "${src}" exists but lacks plugin markers`);
|
|
5828
|
+
}
|
|
4190
5829
|
} else {
|
|
4191
|
-
|
|
4192
|
-
}
|
|
4193
|
-
if (entry.diagnostics === false) {
|
|
4194
|
-
passes.push(`lsp "${lang}": diagnostics disabled (navigation only)`);
|
|
5830
|
+
warnings.push(`plugins[${i}].source path "${src}" does not exist`);
|
|
4195
5831
|
}
|
|
5832
|
+
} else {
|
|
5833
|
+
warnings.push(`plugins[${i}]: missing "source"`);
|
|
4196
5834
|
}
|
|
5835
|
+
if (p.description)
|
|
5836
|
+
passes.push(`plugins[${i}].description present`);
|
|
5837
|
+
if (p.version)
|
|
5838
|
+
passes.push(`plugins[${i}].version: "${p.version}"`);
|
|
5839
|
+
}
|
|
5840
|
+
if (existsSync36(resolve24(dir, "README.md"))) {
|
|
5841
|
+
passes.push("README.md exists at marketplace root");
|
|
5842
|
+
} else {
|
|
5843
|
+
warnings.push("No README.md at marketplace root \u2014 recommended");
|
|
5844
|
+
}
|
|
5845
|
+
if (existsSync36(resolve24(dir, "LICENSE"))) {
|
|
5846
|
+
passes.push("LICENSE exists at marketplace root");
|
|
5847
|
+
} else {
|
|
5848
|
+
warnings.push("No LICENSE at marketplace root \u2014 recommended");
|
|
4197
5849
|
}
|
|
4198
|
-
warnings.push('Reminder: the actual language server binary (gopls, pyright, etc.) must be installed separately on PATH. See /plugin errors tab if "Executable not found".');
|
|
4199
5850
|
return { errors, warnings, passes };
|
|
4200
5851
|
}
|
|
4201
5852
|
};
|
|
4202
5853
|
});
|
|
4203
5854
|
|
|
4204
|
-
// src/validators/
|
|
4205
|
-
import { existsSync as
|
|
4206
|
-
import { resolve as
|
|
4207
|
-
var
|
|
4208
|
-
var
|
|
4209
|
-
|
|
4210
|
-
id: "
|
|
4211
|
-
provider: "
|
|
4212
|
-
name: "
|
|
4213
|
-
description: "Validates
|
|
5855
|
+
// src/validators/copilot/mcp.ts
|
|
5856
|
+
import { existsSync as existsSync37 } from "fs";
|
|
5857
|
+
import { resolve as resolve25 } from "path";
|
|
5858
|
+
var copilotMcpValidator;
|
|
5859
|
+
var init_mcp4 = __esm(() => {
|
|
5860
|
+
copilotMcpValidator = {
|
|
5861
|
+
id: "copilot:mcp",
|
|
5862
|
+
provider: "copilot",
|
|
5863
|
+
name: "Copilot MCP Config",
|
|
5864
|
+
description: "Validates .mcp.json (referenced via mcpServers in manifest). Supports stdio and http servers.",
|
|
4214
5865
|
detect(dir) {
|
|
4215
|
-
return
|
|
5866
|
+
return existsSync37(resolve25(dir, ".mcp.json"));
|
|
4216
5867
|
},
|
|
4217
5868
|
async validate(dir, _opts) {
|
|
4218
5869
|
const errors = [];
|
|
4219
5870
|
const warnings = [];
|
|
4220
5871
|
const passes = [];
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
if (Array.isArray(parsed)) {
|
|
4231
|
-
arr = parsed;
|
|
4232
|
-
passes.push("monitors config is valid JSON array");
|
|
4233
|
-
}
|
|
4234
|
-
break;
|
|
4235
|
-
} catch {
|
|
4236
|
-
errors.push("monitors config is invalid JSON");
|
|
4237
|
-
return { errors, warnings, passes };
|
|
4238
|
-
}
|
|
4239
|
-
}
|
|
4240
|
-
}
|
|
4241
|
-
if (!arr) {
|
|
4242
|
-
const mp = resolve14(dir, ".claude-plugin", "plugin.json");
|
|
4243
|
-
if (existsSync22(mp)) {
|
|
4244
|
-
try {
|
|
4245
|
-
const m = JSON.parse(await Bun.file(mp).text());
|
|
4246
|
-
const exp = m?.experimental;
|
|
4247
|
-
const inline = typeof exp === "string" ? null : exp?.monitors;
|
|
4248
|
-
if (Array.isArray(inline))
|
|
4249
|
-
arr = inline;
|
|
4250
|
-
else if (typeof inline === "string") {
|
|
4251
|
-
passes.push("experimental.monitors declared as path in manifest (content not validated here)");
|
|
4252
|
-
}
|
|
4253
|
-
} catch {}
|
|
4254
|
-
}
|
|
5872
|
+
const mcpPath = resolve25(dir, ".mcp.json");
|
|
5873
|
+
let rawConfig;
|
|
5874
|
+
try {
|
|
5875
|
+
const raw = await Bun.file(mcpPath).text();
|
|
5876
|
+
rawConfig = JSON.parse(raw);
|
|
5877
|
+
passes.push(".mcp.json is valid JSON");
|
|
5878
|
+
} catch {
|
|
5879
|
+
errors.push(".mcp.json is missing or invalid JSON");
|
|
5880
|
+
return { errors, warnings, passes };
|
|
4255
5881
|
}
|
|
4256
|
-
|
|
5882
|
+
let config;
|
|
5883
|
+
if (rawConfig && typeof rawConfig === "object" && !Array.isArray(rawConfig) && rawConfig.mcpServers && typeof rawConfig.mcpServers === "object") {
|
|
5884
|
+
config = rawConfig.mcpServers;
|
|
5885
|
+
passes.push("mcp.json uses mcpServers wrapper (normalized)");
|
|
5886
|
+
} else if (typeof rawConfig === "object" && !Array.isArray(rawConfig)) {
|
|
5887
|
+
config = rawConfig;
|
|
5888
|
+
} else {
|
|
5889
|
+
errors.push(".mcp.json must be an object (or contain mcpServers object)");
|
|
4257
5890
|
return { errors, warnings, passes };
|
|
4258
5891
|
}
|
|
4259
|
-
|
|
4260
|
-
|
|
5892
|
+
const serverNames = Object.keys(config);
|
|
5893
|
+
if (serverNames.length === 0) {
|
|
5894
|
+
warnings.push(".mcp.json is empty \u2014 no servers defined");
|
|
4261
5895
|
return { errors, warnings, passes };
|
|
4262
5896
|
}
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
if (!
|
|
4266
|
-
errors.push(`
|
|
4267
|
-
|
|
5897
|
+
passes.push(`${serverNames.length} server(s) defined`);
|
|
5898
|
+
for (const [name, entry] of Object.entries(config)) {
|
|
5899
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
5900
|
+
errors.push(`mcp server "${name}": definition must be an object`);
|
|
5901
|
+
continue;
|
|
4268
5902
|
}
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
seen.add(mon.name);
|
|
5903
|
+
const e = entry;
|
|
5904
|
+
const hasCommand = typeof e.command === "string";
|
|
5905
|
+
const hasUrl = typeof e.url === "string";
|
|
5906
|
+
if (!hasCommand && !hasUrl) {
|
|
5907
|
+
errors.push(`mcp server "${name}": must have either "command" (for stdio) or "url" (for SSE/HTTP)`);
|
|
4275
5908
|
}
|
|
4276
|
-
if (
|
|
4277
|
-
|
|
4278
|
-
} else if (/\$\{CLAUDE_/.test(mon.command)) {
|
|
4279
|
-
passes.push(`monitors[${i}] "${mon.name || i}": uses CLAUDE_PLUGIN_* substitution`);
|
|
5909
|
+
if (hasCommand && !Array.isArray(e.args)) {
|
|
5910
|
+
warnings.push(`mcp server "${name}": "command" present but no "args" array (ok for some servers)`);
|
|
4280
5911
|
}
|
|
4281
|
-
if (
|
|
4282
|
-
warnings.push(`
|
|
5912
|
+
if (hasUrl && hasCommand) {
|
|
5913
|
+
warnings.push(`mcp server "${name}": both "command" and "url" present \u2014 usually one or the other`);
|
|
4283
5914
|
}
|
|
4284
|
-
if (
|
|
4285
|
-
|
|
5915
|
+
if (e.env && typeof e.env === "object") {
|
|
5916
|
+
passes.push(`mcp server "${name}": has env`);
|
|
4286
5917
|
}
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
5918
|
+
if (typeof e.cwd === "string") {
|
|
5919
|
+
passes.push(`mcp server "${name}": has cwd`);
|
|
5920
|
+
}
|
|
5921
|
+
const hasSubs = JSON.stringify(e).match(/\$\{CODEX_|CLAUDE_PLUGIN_|COPILOT_|PLUGIN_ROOT|user_config\.|ENV_VAR\}/);
|
|
5922
|
+
if (hasSubs) {
|
|
5923
|
+
passes.push(`mcp server "${name}": uses substitutions (e.g. \${PLUGIN_ROOT} or env)`);
|
|
5924
|
+
}
|
|
5925
|
+
}
|
|
4290
5926
|
return { errors, warnings, passes };
|
|
4291
5927
|
}
|
|
4292
5928
|
};
|
|
4293
5929
|
});
|
|
4294
5930
|
|
|
5931
|
+
// src/validators/copilot/skill.ts
|
|
5932
|
+
import { existsSync as existsSync38 } from "fs";
|
|
5933
|
+
import { resolve as resolve26 } from "path";
|
|
5934
|
+
var copilotSkillValidator;
|
|
5935
|
+
var init_skill4 = __esm(() => {
|
|
5936
|
+
init_skill_validate();
|
|
5937
|
+
copilotSkillValidator = {
|
|
5938
|
+
id: "copilot:skill",
|
|
5939
|
+
provider: "copilot",
|
|
5940
|
+
name: "Copilot Skill",
|
|
5941
|
+
description: "Validates SKILL.md (shared format). Copilot supports skills referenced via array paths in the manifest.",
|
|
5942
|
+
detect(dir) {
|
|
5943
|
+
return existsSync38(resolve26(dir, "SKILL.md"));
|
|
5944
|
+
},
|
|
5945
|
+
async validate(dir, _opts) {
|
|
5946
|
+
const loaded = await loadSkill(dir);
|
|
5947
|
+
if (!loaded.ok) {
|
|
5948
|
+
return {
|
|
5949
|
+
errors: [loaded.error],
|
|
5950
|
+
warnings: [],
|
|
5951
|
+
passes: []
|
|
5952
|
+
};
|
|
5953
|
+
}
|
|
5954
|
+
const { model, existingDirs } = loaded;
|
|
5955
|
+
return validateSkillModel(model, { existingDirs: [...existingDirs] });
|
|
5956
|
+
}
|
|
5957
|
+
};
|
|
5958
|
+
});
|
|
5959
|
+
|
|
5960
|
+
// src/providers/index.ts
|
|
5961
|
+
var claudeAdapter, codexAdapter, cursorAdapter, copilotAdapter, adapters;
|
|
5962
|
+
var init_providers = __esm(() => {
|
|
5963
|
+
init_skill();
|
|
5964
|
+
init_plugin();
|
|
5965
|
+
init_marketplace();
|
|
5966
|
+
init_hooks();
|
|
5967
|
+
init_mcp();
|
|
5968
|
+
init_subagent();
|
|
5969
|
+
init_command();
|
|
5970
|
+
init_memory();
|
|
5971
|
+
init_lsp();
|
|
5972
|
+
init_monitors();
|
|
5973
|
+
init_plugin2();
|
|
5974
|
+
init_marketplace2();
|
|
5975
|
+
init_mcp2();
|
|
5976
|
+
init_skill2();
|
|
5977
|
+
init_plugin3();
|
|
5978
|
+
init_marketplace3();
|
|
5979
|
+
init_mcp3();
|
|
5980
|
+
init_skill3();
|
|
5981
|
+
init_plugin4();
|
|
5982
|
+
init_marketplace4();
|
|
5983
|
+
init_mcp4();
|
|
5984
|
+
init_skill4();
|
|
5985
|
+
init_spec();
|
|
5986
|
+
claudeAdapter = {
|
|
5987
|
+
id: "claude",
|
|
5988
|
+
name: "Claude Code",
|
|
5989
|
+
manifestPath: ".claude-plugin/plugin.json",
|
|
5990
|
+
marketplacePath: ".claude-plugin/marketplace.json",
|
|
5991
|
+
mcpFilename: ".mcp.json",
|
|
5992
|
+
validators: [
|
|
5993
|
+
claudeSkillValidator,
|
|
5994
|
+
claudePluginValidator,
|
|
5995
|
+
claudeMarketplaceValidator,
|
|
5996
|
+
claudeHooksValidator,
|
|
5997
|
+
claudeMcpValidator,
|
|
5998
|
+
claudeSubagentValidator,
|
|
5999
|
+
claudeCommandValidator,
|
|
6000
|
+
claudeMemoryValidator,
|
|
6001
|
+
claudeLspValidator,
|
|
6002
|
+
claudeMonitorsValidator
|
|
6003
|
+
],
|
|
6004
|
+
detectContext(dir) {
|
|
6005
|
+
return { cwd: dir };
|
|
6006
|
+
},
|
|
6007
|
+
async scaffold(decision, ctx) {
|
|
6008
|
+
throw new Error("Scaffold via adapter not yet implemented");
|
|
6009
|
+
}
|
|
6010
|
+
};
|
|
6011
|
+
codexAdapter = {
|
|
6012
|
+
id: "codex",
|
|
6013
|
+
name: "Codex",
|
|
6014
|
+
manifestPath: ".codex-plugin/plugin.json",
|
|
6015
|
+
marketplacePath: ".agents/plugins/marketplace.json",
|
|
6016
|
+
mcpFilename: ".mcp.json",
|
|
6017
|
+
validators: [
|
|
6018
|
+
codexPluginValidator,
|
|
6019
|
+
codexMarketplaceValidator,
|
|
6020
|
+
codexMcpValidator,
|
|
6021
|
+
codexSkillValidator
|
|
6022
|
+
],
|
|
6023
|
+
detectContext(dir) {
|
|
6024
|
+
return { cwd: dir };
|
|
6025
|
+
},
|
|
6026
|
+
async scaffold(decision, ctx) {
|
|
6027
|
+
throw new Error("Scaffold via adapter not yet implemented");
|
|
6028
|
+
}
|
|
6029
|
+
};
|
|
6030
|
+
cursorAdapter = {
|
|
6031
|
+
id: "cursor",
|
|
6032
|
+
name: "Cursor",
|
|
6033
|
+
manifestPath: ".cursor-plugin/plugin.json",
|
|
6034
|
+
marketplacePath: ".cursor-plugin/marketplace.json",
|
|
6035
|
+
mcpFilename: "mcp.json",
|
|
6036
|
+
validators: [
|
|
6037
|
+
cursorPluginValidator,
|
|
6038
|
+
cursorMarketplaceValidator,
|
|
6039
|
+
cursorMcpValidator,
|
|
6040
|
+
cursorSkillValidator
|
|
6041
|
+
],
|
|
6042
|
+
detectContext(dir) {
|
|
6043
|
+
return { cwd: dir };
|
|
6044
|
+
},
|
|
6045
|
+
async scaffold(decision, ctx) {
|
|
6046
|
+
throw new Error("Scaffold via adapter not yet implemented");
|
|
6047
|
+
}
|
|
6048
|
+
};
|
|
6049
|
+
copilotAdapter = {
|
|
6050
|
+
id: "copilot",
|
|
6051
|
+
name: "Copilot CLI",
|
|
6052
|
+
manifestPath: ".github/plugin/plugin.json",
|
|
6053
|
+
marketplacePath: ".github/plugin/marketplace.json",
|
|
6054
|
+
mcpFilename: ".mcp.json",
|
|
6055
|
+
validators: [
|
|
6056
|
+
copilotPluginValidator,
|
|
6057
|
+
copilotMarketplaceValidator,
|
|
6058
|
+
copilotMcpValidator,
|
|
6059
|
+
copilotSkillValidator
|
|
6060
|
+
],
|
|
6061
|
+
detectContext(dir) {
|
|
6062
|
+
return { cwd: dir };
|
|
6063
|
+
},
|
|
6064
|
+
async scaffold(decision, ctx) {
|
|
6065
|
+
throw new Error("Scaffold via adapter not yet implemented");
|
|
6066
|
+
}
|
|
6067
|
+
};
|
|
6068
|
+
adapters = [claudeAdapter, codexAdapter, cursorAdapter, copilotAdapter];
|
|
6069
|
+
});
|
|
6070
|
+
|
|
4295
6071
|
// src/validators/index.ts
|
|
4296
6072
|
function resolveFor(forFlag, allValidators = validators) {
|
|
4297
6073
|
if (!forFlag) {
|
|
@@ -4309,43 +6085,30 @@ Available: ${available}` };
|
|
|
4309
6085
|
}
|
|
4310
6086
|
const byProvider = allValidators.filter((v) => v.provider === forFlag);
|
|
4311
6087
|
if (byProvider.length === 0) {
|
|
4312
|
-
const
|
|
4313
|
-
|
|
6088
|
+
const knownProviders = [...new Set([
|
|
6089
|
+
...allValidators.map((v) => v.provider),
|
|
6090
|
+
...supportedProviders
|
|
6091
|
+
])];
|
|
6092
|
+
if (!knownProviders.includes(forFlag)) {
|
|
6093
|
+
return { matched: [], error: `Unknown provider: "${forFlag}"
|
|
4314
6094
|
|
|
4315
|
-
Available providers: ${
|
|
6095
|
+
Available providers: ${knownProviders.join(", ")}` };
|
|
6096
|
+
}
|
|
6097
|
+
return { matched: [] };
|
|
4316
6098
|
}
|
|
4317
6099
|
return { matched: byProvider };
|
|
4318
6100
|
}
|
|
4319
6101
|
var validators;
|
|
4320
6102
|
var init_validators = __esm(() => {
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
init_hooks();
|
|
4325
|
-
init_mcp();
|
|
4326
|
-
init_subagent();
|
|
4327
|
-
init_command();
|
|
4328
|
-
init_memory();
|
|
4329
|
-
init_lsp();
|
|
4330
|
-
init_monitors();
|
|
4331
|
-
validators = [
|
|
4332
|
-
claudeSkillValidator,
|
|
4333
|
-
claudePluginValidator,
|
|
4334
|
-
claudeMarketplaceValidator,
|
|
4335
|
-
claudeHooksValidator,
|
|
4336
|
-
claudeMcpValidator,
|
|
4337
|
-
claudeSubagentValidator,
|
|
4338
|
-
claudeCommandValidator,
|
|
4339
|
-
claudeMemoryValidator,
|
|
4340
|
-
claudeLspValidator,
|
|
4341
|
-
claudeMonitorsValidator
|
|
4342
|
-
];
|
|
6103
|
+
init_providers();
|
|
6104
|
+
init_spec();
|
|
6105
|
+
validators = adapters.flatMap((a) => a.validators);
|
|
4343
6106
|
});
|
|
4344
6107
|
|
|
4345
6108
|
// src/core/remote.ts
|
|
4346
6109
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
4347
6110
|
import { mkdtempSync, rmSync } from "fs";
|
|
4348
|
-
import { join as
|
|
6111
|
+
import { join as join25 } from "path";
|
|
4349
6112
|
import { tmpdir } from "os";
|
|
4350
6113
|
function parseRemoteUrl(input) {
|
|
4351
6114
|
if (input.startsWith(".") || input.startsWith("/") || input.startsWith("~")) {
|
|
@@ -4382,7 +6145,7 @@ function isGhAvailable() {
|
|
|
4382
6145
|
return ghAvailable;
|
|
4383
6146
|
}
|
|
4384
6147
|
async function cloneToTemp(parsed) {
|
|
4385
|
-
const tmpDir = mkdtempSync(
|
|
6148
|
+
const tmpDir = mkdtempSync(join25(tmpdir(), "dora-"));
|
|
4386
6149
|
const cleanup = () => {
|
|
4387
6150
|
try {
|
|
4388
6151
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
@@ -4430,15 +6193,15 @@ var exports_validate_top = {};
|
|
|
4430
6193
|
__export(exports_validate_top, {
|
|
4431
6194
|
default: () => validate_top_default
|
|
4432
6195
|
});
|
|
4433
|
-
import { existsSync as
|
|
4434
|
-
import { resolve as
|
|
4435
|
-
var
|
|
6196
|
+
import { existsSync as existsSync40 } from "fs";
|
|
6197
|
+
import { resolve as resolve27 } from "path";
|
|
6198
|
+
var import_picocolors15, validate_top_default;
|
|
4436
6199
|
var init_validate_top = __esm(() => {
|
|
4437
6200
|
init_dist();
|
|
4438
6201
|
init_out();
|
|
4439
6202
|
init_validators();
|
|
4440
6203
|
init_remote();
|
|
4441
|
-
|
|
6204
|
+
import_picocolors15 = __toESM(require_picocolors(), 1);
|
|
4442
6205
|
validate_top_default = defineCommand({
|
|
4443
6206
|
meta: {
|
|
4444
6207
|
name: "validate",
|
|
@@ -4478,24 +6241,24 @@ var init_validate_top = __esm(() => {
|
|
|
4478
6241
|
let cleanup;
|
|
4479
6242
|
if (remote) {
|
|
4480
6243
|
ui.info(`
|
|
4481
|
-
Cloning ${
|
|
6244
|
+
Cloning ${import_picocolors15.default.dim(args.path)}...`);
|
|
4482
6245
|
try {
|
|
4483
6246
|
const result = await cloneToTemp(remote);
|
|
4484
|
-
fullPath = remote.subpath ?
|
|
6247
|
+
fullPath = remote.subpath ? resolve27(result.dir, remote.subpath) : result.dir;
|
|
4485
6248
|
cleanup = result.cleanup;
|
|
4486
6249
|
} catch (err) {
|
|
4487
6250
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4488
6251
|
ui.fail(msg);
|
|
4489
6252
|
process.exit(1);
|
|
4490
6253
|
}
|
|
4491
|
-
if (!
|
|
6254
|
+
if (!existsSync40(fullPath)) {
|
|
4492
6255
|
cleanup();
|
|
4493
6256
|
ui.fail(`Subdirectory not found in repo: ${remote.subpath}`);
|
|
4494
6257
|
process.exit(1);
|
|
4495
6258
|
}
|
|
4496
6259
|
} else {
|
|
4497
|
-
fullPath =
|
|
4498
|
-
if (!
|
|
6260
|
+
fullPath = resolve27(args.path);
|
|
6261
|
+
if (!existsSync40(fullPath)) {
|
|
4499
6262
|
ui.fail(`Path not found: ${args.path}
|
|
4500
6263
|
|
|
4501
6264
|
Check that the path is correct and the directory exists.`);
|
|
@@ -4526,13 +6289,13 @@ Check that the path is correct and the directory exists.`);
|
|
|
4526
6289
|
` + `Available providers:
|
|
4527
6290
|
` + providers.map((p) => {
|
|
4528
6291
|
const pvs = validators.filter((v) => v.provider === p);
|
|
4529
|
-
return ` ${
|
|
4530
|
-
` + pvs.map((v) => ` \u2022 ${
|
|
6292
|
+
return ` ${import_picocolors15.default.bold(p)}
|
|
6293
|
+
` + pvs.map((v) => ` \u2022 ${import_picocolors15.default.dim(v.id)} \u2014 ${v.description}`).join(`
|
|
4531
6294
|
`);
|
|
4532
6295
|
}).join(`
|
|
4533
6296
|
`) + `
|
|
4534
6297
|
|
|
4535
|
-
Use ${
|
|
6298
|
+
Use ${import_picocolors15.default.dim("--for <provider>")} or ${import_picocolors15.default.dim("--for <provider:type>")} to target explicitly.`);
|
|
4536
6299
|
process.exit(1);
|
|
4537
6300
|
}
|
|
4538
6301
|
const allResults = [];
|
|
@@ -4553,7 +6316,7 @@ Use ${import_picocolors13.default.dim("--for <provider>")} or ${import_picocolor
|
|
|
4553
6316
|
} else {
|
|
4554
6317
|
for (const { id, name, result } of allResults) {
|
|
4555
6318
|
ui.write(`
|
|
4556
|
-
${
|
|
6319
|
+
${import_picocolors15.default.bold("dora validate")} \u2014 ${import_picocolors15.default.white(name)} ${import_picocolors15.default.dim(`(${id})`)}
|
|
4557
6320
|
`);
|
|
4558
6321
|
ui.info(` Path: ${args.path}
|
|
4559
6322
|
`);
|
|
@@ -4568,7 +6331,7 @@ Use ${import_picocolors13.default.dim("--for <provider>")} or ${import_picocolor
|
|
|
4568
6331
|
}
|
|
4569
6332
|
if (result.errors.length === 0 && result.warnings.length === 0) {
|
|
4570
6333
|
ui.write(`
|
|
4571
|
-
${
|
|
6334
|
+
${import_picocolors15.default.green("\u2713")} ${import_picocolors15.default.white("All checks passed.")}
|
|
4572
6335
|
`);
|
|
4573
6336
|
} else {
|
|
4574
6337
|
ui.info(`
|
|
@@ -4590,16 +6353,16 @@ var exports_init2 = {};
|
|
|
4590
6353
|
__export(exports_init2, {
|
|
4591
6354
|
default: () => init_default2
|
|
4592
6355
|
});
|
|
4593
|
-
import { basename as
|
|
6356
|
+
import { basename as basename6, join as join26 } from "path";
|
|
4594
6357
|
var {spawnSync: spawnSync5 } = globalThis.Bun;
|
|
4595
|
-
var
|
|
6358
|
+
var import_picocolors16, init_default2;
|
|
4596
6359
|
var init_init2 = __esm(() => {
|
|
4597
6360
|
init_dist();
|
|
4598
6361
|
init_out();
|
|
4599
6362
|
init_journal_config();
|
|
4600
6363
|
init_journal_remote();
|
|
4601
6364
|
init_prompt();
|
|
4602
|
-
|
|
6365
|
+
import_picocolors16 = __toESM(require_picocolors(), 1);
|
|
4603
6366
|
init_default2 = defineCommand({
|
|
4604
6367
|
meta: {
|
|
4605
6368
|
name: "init",
|
|
@@ -4626,17 +6389,17 @@ var init_init2 = __esm(() => {
|
|
|
4626
6389
|
ui.heading("dora init \u2014 Set up doraval, your journal, and the coding agent dora should use on the fly");
|
|
4627
6390
|
const ghCheck = ensureGhCli();
|
|
4628
6391
|
if (!ghCheck.ok) {
|
|
4629
|
-
ui.write(` ${
|
|
6392
|
+
ui.write(` ${import_picocolors16.default.red("\u2717")} ${import_picocolors16.default.white("The GitHub CLI (")}${import_picocolors16.default.bold("gh")}${import_picocolors16.default.white(") is not installed.")}
|
|
4630
6393
|
`);
|
|
4631
|
-
ui.info(` doraval uses ${
|
|
6394
|
+
ui.info(` doraval uses ${import_picocolors16.default.bold("gh")} to fetch and sync journal files with GitHub.
|
|
4632
6395
|
`);
|
|
4633
6396
|
ui.info(` Install it:
|
|
4634
6397
|
`);
|
|
4635
|
-
ui.info(` macOS: ${
|
|
4636
|
-
ui.info(` Linux: ${
|
|
4637
|
-
ui.info(` Windows: ${
|
|
6398
|
+
ui.info(` macOS: ${import_picocolors16.default.dim("brew install gh")}`);
|
|
6399
|
+
ui.info(` Linux: ${import_picocolors16.default.dim("https://github.com/cli/cli/blob/trunk/docs/install_linux.md")}`);
|
|
6400
|
+
ui.info(` Windows: ${import_picocolors16.default.dim("winget install --id GitHub.cli")}
|
|
4638
6401
|
`);
|
|
4639
|
-
ui.info(` Then authenticate: ${
|
|
6402
|
+
ui.info(` Then authenticate: ${import_picocolors16.default.dim("gh auth login")}
|
|
4640
6403
|
`);
|
|
4641
6404
|
process.exit(1);
|
|
4642
6405
|
}
|
|
@@ -4649,44 +6412,44 @@ var init_init2 = __esm(() => {
|
|
|
4649
6412
|
if (gitOwner) {
|
|
4650
6413
|
defaultRepo = `${gitOwner}/${gitOwner}.md`;
|
|
4651
6414
|
if (ghLogin && ghLogin !== gitOwner) {
|
|
4652
|
-
sourceNote = ` ${
|
|
6415
|
+
sourceNote = ` ${import_picocolors16.default.dim("(from git remote; your active gh account is " + ghLogin + ")")}
|
|
4653
6416
|
`;
|
|
4654
6417
|
} else {
|
|
4655
|
-
sourceNote = ` ${
|
|
6418
|
+
sourceNote = ` ${import_picocolors16.default.dim("(from git remote)")}
|
|
4656
6419
|
`;
|
|
4657
6420
|
}
|
|
4658
6421
|
} else if (ghLogin) {
|
|
4659
6422
|
defaultRepo = `${ghLogin}/${ghLogin}.md`;
|
|
4660
|
-
sourceNote = ` ${
|
|
6423
|
+
sourceNote = ` ${import_picocolors16.default.dim("(from your active gh account)")}
|
|
4661
6424
|
`;
|
|
4662
6425
|
} else {
|
|
4663
|
-
ui.warn(`Not logged in to GitHub. Run ${
|
|
6426
|
+
ui.warn(`Not logged in to GitHub. Run ${import_picocolors16.default.dim("gh auth login")} first.
|
|
4664
6427
|
`);
|
|
4665
6428
|
process.exit(1);
|
|
4666
6429
|
}
|
|
4667
6430
|
const existingConfig = await readConfig();
|
|
4668
6431
|
if (existingConfig?.journal.repo) {
|
|
4669
6432
|
defaultRepo = existingConfig.journal.repo;
|
|
4670
|
-
sourceNote = ` ${
|
|
6433
|
+
sourceNote = ` ${import_picocolors16.default.dim("(from your previous journal setup)")}
|
|
4671
6434
|
`;
|
|
4672
6435
|
}
|
|
4673
|
-
ui.info(` Journal repo ${
|
|
6436
|
+
ui.info(` Journal repo ${import_picocolors16.default.dim("(owner/name)")}`);
|
|
4674
6437
|
if (sourceNote)
|
|
4675
6438
|
ui.write(sourceNote);
|
|
4676
6439
|
repo = prompt(" >", defaultRepo);
|
|
4677
6440
|
}
|
|
4678
6441
|
let project = args.project || process.env.DORAVAL_PROJECT;
|
|
4679
6442
|
if (!project) {
|
|
4680
|
-
const defaultProject =
|
|
6443
|
+
const defaultProject = basename6(process.cwd());
|
|
4681
6444
|
project = prompt(" Project name", defaultProject);
|
|
4682
6445
|
}
|
|
4683
6446
|
project = sanitizeProjectName(project);
|
|
4684
6447
|
if (!repoExists(repo)) {
|
|
4685
|
-
ui.write(` ${
|
|
6448
|
+
ui.write(` ${import_picocolors16.default.red("\u2717")} ${import_picocolors16.default.white("Repository")} ${import_picocolors16.default.bold(repo)} ${import_picocolors16.default.white("not found on GitHub.")}
|
|
4686
6449
|
`);
|
|
4687
6450
|
ui.info(` Create it first:
|
|
4688
6451
|
`);
|
|
4689
|
-
ui.info(` ${
|
|
6452
|
+
ui.info(` ${import_picocolors16.default.dim(`gh repo create ${repo} --private --description "Personal journal for agent decisions"`)}
|
|
4690
6453
|
`);
|
|
4691
6454
|
process.exit(1);
|
|
4692
6455
|
}
|
|
@@ -4694,16 +6457,16 @@ var init_init2 = __esm(() => {
|
|
|
4694
6457
|
const alreadyRegistered = existing?.journal.projects[project];
|
|
4695
6458
|
const isRefresh = alreadyRegistered && args.refresh;
|
|
4696
6459
|
if (alreadyRegistered && !isRefresh) {
|
|
4697
|
-
ui.write(` ${
|
|
6460
|
+
ui.write(` ${import_picocolors16.default.yellow("\u26A0")} ${import_picocolors16.default.white("Project")} ${import_picocolors16.default.bold(project)} ${import_picocolors16.default.white("is already registered.")}
|
|
4698
6461
|
`);
|
|
4699
6462
|
ui.info(` Repo: ${existing.journal.repo}
|
|
4700
6463
|
`);
|
|
4701
|
-
ui.info(` To refresh journal files, use ${
|
|
6464
|
+
ui.info(` To refresh journal files, use ${import_picocolors16.default.dim("dora journal update")} (or ${import_picocolors16.default.dim("dora init --refresh")}).
|
|
4702
6465
|
`);
|
|
4703
6466
|
}
|
|
4704
6467
|
const journalsDir = getJournalsDir();
|
|
4705
6468
|
const remotePath = `projects/${project}.md`;
|
|
4706
|
-
const localPath =
|
|
6469
|
+
const localPath = join26(journalsDir, `${project}.md`);
|
|
4707
6470
|
const effectiveRepo = isRefresh && !args.repo ? existing.journal.repo : repo;
|
|
4708
6471
|
const config = existing ?? {
|
|
4709
6472
|
journal: { repo: effectiveRepo, projects: {} }
|
|
@@ -4714,9 +6477,9 @@ var init_init2 = __esm(() => {
|
|
|
4714
6477
|
local_path: localPath
|
|
4715
6478
|
};
|
|
4716
6479
|
ensureDoravalDirs();
|
|
4717
|
-
ui.write(` ${
|
|
6480
|
+
ui.write(` ${import_picocolors16.default.dim(import_picocolors16.default.gray("Fetching journal files from"))} ${import_picocolors16.default.gray(effectiveRepo)}${import_picocolors16.default.dim(import_picocolors16.default.gray("..."))}
|
|
4718
6481
|
`);
|
|
4719
|
-
const globalDest =
|
|
6482
|
+
const globalDest = join26(journalsDir, "global.md");
|
|
4720
6483
|
const refreshGlobalRes = await refreshLocalJournalFile(effectiveRepo, "global.md", globalDest);
|
|
4721
6484
|
let wroteGlobal;
|
|
4722
6485
|
if (!refreshGlobalRes.ok) {
|
|
@@ -4733,7 +6496,7 @@ var init_init2 = __esm(() => {
|
|
|
4733
6496
|
if (wroteGlobal) {
|
|
4734
6497
|
ui.success("global.md");
|
|
4735
6498
|
} else {
|
|
4736
|
-
ui.write(` ${
|
|
6499
|
+
ui.write(` ${import_picocolors16.default.dim("\xB7")} global.md ${import_picocolors16.default.dim("(not found \u2014 will be created on first sync)")}`);
|
|
4737
6500
|
await Bun.write(globalDest, `# Global Journal
|
|
4738
6501
|
|
|
4739
6502
|
Cross-project principles.
|
|
@@ -4755,7 +6518,7 @@ Cross-project principles.
|
|
|
4755
6518
|
if (wroteProject) {
|
|
4756
6519
|
ui.success(remotePath);
|
|
4757
6520
|
} else {
|
|
4758
|
-
ui.write(` ${
|
|
6521
|
+
ui.write(` ${import_picocolors16.default.dim("\xB7")} ${remotePath} ${import_picocolors16.default.dim("(not found \u2014 will be created on first sync)")}`);
|
|
4759
6522
|
await Bun.write(localPath, `# ${project} Journal
|
|
4760
6523
|
|
|
4761
6524
|
Project-specific decisions.
|
|
@@ -4763,13 +6526,13 @@ Project-specific decisions.
|
|
|
4763
6526
|
}
|
|
4764
6527
|
await writeConfig(config);
|
|
4765
6528
|
ui.write(`
|
|
4766
|
-
${
|
|
6529
|
+
${import_picocolors16.default.green("\u2713")} ${import_picocolors16.default.white("Journal ready for project")} ${import_picocolors16.default.bold(import_picocolors16.default.white(project))}.
|
|
4767
6530
|
`);
|
|
4768
6531
|
const existingAgent = (await readConfig())?.agent;
|
|
4769
6532
|
if (existingAgent?.command) {
|
|
4770
|
-
ui.write(` ${
|
|
6533
|
+
ui.write(` ${import_picocolors16.default.bold(import_picocolors16.default.white("Coding agent (already configured)"))}
|
|
4771
6534
|
`);
|
|
4772
|
-
ui.write(` Current: ${
|
|
6535
|
+
ui.write(` Current: ${import_picocolors16.default.dim(import_picocolors16.default.gray(existingAgent.command))} template: ${import_picocolors16.default.dim(import_picocolors16.default.gray(existingAgent.prompt_template || "(default)"))}
|
|
4773
6536
|
`);
|
|
4774
6537
|
const change = prompt(" Reconfigure / change the coding agent for on-the-fly enrichment? (y/N)", "n");
|
|
4775
6538
|
if (!/^y/i.test(String(change))) {
|
|
@@ -4779,16 +6542,16 @@ Project-specific decisions.
|
|
|
4779
6542
|
if (existingAgent)
|
|
4780
6543
|
cfg.agent = existingAgent;
|
|
4781
6544
|
await writeConfig(cfg);
|
|
4782
|
-
ui.write(` ${
|
|
6545
|
+
ui.write(` ${import_picocolors16.default.green("\u2713")} ${import_picocolors16.default.white("Try:")} ${import_picocolors16.default.dim(import_picocolors16.default.gray('dora journal add "short decision"'))}
|
|
4783
6546
|
`);
|
|
4784
6547
|
process.exit(0);
|
|
4785
6548
|
return;
|
|
4786
6549
|
}
|
|
4787
6550
|
ui.blank();
|
|
4788
6551
|
} else {
|
|
4789
|
-
ui.write(` ${
|
|
6552
|
+
ui.write(` ${import_picocolors16.default.bold(import_picocolors16.default.white("Coding agent for journal add"))}
|
|
4790
6553
|
`);
|
|
4791
|
-
ui.info(` When configured, ${
|
|
6554
|
+
ui.info(` When configured, ${import_picocolors16.default.dim(import_picocolors16.default.gray('dora journal add ".."'))} will use your agent to enrich entries with tags and rationale automatically.
|
|
4792
6555
|
`);
|
|
4793
6556
|
}
|
|
4794
6557
|
const common = [
|
|
@@ -4807,7 +6570,7 @@ Project-specific decisions.
|
|
|
4807
6570
|
}
|
|
4808
6571
|
}
|
|
4809
6572
|
let agentCmd = detected || "claude";
|
|
4810
|
-
ui.write(` Detected / default agent command: ${
|
|
6573
|
+
ui.write(` Detected / default agent command: ${import_picocolors16.default.dim(import_picocolors16.default.gray(agentCmd))}`);
|
|
4811
6574
|
agentCmd = prompt(" Agent command (the binary you run for prompts)", agentCmd);
|
|
4812
6575
|
let template = detected ? common.find((c) => c.name === detected)?.template || '-p "{{prompt}}" --output-format json' : '-p "{{prompt}}" --output-format json';
|
|
4813
6576
|
ui.info(` Prompt template (use {{prompt}} placeholder):`);
|
|
@@ -4819,11 +6582,11 @@ Project-specific decisions.
|
|
|
4819
6582
|
};
|
|
4820
6583
|
await writeConfig(finalConfig);
|
|
4821
6584
|
ui.write(`
|
|
4822
|
-
${
|
|
6585
|
+
${import_picocolors16.default.green("\u2713")} ${import_picocolors16.default.white("Agent configured.")}
|
|
4823
6586
|
`);
|
|
4824
|
-
ui.info(` Re-run ${
|
|
6587
|
+
ui.info(` Re-run ${import_picocolors16.default.dim(import_picocolors16.default.gray("dora init"))} anytime to change it.
|
|
4825
6588
|
`);
|
|
4826
|
-
ui.info(` Next: ${
|
|
6589
|
+
ui.info(` Next: ${import_picocolors16.default.dim(import_picocolors16.default.gray('dora journal add ".."'))}, ${import_picocolors16.default.dim(import_picocolors16.default.gray("dora journal list"))}, or ${import_picocolors16.default.dim(import_picocolors16.default.gray("dora journal update"))}.
|
|
4827
6590
|
`);
|
|
4828
6591
|
process.exit(0);
|
|
4829
6592
|
}
|
|
@@ -4831,7 +6594,7 @@ Project-specific decisions.
|
|
|
4831
6594
|
});
|
|
4832
6595
|
|
|
4833
6596
|
// src/core/update.ts
|
|
4834
|
-
import { resolve as
|
|
6597
|
+
import { resolve as resolve28 } from "path";
|
|
4835
6598
|
import { homedir as homedir2 } from "os";
|
|
4836
6599
|
function normalizePath(p) {
|
|
4837
6600
|
return p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
@@ -5010,14 +6773,14 @@ async function readMarker() {
|
|
|
5010
6773
|
async function writeMarker(marker) {
|
|
5011
6774
|
try {
|
|
5012
6775
|
const { mkdir, writeFile } = await import("fs/promises");
|
|
5013
|
-
const { dirname:
|
|
5014
|
-
await mkdir(
|
|
6776
|
+
const { dirname: dirname7 } = await import("path");
|
|
6777
|
+
await mkdir(dirname7(MARKER_PATH), { recursive: true });
|
|
5015
6778
|
await writeFile(MARKER_PATH, JSON.stringify(marker, null, 2));
|
|
5016
6779
|
} catch {}
|
|
5017
6780
|
}
|
|
5018
6781
|
var MARKER_PATH;
|
|
5019
6782
|
var init_update2 = __esm(() => {
|
|
5020
|
-
MARKER_PATH =
|
|
6783
|
+
MARKER_PATH = resolve28(homedir2(), ".doraval", "install.json");
|
|
5021
6784
|
});
|
|
5022
6785
|
|
|
5023
6786
|
// src/cli/commands/update.ts
|
|
@@ -5032,10 +6795,10 @@ import { realpath, access } from "fs/promises";
|
|
|
5032
6795
|
async function confirmUpdate() {
|
|
5033
6796
|
const { createInterface } = await import("readline");
|
|
5034
6797
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
5035
|
-
return new Promise((
|
|
6798
|
+
return new Promise((resolve29) => {
|
|
5036
6799
|
rl.question("Update now? (y/N) ", (answer) => {
|
|
5037
6800
|
rl.close();
|
|
5038
|
-
|
|
6801
|
+
resolve29(answer.toLowerCase().startsWith("y"));
|
|
5039
6802
|
});
|
|
5040
6803
|
});
|
|
5041
6804
|
}
|
|
@@ -5158,10 +6921,58 @@ Raw output above.`);
|
|
|
5158
6921
|
});
|
|
5159
6922
|
});
|
|
5160
6923
|
|
|
6924
|
+
// src/cli/commands/providers.ts
|
|
6925
|
+
var exports_providers = {};
|
|
6926
|
+
__export(exports_providers, {
|
|
6927
|
+
default: () => providers_default
|
|
6928
|
+
});
|
|
6929
|
+
var import_picocolors17, providers_default;
|
|
6930
|
+
var init_providers2 = __esm(() => {
|
|
6931
|
+
init_dist();
|
|
6932
|
+
init_out();
|
|
6933
|
+
init_spec();
|
|
6934
|
+
import_picocolors17 = __toESM(require_picocolors(), 1);
|
|
6935
|
+
providers_default = defineCommand({
|
|
6936
|
+
meta: {
|
|
6937
|
+
name: "providers",
|
|
6938
|
+
description: "List supported providers and their packaging details"
|
|
6939
|
+
},
|
|
6940
|
+
args: {
|
|
6941
|
+
json: {
|
|
6942
|
+
type: "boolean",
|
|
6943
|
+
description: "Output as JSON",
|
|
6944
|
+
default: false
|
|
6945
|
+
}
|
|
6946
|
+
},
|
|
6947
|
+
run({ args }) {
|
|
6948
|
+
if (args.json) {
|
|
6949
|
+
console.log(JSON.stringify(supportedProviders.map((id) => {
|
|
6950
|
+
const spec = getProviderSpec(id);
|
|
6951
|
+
return { ...spec, id };
|
|
6952
|
+
}), null, 2));
|
|
6953
|
+
process.exit(0);
|
|
6954
|
+
}
|
|
6955
|
+
ui.heading("doraval providers \u2014 Supported platforms");
|
|
6956
|
+
for (const id of supportedProviders) {
|
|
6957
|
+
const spec = getProviderSpec(id);
|
|
6958
|
+
ui.write(`
|
|
6959
|
+
${import_picocolors17.default.bold(id)} \u2014 ${spec.name}`);
|
|
6960
|
+
ui.info(` Manifest: ${spec.manifestPath}`);
|
|
6961
|
+
ui.info(` Marketplace: ${spec.marketplacePath}`);
|
|
6962
|
+
ui.info(` MCP: ${spec.mcpFilename}`);
|
|
6963
|
+
ui.info(` Example: doraval validate . --for ${id}`);
|
|
6964
|
+
}
|
|
6965
|
+
ui.write(`
|
|
6966
|
+
Use --json for machine-readable output.`);
|
|
6967
|
+
process.exit(0);
|
|
6968
|
+
}
|
|
6969
|
+
});
|
|
6970
|
+
});
|
|
6971
|
+
|
|
5161
6972
|
// src/cli/index.ts
|
|
5162
6973
|
init_dist();
|
|
5163
6974
|
var import__package = __toESM(require_package(), 1);
|
|
5164
|
-
var
|
|
6975
|
+
var import_picocolors18 = __toESM(require_picocolors(), 1);
|
|
5165
6976
|
var skill = defineCommand({
|
|
5166
6977
|
meta: {
|
|
5167
6978
|
name: "skill",
|
|
@@ -5218,6 +7029,32 @@ var codex = defineCommand({
|
|
|
5218
7029
|
showUsage(codex);
|
|
5219
7030
|
}
|
|
5220
7031
|
});
|
|
7032
|
+
var cursor = defineCommand({
|
|
7033
|
+
meta: {
|
|
7034
|
+
name: "cursor",
|
|
7035
|
+
description: "Cursor-specific commands (packaging, scaffolding, distribution)"
|
|
7036
|
+
},
|
|
7037
|
+
subCommands: {
|
|
7038
|
+
new: () => Promise.resolve().then(() => (init_new3(), exports_new3)).then((m) => m.default),
|
|
7039
|
+
bump: () => Promise.resolve().then(() => (init_bump(), exports_bump)).then((m) => m.default)
|
|
7040
|
+
},
|
|
7041
|
+
run() {
|
|
7042
|
+
showUsage(cursor);
|
|
7043
|
+
}
|
|
7044
|
+
});
|
|
7045
|
+
var copilot = defineCommand({
|
|
7046
|
+
meta: {
|
|
7047
|
+
name: "copilot",
|
|
7048
|
+
description: "Copilot CLI-specific commands (packaging, scaffolding, distribution)"
|
|
7049
|
+
},
|
|
7050
|
+
subCommands: {
|
|
7051
|
+
new: () => Promise.resolve().then(() => (init_new4(), exports_new4)).then((m) => m.default),
|
|
7052
|
+
bump: () => Promise.resolve().then(() => (init_bump(), exports_bump)).then((m) => m.default)
|
|
7053
|
+
},
|
|
7054
|
+
run() {
|
|
7055
|
+
showUsage(copilot);
|
|
7056
|
+
}
|
|
7057
|
+
});
|
|
5221
7058
|
var doraemonArt = `
|
|
5222
7059
|
\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2880\u28E0\u28E4\u28F4\u28F6\u28F6\u28F6\u28F6\u28F6\u2836\u28F6\u28E4\u28E4\u28C0\u2800\u2800\u2800\u2800\u2800\u2800
|
|
5223
7060
|
\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2880\u28E4\u28FE\u28FF\u28FF\u28FF\u2801\u2800\u2880\u2808\u28BF\u2880\u28C0\u2800\u2839\u28FF\u28FF\u28FF\u28E6\u28C4\u2800\u2800\u2800
|
|
@@ -5241,14 +7078,17 @@ var main = defineCommand({
|
|
|
5241
7078
|
init: () => Promise.resolve().then(() => (init_init2(), exports_init2)).then((m) => m.default),
|
|
5242
7079
|
bump: () => Promise.resolve().then(() => (init_bump(), exports_bump)).then((m) => m.default),
|
|
5243
7080
|
update: () => Promise.resolve().then(() => (init_update3(), exports_update2)).then((m) => m.default),
|
|
7081
|
+
providers: () => Promise.resolve().then(() => (init_providers2(), exports_providers)).then((m) => m.default),
|
|
5244
7082
|
skill: () => Promise.resolve(skill),
|
|
5245
7083
|
journal: () => Promise.resolve(journal),
|
|
5246
7084
|
claude: () => Promise.resolve(claude),
|
|
5247
|
-
codex: () => Promise.resolve(codex)
|
|
7085
|
+
codex: () => Promise.resolve(codex),
|
|
7086
|
+
cursor: () => Promise.resolve(cursor),
|
|
7087
|
+
copilot: () => Promise.resolve(copilot)
|
|
5248
7088
|
},
|
|
5249
7089
|
run() {
|
|
5250
7090
|
console.log(`
|
|
5251
|
-
` +
|
|
7091
|
+
` + import_picocolors18.default.blue(doraemonArt) + `
|
|
5252
7092
|
`);
|
|
5253
7093
|
showUsage(main);
|
|
5254
7094
|
}
|