@hacksmith/doraval 0.2.21 → 0.2.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/bin/doraval.js +1187 -187
- package/package.json +1 -1
package/bin/doraval.js
CHANGED
|
@@ -2624,17 +2624,17 @@ __export(exports_new, {
|
|
|
2624
2624
|
default: () => new_default,
|
|
2625
2625
|
decidePath: () => decidePath
|
|
2626
2626
|
});
|
|
2627
|
-
import { join as join8 } from "path";
|
|
2627
|
+
import { join as join8, basename as basename2 } from "path";
|
|
2628
2628
|
import { mkdirSync as mkdirSync2, writeFileSync, existsSync as existsSync9 } from "fs";
|
|
2629
2629
|
function decidePath(ctx, intent, providedName) {
|
|
2630
2630
|
const rawName = providedName || "";
|
|
2631
|
-
let
|
|
2631
|
+
let decisionPath = "standalone";
|
|
2632
2632
|
let targetDir = ctx.cwd;
|
|
2633
2633
|
let shouldCreateDir = false;
|
|
2634
2634
|
let migrateExisting = false;
|
|
2635
|
-
const useCurrentDirAsRoot = rawName === "." || rawName ===
|
|
2635
|
+
const useCurrentDirAsRoot = rawName === "." || rawName === basename2(ctx.cwd) || !rawName;
|
|
2636
2636
|
if (intent === "distribute" || intent === "self-later" && ctx.looseSkillFiles.length > 0 && !ctx.hasClaudeDir) {
|
|
2637
|
-
|
|
2637
|
+
decisionPath = "plugin";
|
|
2638
2638
|
if (useCurrentDirAsRoot) {
|
|
2639
2639
|
targetDir = ctx.cwd;
|
|
2640
2640
|
shouldCreateDir = false;
|
|
@@ -2644,7 +2644,7 @@ function decidePath(ctx, intent, providedName) {
|
|
|
2644
2644
|
}
|
|
2645
2645
|
migrateExisting = ctx.looseSkillFiles.length > 0;
|
|
2646
2646
|
} else if (intent === "self-later" && !ctx.hasClaudeDir) {
|
|
2647
|
-
|
|
2647
|
+
decisionPath = "plugin";
|
|
2648
2648
|
if (useCurrentDirAsRoot) {
|
|
2649
2649
|
targetDir = ctx.cwd;
|
|
2650
2650
|
shouldCreateDir = false;
|
|
@@ -2652,7 +2652,7 @@ function decidePath(ctx, intent, providedName) {
|
|
|
2652
2652
|
targetDir = join8(ctx.cwd, rawName);
|
|
2653
2653
|
shouldCreateDir = true;
|
|
2654
2654
|
}
|
|
2655
|
-
} else if (
|
|
2655
|
+
} else if (decisionPath === "standalone") {
|
|
2656
2656
|
if (useCurrentDirAsRoot) {
|
|
2657
2657
|
targetDir = ctx.cwd;
|
|
2658
2658
|
shouldCreateDir = false;
|
|
@@ -2661,7 +2661,7 @@ function decidePath(ctx, intent, providedName) {
|
|
|
2661
2661
|
shouldCreateDir = true;
|
|
2662
2662
|
}
|
|
2663
2663
|
}
|
|
2664
|
-
return { path, targetDir, shouldCreateDir, migrateExisting };
|
|
2664
|
+
return { path: decisionPath, targetDir, shouldCreateDir, migrateExisting };
|
|
2665
2665
|
}
|
|
2666
2666
|
function scaffold(decision, ctx, migrateContent) {
|
|
2667
2667
|
const { targetDir, path, shouldCreateDir } = decision;
|
|
@@ -2673,26 +2673,57 @@ function scaffold(decision, ctx, migrateContent) {
|
|
|
2673
2673
|
mkdirSync2(targetDir, { recursive: true });
|
|
2674
2674
|
}
|
|
2675
2675
|
if (path === "plugin") {
|
|
2676
|
+
const pluginName = basename2(targetDir);
|
|
2676
2677
|
const pluginJson = {
|
|
2677
|
-
name:
|
|
2678
|
+
name: pluginName,
|
|
2678
2679
|
description: "Scaffolded by doraval claude new",
|
|
2679
2680
|
version: "0.1.0"
|
|
2680
2681
|
};
|
|
2681
2682
|
mkdirSync2(join8(targetDir, ".claude-plugin"), { recursive: true });
|
|
2682
2683
|
writeFileSync(join8(targetDir, ".claude-plugin", "plugin.json"), JSON.stringify(pluginJson, null, 2));
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2684
|
+
const marketplaceJson = {
|
|
2685
|
+
name: pluginName,
|
|
2686
|
+
version: "0.1.0",
|
|
2687
|
+
description: "Scaffolded by doraval claude new",
|
|
2688
|
+
author: { name: "" },
|
|
2689
|
+
homepage: "",
|
|
2690
|
+
repository: "",
|
|
2691
|
+
license: "MIT",
|
|
2692
|
+
keywords: ["claude-code", "skills", "plugin"]
|
|
2693
|
+
};
|
|
2694
|
+
writeFileSync(join8(targetDir, "marketplace.json"), JSON.stringify(marketplaceJson, null, 2));
|
|
2695
|
+
const demoSkillName = "doraval";
|
|
2696
|
+
mkdirSync2(join8(targetDir, "skills", demoSkillName), { recursive: true });
|
|
2697
|
+
let skillContent;
|
|
2698
|
+
if (migrateContent) {
|
|
2699
|
+
skillContent = migrateContent;
|
|
2700
|
+
} else {
|
|
2701
|
+
skillContent = `---
|
|
2702
|
+
name: ${demoSkillName}
|
|
2703
|
+
description: Use doraval to validate, measure drift, and judge skills and plugins. Use when authoring or reviewing context engineering artifacts for AI coding agents.
|
|
2690
2704
|
---
|
|
2691
2705
|
|
|
2692
|
-
|
|
2693
|
-
|
|
2706
|
+
# Use Doraval
|
|
2707
|
+
|
|
2708
|
+
Doraval is the context engineering toolkit.
|
|
2709
|
+
|
|
2710
|
+
When you need to check a skill or plugin:
|
|
2711
|
+
|
|
2712
|
+
- Validate the current directory: \`doraval validate .\`
|
|
2713
|
+
- Validate a specific plugin: \`doraval validate . --for claude:plugin\`
|
|
2714
|
+
- Validate one skill: \`doraval skill validate ./skills/${demoSkillName}/\`
|
|
2715
|
+
- Check for rubric drift: \`doraval skill drift ./skills/${demoSkillName}/\`
|
|
2716
|
+
- Get an AI quality judgment: \`doraval skill judge ./skills/${demoSkillName}/\`
|
|
2717
|
+
|
|
2718
|
+
Always run \`doraval validate\` before sharing or publishing a plugin. This skill demonstrates a complete, self-referential example of using doraval inside a generated plugin.`;
|
|
2719
|
+
}
|
|
2720
|
+
writeFileSync(join8(targetDir, "skills", demoSkillName, "SKILL.md"), skillContent);
|
|
2721
|
+
const readmePath = join8(targetDir, "README.md");
|
|
2722
|
+
if (!existsSync9(readmePath)) {
|
|
2723
|
+
writeFileSync(readmePath, "# " + pluginName + `
|
|
2694
2724
|
|
|
2695
2725
|
Claude Code plugin scaffolded by doraval.`);
|
|
2726
|
+
}
|
|
2696
2727
|
} else {
|
|
2697
2728
|
mkdirSync2(join8(targetDir, ".claude", "skills", "my-skill"), { recursive: true });
|
|
2698
2729
|
const skillBody = migrateContent || `# My Skill
|
|
@@ -2752,7 +2783,12 @@ var init_new = __esm(() => {
|
|
|
2752
2783
|
scaffold(decision, ctx, migrateContent);
|
|
2753
2784
|
ui.write(`
|
|
2754
2785
|
${import_picocolors10.default.green("\u2713")} Created ${decision.path} at ${import_picocolors10.default.bold(decision.targetDir)}`);
|
|
2755
|
-
|
|
2786
|
+
const cmdName = decision.path === "plugin" ? `/${basename2(decision.targetDir)}:doraval` : "/my-skill";
|
|
2787
|
+
ui.info(` Command: ${cmdName}`);
|
|
2788
|
+
if (decision.path === "plugin") {
|
|
2789
|
+
ui.info(` Claude: .claude-plugin/plugin.json`);
|
|
2790
|
+
ui.info(` Marketplace: marketplace.json (unified / cross-provider listings)`);
|
|
2791
|
+
}
|
|
2756
2792
|
ui.info(` Test: claude --plugin-dir ${decision.targetDir} (or use normally for standalone)`);
|
|
2757
2793
|
ui.info(` Validate: doraval validate ${decision.targetDir}`);
|
|
2758
2794
|
if (decision.path === "plugin" && decision.migrateExisting) {
|
|
@@ -2763,9 +2799,472 @@ var init_new = __esm(() => {
|
|
|
2763
2799
|
});
|
|
2764
2800
|
});
|
|
2765
2801
|
|
|
2802
|
+
// src/cli/commands/bump.ts
|
|
2803
|
+
var exports_bump = {};
|
|
2804
|
+
__export(exports_bump, {
|
|
2805
|
+
default: () => bump_default
|
|
2806
|
+
});
|
|
2807
|
+
import { resolve as resolve3, join as join9, dirname, relative } from "path";
|
|
2808
|
+
import { existsSync as existsSync10, readFileSync, writeFileSync as writeFileSync2, readdirSync as readdirSync4, statSync } from "fs";
|
|
2809
|
+
function bumpVersion(current, type) {
|
|
2810
|
+
if (/^\d+\.\d+\.\d+$/.test(type))
|
|
2811
|
+
return type;
|
|
2812
|
+
const curr = current || "0.0.0";
|
|
2813
|
+
const parts = curr.split(".").map((n) => parseInt(n, 10) || 0);
|
|
2814
|
+
const [major = 0, minor = 0, patch = 0] = parts;
|
|
2815
|
+
switch (type) {
|
|
2816
|
+
case "patch":
|
|
2817
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
2818
|
+
case "minor":
|
|
2819
|
+
return `${major}.${minor + 1}.0`;
|
|
2820
|
+
case "major":
|
|
2821
|
+
return `${major + 1}.0.0`;
|
|
2822
|
+
default:
|
|
2823
|
+
throw new Error(`Invalid bump type "${type}". Use patch, minor, major, or an exact version like 1.2.3`);
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
function readJson(p) {
|
|
2827
|
+
try {
|
|
2828
|
+
const content = readFileSync(p, "utf8");
|
|
2829
|
+
return JSON.parse(content);
|
|
2830
|
+
} catch {
|
|
2831
|
+
return null;
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
function writeJson(p, data) {
|
|
2835
|
+
writeFileSync2(p, JSON.stringify(data, null, 2) + `
|
|
2836
|
+
`, "utf8");
|
|
2837
|
+
}
|
|
2838
|
+
function getVersion(obj) {
|
|
2839
|
+
if (!obj || typeof obj !== "object")
|
|
2840
|
+
return;
|
|
2841
|
+
if (typeof obj.version === "string")
|
|
2842
|
+
return obj.version;
|
|
2843
|
+
if (obj.metadata && typeof obj.metadata.version === "string")
|
|
2844
|
+
return obj.metadata.version;
|
|
2845
|
+
return;
|
|
2846
|
+
}
|
|
2847
|
+
function setVersion(obj, newVersion) {
|
|
2848
|
+
if (!obj || typeof obj !== "object")
|
|
2849
|
+
return false;
|
|
2850
|
+
if (typeof obj.version === "string") {
|
|
2851
|
+
obj.version = newVersion;
|
|
2852
|
+
return true;
|
|
2853
|
+
}
|
|
2854
|
+
if (obj.metadata && typeof obj.metadata.version === "string") {
|
|
2855
|
+
obj.metadata.version = newVersion;
|
|
2856
|
+
return true;
|
|
2857
|
+
}
|
|
2858
|
+
return false;
|
|
2859
|
+
}
|
|
2860
|
+
function walkForTargets(dir, maxDepth = 6, currentDepth = 0) {
|
|
2861
|
+
const results = [];
|
|
2862
|
+
if (currentDepth > maxDepth)
|
|
2863
|
+
return results;
|
|
2864
|
+
let entries;
|
|
2865
|
+
try {
|
|
2866
|
+
entries = readdirSync4(dir);
|
|
2867
|
+
} catch {
|
|
2868
|
+
return results;
|
|
2869
|
+
}
|
|
2870
|
+
for (const entry of entries) {
|
|
2871
|
+
const full = join9(dir, entry);
|
|
2872
|
+
let st;
|
|
2873
|
+
try {
|
|
2874
|
+
st = statSync(full);
|
|
2875
|
+
} catch {
|
|
2876
|
+
continue;
|
|
2877
|
+
}
|
|
2878
|
+
if (st.isDirectory()) {
|
|
2879
|
+
const sub = walkForTargets(full, maxDepth, currentDepth + 1);
|
|
2880
|
+
results.push(...sub);
|
|
2881
|
+
} else if (st.isFile()) {
|
|
2882
|
+
if (entry === "plugin.json") {
|
|
2883
|
+
const parentDir = dirname(full);
|
|
2884
|
+
const parentName = parentDir.split(/[/\\]/).pop();
|
|
2885
|
+
if (parentName === ".claude-plugin" || parentName === ".codex-plugin" || parentName === ".cursor-plugin") {
|
|
2886
|
+
results.push({
|
|
2887
|
+
file: full,
|
|
2888
|
+
kind: "plugin",
|
|
2889
|
+
label: `plugin manifest (${parentName.replace(".", "")})`
|
|
2890
|
+
});
|
|
2891
|
+
}
|
|
2892
|
+
} else if (entry === "marketplace.json") {
|
|
2893
|
+
const json = readJson(full);
|
|
2894
|
+
if (json && getVersion(json)) {
|
|
2895
|
+
results.push({
|
|
2896
|
+
file: full,
|
|
2897
|
+
kind: "marketplace",
|
|
2898
|
+
label: "marketplace.json"
|
|
2899
|
+
});
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
return results;
|
|
2905
|
+
}
|
|
2906
|
+
var import_picocolors11, bump_default;
|
|
2907
|
+
var init_bump = __esm(() => {
|
|
2908
|
+
init_dist();
|
|
2909
|
+
init_out();
|
|
2910
|
+
import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
2911
|
+
bump_default = defineCommand({
|
|
2912
|
+
meta: {
|
|
2913
|
+
name: "bump",
|
|
2914
|
+
description: "Bump semver versions in plugin.json (manifests) and marketplace.json files (supports Claude, Codex, Cursor)"
|
|
2915
|
+
},
|
|
2916
|
+
args: {
|
|
2917
|
+
type: {
|
|
2918
|
+
type: "positional",
|
|
2919
|
+
description: "patch | minor | major | x.y.z (exact version)",
|
|
2920
|
+
required: false
|
|
2921
|
+
},
|
|
2922
|
+
path: {
|
|
2923
|
+
type: "positional",
|
|
2924
|
+
description: "Directory to scan from (defaults to current dir). Supports single plugin or marketplace root with many plugins/",
|
|
2925
|
+
required: false
|
|
2926
|
+
},
|
|
2927
|
+
only: {
|
|
2928
|
+
type: "string",
|
|
2929
|
+
description: 'Scope to "all" (default), "plugin" (only plugin.json manifests), or "marketplace" (only marketplace.json files that carry a top-level version)',
|
|
2930
|
+
default: "all"
|
|
2931
|
+
}
|
|
2932
|
+
},
|
|
2933
|
+
run({ args }) {
|
|
2934
|
+
let rawType = args.type || "patch";
|
|
2935
|
+
let targetPath = args.path || ".";
|
|
2936
|
+
const scopeInput = (args.only || "all").toLowerCase();
|
|
2937
|
+
const scope = scopeInput === "plugin" || scopeInput === "marketplace" ? scopeInput : "all";
|
|
2938
|
+
if (!["all", "plugin", "marketplace"].includes(scopeInput)) {
|
|
2939
|
+
ui.fail(`Invalid --only "${args.only}". Allowed: all, plugin, marketplace.`);
|
|
2940
|
+
process.exit(1);
|
|
2941
|
+
}
|
|
2942
|
+
const isKnownType = ["patch", "minor", "major"].includes(rawType) || /^\d+\.\d+\.\d+$/.test(rawType);
|
|
2943
|
+
const maybePath = resolve3(rawType);
|
|
2944
|
+
const looksLikeDir = existsSync10(maybePath) || rawType === "." || rawType.startsWith("./") || rawType.startsWith("../");
|
|
2945
|
+
if (!isKnownType && looksLikeDir) {
|
|
2946
|
+
targetPath = rawType;
|
|
2947
|
+
rawType = "patch";
|
|
2948
|
+
} else if (!isKnownType) {
|
|
2949
|
+
ui.fail(`Unknown bump type "${rawType}". Use patch | minor | major | 1.2.3`);
|
|
2950
|
+
process.exit(1);
|
|
2951
|
+
}
|
|
2952
|
+
const root = resolve3(targetPath);
|
|
2953
|
+
if (!existsSync10(root)) {
|
|
2954
|
+
ui.fail(`Path does not exist: ${root}`);
|
|
2955
|
+
process.exit(1);
|
|
2956
|
+
}
|
|
2957
|
+
ui.heading("doraval bump");
|
|
2958
|
+
ui.info(` scanning: ${root}`);
|
|
2959
|
+
ui.info(` scope: ${scope} (use --only plugin or --only marketplace to narrow; Cursor metadata.version supported)`);
|
|
2960
|
+
const discovered = walkForTargets(root);
|
|
2961
|
+
let targets = discovered;
|
|
2962
|
+
if (scope === "plugin") {
|
|
2963
|
+
targets = discovered.filter((t) => t.kind === "plugin");
|
|
2964
|
+
} else if (scope === "marketplace") {
|
|
2965
|
+
targets = discovered.filter((t) => t.kind === "marketplace");
|
|
2966
|
+
}
|
|
2967
|
+
if (targets.length === 0) {
|
|
2968
|
+
ui.fail("No matching files found under the scope.");
|
|
2969
|
+
ui.info("");
|
|
2970
|
+
ui.info(" Looked for (recursively):");
|
|
2971
|
+
ui.info(" \u2022 **/.claude-plugin/plugin.json");
|
|
2972
|
+
ui.info(" \u2022 **/.codex-plugin/plugin.json");
|
|
2973
|
+
ui.info(" \u2022 **/.cursor-plugin/plugin.json (or marketplace.json)");
|
|
2974
|
+
ui.info(" \u2022 **/marketplace.json (top-level version or metadata.version for Cursor)");
|
|
2975
|
+
ui.info("");
|
|
2976
|
+
ui.info(" Tip: run from inside a plugin directory, or pass a path that contains plugins/.");
|
|
2977
|
+
ui.info(" Examples:");
|
|
2978
|
+
ui.info(" dora bump minor");
|
|
2979
|
+
ui.info(" dora bump minor ./my-claude-plugin");
|
|
2980
|
+
ui.info(" dora bump --only plugin . # only the manifests");
|
|
2981
|
+
ui.info(" dora bump --only marketplace ./marketplaces-root # includes Cursor metadata.version");
|
|
2982
|
+
process.exit(1);
|
|
2983
|
+
}
|
|
2984
|
+
ui.info(` matched ${targets.length} file(s)`);
|
|
2985
|
+
let bumpedCount = 0;
|
|
2986
|
+
for (const t of targets) {
|
|
2987
|
+
const json = readJson(t.file);
|
|
2988
|
+
if (!json || typeof json !== "object") {
|
|
2989
|
+
ui.warnItem(`skipped (invalid JSON): ${relative(root, t.file)}`);
|
|
2990
|
+
continue;
|
|
2991
|
+
}
|
|
2992
|
+
const current = getVersion(json);
|
|
2993
|
+
let next;
|
|
2994
|
+
try {
|
|
2995
|
+
next = bumpVersion(current, rawType);
|
|
2996
|
+
} catch (err) {
|
|
2997
|
+
ui.fail(err.message || String(err));
|
|
2998
|
+
process.exit(1);
|
|
2999
|
+
}
|
|
3000
|
+
const relPath = relative(root, t.file);
|
|
3001
|
+
if (current === next) {
|
|
3002
|
+
ui.dim(` \u2022 ${t.label} ${current || "(no version)"} (no change) [${relPath}]`);
|
|
3003
|
+
continue;
|
|
3004
|
+
}
|
|
3005
|
+
const didUpdate = setVersion(json, next);
|
|
3006
|
+
if (!didUpdate) {
|
|
3007
|
+
ui.warnItem(`skipped (could not locate version field to update): ${relPath}`);
|
|
3008
|
+
continue;
|
|
3009
|
+
}
|
|
3010
|
+
writeJson(t.file, json);
|
|
3011
|
+
ui.success(`${t.label}: ${import_picocolors11.default.dim(current || "(none)")} \u2192 ${import_picocolors11.default.green(next)}`);
|
|
3012
|
+
ui.info(` ${relPath}`);
|
|
3013
|
+
bumpedCount++;
|
|
3014
|
+
}
|
|
3015
|
+
ui.blank();
|
|
3016
|
+
if (bumpedCount === 0) {
|
|
3017
|
+
ui.info("All matched files were already at the target version.");
|
|
3018
|
+
} else {
|
|
3019
|
+
ui.info(`Done. Bumped ${bumpedCount} file(s).`);
|
|
3020
|
+
ui.dim(" Next: doraval validate " + (targetPath === "." ? "." : targetPath));
|
|
3021
|
+
}
|
|
3022
|
+
process.exit(0);
|
|
3023
|
+
}
|
|
3024
|
+
});
|
|
3025
|
+
});
|
|
3026
|
+
|
|
3027
|
+
// src/cli/commands/codex/context.ts
|
|
3028
|
+
import { existsSync as existsSync11, readdirSync as readdirSync5 } from "fs";
|
|
3029
|
+
import { join as join10 } from "path";
|
|
3030
|
+
function detectContext2(cwd = process.cwd()) {
|
|
3031
|
+
const hasCodexDir = existsSync11(join10(cwd, ".codex"));
|
|
3032
|
+
const hasPluginManifest = existsSync11(join10(cwd, ".codex-plugin", "plugin.json"));
|
|
3033
|
+
const hasMarketplace = existsSync11(join10(cwd, ".agents", "plugins", "marketplace.json")) || existsSync11(join10(cwd, ".codex-plugin", "marketplace.json"));
|
|
3034
|
+
let looseSkillFiles = [];
|
|
3035
|
+
try {
|
|
3036
|
+
const files = readdirSync5(cwd);
|
|
3037
|
+
looseSkillFiles = files.filter((f) => {
|
|
3038
|
+
if (!f.endsWith(".md") || f.startsWith("."))
|
|
3039
|
+
return false;
|
|
3040
|
+
const lower = f.toLowerCase();
|
|
3041
|
+
if (lower === "readme.md" || lower === "changelog.md" || lower === "license.md" || lower.includes("contributing"))
|
|
3042
|
+
return false;
|
|
3043
|
+
return lower.includes("skill") || lower === "skill.md";
|
|
3044
|
+
});
|
|
3045
|
+
} catch {}
|
|
3046
|
+
const isEmpty = !hasPluginManifest && looseSkillFiles.length === 0;
|
|
3047
|
+
return {
|
|
3048
|
+
cwd,
|
|
3049
|
+
hasCodexDir,
|
|
3050
|
+
hasPluginManifest,
|
|
3051
|
+
hasMarketplace,
|
|
3052
|
+
looseSkillFiles,
|
|
3053
|
+
isEmpty
|
|
3054
|
+
};
|
|
3055
|
+
}
|
|
3056
|
+
var init_context2 = () => {};
|
|
3057
|
+
|
|
3058
|
+
// src/cli/commands/codex/new.ts
|
|
3059
|
+
var exports_new2 = {};
|
|
3060
|
+
__export(exports_new2, {
|
|
3061
|
+
scaffold: () => scaffold2,
|
|
3062
|
+
default: () => new_default2,
|
|
3063
|
+
decidePath: () => decidePath2
|
|
3064
|
+
});
|
|
3065
|
+
import { join as join11, basename as basename3 } from "path";
|
|
3066
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, existsSync as existsSync12 } from "fs";
|
|
3067
|
+
function decidePath2(ctx, intent, providedName) {
|
|
3068
|
+
const rawName = providedName || "";
|
|
3069
|
+
let decisionPath = "standalone";
|
|
3070
|
+
let targetDir = ctx.cwd;
|
|
3071
|
+
let shouldCreateDir = false;
|
|
3072
|
+
let migrateExisting = false;
|
|
3073
|
+
const useCurrentDirAsRoot = rawName === "." || rawName === basename3(ctx.cwd) || !rawName;
|
|
3074
|
+
if (intent === "distribute" || intent === "self-later" && ctx.looseSkillFiles.length > 0 && !ctx.hasPluginManifest) {
|
|
3075
|
+
decisionPath = "plugin";
|
|
3076
|
+
if (useCurrentDirAsRoot) {
|
|
3077
|
+
targetDir = ctx.cwd;
|
|
3078
|
+
shouldCreateDir = false;
|
|
3079
|
+
} else {
|
|
3080
|
+
targetDir = join11(ctx.cwd, rawName);
|
|
3081
|
+
shouldCreateDir = true;
|
|
3082
|
+
}
|
|
3083
|
+
migrateExisting = ctx.looseSkillFiles.length > 0;
|
|
3084
|
+
} else if (intent === "self-later" && !ctx.hasPluginManifest) {
|
|
3085
|
+
decisionPath = "plugin";
|
|
3086
|
+
if (useCurrentDirAsRoot) {
|
|
3087
|
+
targetDir = ctx.cwd;
|
|
3088
|
+
shouldCreateDir = false;
|
|
3089
|
+
} else {
|
|
3090
|
+
targetDir = join11(ctx.cwd, rawName);
|
|
3091
|
+
shouldCreateDir = true;
|
|
3092
|
+
}
|
|
3093
|
+
} else if (decisionPath === "standalone") {
|
|
3094
|
+
if (useCurrentDirAsRoot) {
|
|
3095
|
+
targetDir = ctx.cwd;
|
|
3096
|
+
shouldCreateDir = false;
|
|
3097
|
+
} else {
|
|
3098
|
+
targetDir = join11(ctx.cwd, rawName);
|
|
3099
|
+
shouldCreateDir = true;
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
return { path: decisionPath, targetDir, shouldCreateDir, migrateExisting };
|
|
3103
|
+
}
|
|
3104
|
+
function scaffold2(decision, ctx, migrateContent) {
|
|
3105
|
+
const { targetDir, path, shouldCreateDir } = decision;
|
|
3106
|
+
if (existsSync12(targetDir) && shouldCreateDir) {
|
|
3107
|
+
ui.fail("Target already exists");
|
|
3108
|
+
process.exit(1);
|
|
3109
|
+
}
|
|
3110
|
+
if (shouldCreateDir) {
|
|
3111
|
+
mkdirSync3(targetDir, { recursive: true });
|
|
3112
|
+
}
|
|
3113
|
+
if (path === "plugin") {
|
|
3114
|
+
const pluginName = basename3(targetDir);
|
|
3115
|
+
const pluginJson = {
|
|
3116
|
+
name: pluginName,
|
|
3117
|
+
version: "0.1.0",
|
|
3118
|
+
description: "Scaffolded by doraval codex new",
|
|
3119
|
+
skills: "./skills/",
|
|
3120
|
+
interface: {
|
|
3121
|
+
displayName: pluginName,
|
|
3122
|
+
shortDescription: "Scaffolded starter plugin",
|
|
3123
|
+
category: "Productivity"
|
|
3124
|
+
}
|
|
3125
|
+
};
|
|
3126
|
+
mkdirSync3(join11(targetDir, ".codex-plugin"), { recursive: true });
|
|
3127
|
+
writeFileSync3(join11(targetDir, ".codex-plugin", "plugin.json"), JSON.stringify(pluginJson, null, 2));
|
|
3128
|
+
mkdirSync3(join11(targetDir, ".agents", "plugins"), { recursive: true });
|
|
3129
|
+
const marketplaceJson = {
|
|
3130
|
+
name: "local",
|
|
3131
|
+
interface: {
|
|
3132
|
+
displayName: "Local (doraval scaffold)"
|
|
3133
|
+
},
|
|
3134
|
+
plugins: [
|
|
3135
|
+
{
|
|
3136
|
+
name: pluginName,
|
|
3137
|
+
source: {
|
|
3138
|
+
source: "local",
|
|
3139
|
+
path: "../.."
|
|
3140
|
+
},
|
|
3141
|
+
policy: {
|
|
3142
|
+
installation: "AVAILABLE",
|
|
3143
|
+
authentication: "ON_INSTALL"
|
|
3144
|
+
},
|
|
3145
|
+
category: "Productivity"
|
|
3146
|
+
}
|
|
3147
|
+
]
|
|
3148
|
+
};
|
|
3149
|
+
writeFileSync3(join11(targetDir, ".agents", "plugins", "marketplace.json"), JSON.stringify(marketplaceJson, null, 2));
|
|
3150
|
+
const demoSkillName = "doraval";
|
|
3151
|
+
mkdirSync3(join11(targetDir, "skills", demoSkillName), { recursive: true });
|
|
3152
|
+
let skillContent;
|
|
3153
|
+
if (migrateContent) {
|
|
3154
|
+
skillContent = migrateContent;
|
|
3155
|
+
} else {
|
|
3156
|
+
skillContent = `---
|
|
3157
|
+
name: ${demoSkillName}
|
|
3158
|
+
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 Codex too).
|
|
3159
|
+
---
|
|
3160
|
+
|
|
3161
|
+
# Use Doraval (Codex edition)
|
|
3162
|
+
|
|
3163
|
+
Doraval is the context engineering toolkit.
|
|
3164
|
+
|
|
3165
|
+
When you need to check a skill or Codex plugin:
|
|
3166
|
+
|
|
3167
|
+
- Validate the current directory: \`doraval validate .\`
|
|
3168
|
+
- Validate one skill: \`doraval skill validate ./skills/${demoSkillName}/\`
|
|
3169
|
+
- Check for rubric drift: \`doraval skill drift ./skills/${demoSkillName}/\`
|
|
3170
|
+
- Get an AI quality judgment: \`doraval skill judge ./skills/${demoSkillName}/\`
|
|
3171
|
+
|
|
3172
|
+
Always run \`doraval validate\` before sharing or publishing a plugin.
|
|
3173
|
+
|
|
3174
|
+
This skill demonstrates a complete, self-referential example of using doraval inside a generated Codex plugin.
|
|
3175
|
+
|
|
3176
|
+
To test in Codex:
|
|
3177
|
+
1. Make sure this plugin is listed in a marketplace (we created .agents/plugins/marketplace.json for you).
|
|
3178
|
+
2. Restart Codex.
|
|
3179
|
+
3. Open the plugin directory, select your local marketplace, and enable the plugin.
|
|
3180
|
+
4. Invoke the demo with /${pluginName}:doraval`;
|
|
3181
|
+
}
|
|
3182
|
+
writeFileSync3(join11(targetDir, "skills", demoSkillName, "SKILL.md"), skillContent);
|
|
3183
|
+
const readmePath = join11(targetDir, "README.md");
|
|
3184
|
+
if (!existsSync12(readmePath)) {
|
|
3185
|
+
writeFileSync3(readmePath, "# " + pluginName + `
|
|
3186
|
+
|
|
3187
|
+
Codex plugin scaffolded by doraval.`);
|
|
3188
|
+
}
|
|
3189
|
+
} else {
|
|
3190
|
+
mkdirSync3(join11(targetDir, "skills", "doraval"), { recursive: true });
|
|
3191
|
+
const skillBody = migrateContent || `# My Skill
|
|
3192
|
+
|
|
3193
|
+
Basic starter for Codex.`;
|
|
3194
|
+
writeFileSync3(join11(targetDir, "skills", "doraval", "SKILL.md"), `---
|
|
3195
|
+
name: doraval
|
|
3196
|
+
description: Starter (local skill)
|
|
3197
|
+
---
|
|
3198
|
+
|
|
3199
|
+
${skillBody}`);
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
var import_picocolors12, new_default2;
|
|
3203
|
+
var init_new2 = __esm(() => {
|
|
3204
|
+
init_dist();
|
|
3205
|
+
init_out();
|
|
3206
|
+
init_context2();
|
|
3207
|
+
init_prompt();
|
|
3208
|
+
import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
3209
|
+
new_default2 = defineCommand({
|
|
3210
|
+
meta: {
|
|
3211
|
+
name: "new",
|
|
3212
|
+
description: "Create a new skill or plugin following Codex packaging rules"
|
|
3213
|
+
},
|
|
3214
|
+
args: {
|
|
3215
|
+
name: {
|
|
3216
|
+
type: "positional",
|
|
3217
|
+
description: "Optional name for the skill or plugin",
|
|
3218
|
+
required: false
|
|
3219
|
+
},
|
|
3220
|
+
yes: {
|
|
3221
|
+
type: "boolean",
|
|
3222
|
+
description: "Skip interactive prompts (use defaults and flags)",
|
|
3223
|
+
default: false
|
|
3224
|
+
},
|
|
3225
|
+
intent: {
|
|
3226
|
+
type: "string",
|
|
3227
|
+
description: 'Intent: "self" | "self-later" | "distribute"',
|
|
3228
|
+
required: false
|
|
3229
|
+
}
|
|
3230
|
+
},
|
|
3231
|
+
run({ args }) {
|
|
3232
|
+
ui.heading("doraval codex new \u2014 Context-aware scaffolding");
|
|
3233
|
+
const ctx = detectContext2();
|
|
3234
|
+
let intent = args.intent || "self-later";
|
|
3235
|
+
if (!args.yes) {
|
|
3236
|
+
const ans = prompt(" Intent (self | self-later | distribute)", intent);
|
|
3237
|
+
intent = ans || intent;
|
|
3238
|
+
}
|
|
3239
|
+
const decision = decidePath2(ctx, intent, args.name);
|
|
3240
|
+
ui.info(` Decision: path=${decision.path}, target=${decision.targetDir}`);
|
|
3241
|
+
let migrateContent;
|
|
3242
|
+
if (decision.migrateExisting && !args.yes) {
|
|
3243
|
+
migrateContent = "Content from your existing SKILL.md (user-confirmed).";
|
|
3244
|
+
}
|
|
3245
|
+
scaffold2(decision, ctx, migrateContent);
|
|
3246
|
+
ui.write(`
|
|
3247
|
+
${import_picocolors12.default.green("\u2713")} Created ${decision.path} at ${import_picocolors12.default.bold(decision.targetDir)}`);
|
|
3248
|
+
const cmdName = decision.path === "plugin" ? `/${basename3(decision.targetDir)}:doraval` : "/doraval (local skill)";
|
|
3249
|
+
ui.info(` Command: ${cmdName}`);
|
|
3250
|
+
if (decision.path === "plugin") {
|
|
3251
|
+
ui.info(` Codex manifest: .codex-plugin/plugin.json`);
|
|
3252
|
+
ui.info(` Marketplace catalog: .agents/plugins/marketplace.json (starter for local testing)`);
|
|
3253
|
+
ui.info(` (Move/expand the marketplace.json to $REPO_ROOT/.agents/plugins/ or ~/.agents/plugins/ as needed)`);
|
|
3254
|
+
}
|
|
3255
|
+
ui.info(` Test (local): restart Codex, select your marketplace in the plugin directory`);
|
|
3256
|
+
ui.info(` Validate: doraval validate ${decision.targetDir}`);
|
|
3257
|
+
if (decision.path === "plugin" && decision.migrateExisting) {
|
|
3258
|
+
ui.info(" (Existing content migrated where confirmed.)");
|
|
3259
|
+
}
|
|
3260
|
+
process.exit(0);
|
|
3261
|
+
}
|
|
3262
|
+
});
|
|
3263
|
+
});
|
|
3264
|
+
|
|
2766
3265
|
// src/validators/claude/skill.ts
|
|
2767
|
-
import { existsSync as
|
|
2768
|
-
import { resolve as
|
|
3266
|
+
import { existsSync as existsSync13 } from "fs";
|
|
3267
|
+
import { resolve as resolve4 } from "path";
|
|
2769
3268
|
var OPTIONAL_DIRS2, claudeSkillValidator;
|
|
2770
3269
|
var init_skill = __esm(() => {
|
|
2771
3270
|
init_frontmatter();
|
|
@@ -2777,10 +3276,10 @@ var init_skill = __esm(() => {
|
|
|
2777
3276
|
name: "Claude Skill",
|
|
2778
3277
|
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.)",
|
|
2779
3278
|
detect(dir) {
|
|
2780
|
-
return
|
|
3279
|
+
return existsSync13(resolve4(dir, "SKILL.md"));
|
|
2781
3280
|
},
|
|
2782
3281
|
async validate(dir, _opts) {
|
|
2783
|
-
const skillMd =
|
|
3282
|
+
const skillMd = resolve4(dir, "SKILL.md");
|
|
2784
3283
|
const raw = await Bun.file(skillMd).text();
|
|
2785
3284
|
let parsed;
|
|
2786
3285
|
try {
|
|
@@ -2792,32 +3291,108 @@ var init_skill = __esm(() => {
|
|
|
2792
3291
|
passes: []
|
|
2793
3292
|
};
|
|
2794
3293
|
}
|
|
2795
|
-
const existingDirs = OPTIONAL_DIRS2.filter((d) =>
|
|
3294
|
+
const existingDirs = OPTIONAL_DIRS2.filter((d) => existsSync13(resolve4(dir, d)));
|
|
2796
3295
|
return validateSkillModel(parsed, { existingDirs: [...existingDirs] });
|
|
2797
3296
|
}
|
|
2798
3297
|
};
|
|
2799
3298
|
});
|
|
2800
3299
|
|
|
2801
3300
|
// src/validators/claude/plugin.ts
|
|
2802
|
-
import { existsSync as
|
|
2803
|
-
import { resolve as
|
|
2804
|
-
|
|
3301
|
+
import { existsSync as existsSync14, readdirSync as readdirSync6 } from "fs";
|
|
3302
|
+
import { resolve as resolve5, join as join12 } from "path";
|
|
3303
|
+
function levenshtein(a, b) {
|
|
3304
|
+
if (a === b)
|
|
3305
|
+
return 0;
|
|
3306
|
+
const m = a.length, n = b.length;
|
|
3307
|
+
if (m === 0)
|
|
3308
|
+
return n;
|
|
3309
|
+
if (n === 0)
|
|
3310
|
+
return m;
|
|
3311
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
3312
|
+
for (let i = 0;i <= m; i++)
|
|
3313
|
+
dp[i][0] = i;
|
|
3314
|
+
for (let j = 0;j <= n; j++)
|
|
3315
|
+
dp[0][j] = j;
|
|
3316
|
+
for (let i = 1;i <= m; i++) {
|
|
3317
|
+
for (let j = 1;j <= n; j++) {
|
|
3318
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
3319
|
+
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
return dp[m][n];
|
|
3323
|
+
}
|
|
3324
|
+
function suggestField(unknown) {
|
|
3325
|
+
const lower = unknown.toLowerCase();
|
|
3326
|
+
for (const k of KNOWN_FIELDS2) {
|
|
3327
|
+
if (k.toLowerCase() === lower)
|
|
3328
|
+
return k;
|
|
3329
|
+
if (levenshtein(k.toLowerCase(), lower) <= 1)
|
|
3330
|
+
return k;
|
|
3331
|
+
if (k.toLowerCase().startsWith(lower.slice(0, 3)) && lower.length > 3)
|
|
3332
|
+
return k;
|
|
3333
|
+
}
|
|
3334
|
+
if (lower === "licence")
|
|
3335
|
+
return "license";
|
|
3336
|
+
if (lower === "dependancies" || lower === "deps")
|
|
3337
|
+
return "dependencies";
|
|
3338
|
+
if (lower === "mcp" || lower === "mcpservers")
|
|
3339
|
+
return "mcpServers";
|
|
3340
|
+
if (lower === "lsp")
|
|
3341
|
+
return "lspServers";
|
|
3342
|
+
if (lower === "outputstyles" || lower === "styles")
|
|
3343
|
+
return "outputStyles";
|
|
3344
|
+
if (lower === "userconfig")
|
|
3345
|
+
return "userConfig";
|
|
3346
|
+
return null;
|
|
3347
|
+
}
|
|
3348
|
+
function isRelativePathLike(v) {
|
|
3349
|
+
if (typeof v !== "string")
|
|
3350
|
+
return false;
|
|
3351
|
+
return RELATIVE_PATH_REGEX.test(v) && !v.includes("..");
|
|
3352
|
+
}
|
|
3353
|
+
var NAME_REGEX2, RELATIVE_PATH_REGEX, KNOWN_FIELDS2, REPLACES_DEFAULT, claudePluginValidator;
|
|
2805
3354
|
var init_plugin = __esm(() => {
|
|
2806
3355
|
NAME_REGEX2 = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
2807
3356
|
RELATIVE_PATH_REGEX = /^\.\//;
|
|
3357
|
+
KNOWN_FIELDS2 = new Set([
|
|
3358
|
+
"$schema",
|
|
3359
|
+
"name",
|
|
3360
|
+
"displayName",
|
|
3361
|
+
"version",
|
|
3362
|
+
"description",
|
|
3363
|
+
"author",
|
|
3364
|
+
"homepage",
|
|
3365
|
+
"repository",
|
|
3366
|
+
"license",
|
|
3367
|
+
"keywords",
|
|
3368
|
+
"defaultEnabled",
|
|
3369
|
+
"skills",
|
|
3370
|
+
"commands",
|
|
3371
|
+
"agents",
|
|
3372
|
+
"hooks",
|
|
3373
|
+
"mcpServers",
|
|
3374
|
+
"outputStyles",
|
|
3375
|
+
"lspServers",
|
|
3376
|
+
"experimental",
|
|
3377
|
+
"userConfig",
|
|
3378
|
+
"channels",
|
|
3379
|
+
"dependencies"
|
|
3380
|
+
]);
|
|
3381
|
+
REPLACES_DEFAULT = new Set(["commands", "agents", "outputStyles", "lspServers"]);
|
|
2808
3382
|
claudePluginValidator = {
|
|
2809
3383
|
id: "claude:plugin",
|
|
2810
3384
|
provider: "claude",
|
|
2811
3385
|
name: "Claude Plugin",
|
|
2812
|
-
description: "Validates .claude-plugin/plugin.json manifest, component
|
|
3386
|
+
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",
|
|
2813
3387
|
detect(dir) {
|
|
2814
|
-
return
|
|
3388
|
+
return existsSync14(resolve5(dir, ".claude-plugin", "plugin.json"));
|
|
2815
3389
|
},
|
|
2816
3390
|
async validate(dir, _opts) {
|
|
2817
3391
|
const errors = [];
|
|
2818
3392
|
const warnings = [];
|
|
2819
3393
|
const passes = [];
|
|
2820
|
-
const manifestPath =
|
|
3394
|
+
const manifestPath = resolve5(dir, ".claude-plugin", "plugin.json");
|
|
3395
|
+
const dotClaudePluginDir = resolve5(dir, ".claude-plugin");
|
|
2821
3396
|
let manifest;
|
|
2822
3397
|
try {
|
|
2823
3398
|
const raw = await Bun.file(manifestPath).text();
|
|
@@ -2827,6 +3402,17 @@ var init_plugin = __esm(() => {
|
|
|
2827
3402
|
errors.push(".claude-plugin/plugin.json is missing or invalid JSON");
|
|
2828
3403
|
return { errors, warnings, passes };
|
|
2829
3404
|
}
|
|
3405
|
+
try {
|
|
3406
|
+
const entries = readdirSync6(dotClaudePluginDir);
|
|
3407
|
+
const unexpected = entries.filter((e) => e !== "plugin.json");
|
|
3408
|
+
if (unexpected.length > 0) {
|
|
3409
|
+
for (const e of unexpected) {
|
|
3410
|
+
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.`);
|
|
3411
|
+
}
|
|
3412
|
+
} else if (entries.length === 1) {
|
|
3413
|
+
passes.push(".claude-plugin/ contains only plugin.json (correct layout)");
|
|
3414
|
+
}
|
|
3415
|
+
} catch {}
|
|
2830
3416
|
if (!manifest.name) {
|
|
2831
3417
|
errors.push('Missing required field: "name"');
|
|
2832
3418
|
} else {
|
|
@@ -2840,10 +3426,12 @@ var init_plugin = __esm(() => {
|
|
|
2840
3426
|
if (manifest.version !== undefined) {
|
|
2841
3427
|
const v = String(manifest.version);
|
|
2842
3428
|
if (!/^\d+\.\d+\.\d+/.test(v)) {
|
|
2843
|
-
errors.push(`Invalid version format: "${v}" \u2014 must
|
|
3429
|
+
errors.push(`Invalid version format: "${v}" \u2014 must look like semver (MAJOR.MINOR.PATCH) when using explicit versioning`);
|
|
2844
3430
|
} else {
|
|
2845
|
-
passes.push(`version: "${v}"`);
|
|
3431
|
+
passes.push(`version: "${v}" (explicit \u2014 bump on every release to publish updates)`);
|
|
2846
3432
|
}
|
|
3433
|
+
} else {
|
|
3434
|
+
passes.push("version omitted (git commit SHA used as version key \u2014 every commit becomes an available update)");
|
|
2847
3435
|
}
|
|
2848
3436
|
if (manifest.description !== undefined) {
|
|
2849
3437
|
const desc = String(manifest.description);
|
|
@@ -2852,56 +3440,163 @@ var init_plugin = __esm(() => {
|
|
|
2852
3440
|
} else {
|
|
2853
3441
|
passes.push("description field present");
|
|
2854
3442
|
}
|
|
3443
|
+
} else {
|
|
3444
|
+
warnings.push('Missing "description" (recommended for UI, marketplace listings, and auto-discovery)');
|
|
3445
|
+
}
|
|
3446
|
+
if (manifest.displayName !== undefined) {
|
|
3447
|
+
passes.push(`displayName: "${manifest.displayName}" (human UI label; falls back to name)`);
|
|
3448
|
+
}
|
|
3449
|
+
if (manifest.author !== undefined) {
|
|
3450
|
+
const a = manifest.author;
|
|
3451
|
+
if (a && typeof a === "object" && a.name) {
|
|
3452
|
+
passes.push("author present");
|
|
3453
|
+
} else {
|
|
3454
|
+
warnings.push('author should be an object like {"name": "...", "email?": "..."}');
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
if (manifest.license !== undefined) {
|
|
3458
|
+
passes.push(`license: "${manifest.license}"`);
|
|
3459
|
+
}
|
|
3460
|
+
if (manifest.keywords !== undefined) {
|
|
3461
|
+
if (Array.isArray(manifest.keywords)) {
|
|
3462
|
+
passes.push(`keywords: [${manifest.keywords.join(", ")}]`);
|
|
3463
|
+
} else {
|
|
3464
|
+
errors.push("keywords must be an array of strings");
|
|
3465
|
+
}
|
|
2855
3466
|
}
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
3467
|
+
if (manifest.defaultEnabled !== undefined) {
|
|
3468
|
+
passes.push(`defaultEnabled: ${manifest.defaultEnabled}`);
|
|
3469
|
+
}
|
|
3470
|
+
if (manifest.homepage)
|
|
3471
|
+
passes.push("homepage present");
|
|
3472
|
+
if (manifest.repository)
|
|
3473
|
+
passes.push("repository present");
|
|
3474
|
+
const unknown = Object.keys(manifest).filter((k) => !KNOWN_FIELDS2.has(k));
|
|
3475
|
+
for (const k of unknown) {
|
|
3476
|
+
const sug = suggestField(k);
|
|
3477
|
+
const hint = sug ? ` (did you mean "${sug}"?)` : "";
|
|
3478
|
+
warnings.push(`Unrecognized top-level field "${k}"${hint} \u2014 will be ignored at runtime (allowed for cross-tool manifest compatibility).`);
|
|
3479
|
+
}
|
|
3480
|
+
const handleField = (field, val) => {
|
|
3481
|
+
if (val === undefined || val === null)
|
|
3482
|
+
return;
|
|
3483
|
+
if (isRelativePathLike(val) || Array.isArray(val) && val.every(isRelativePathLike)) {
|
|
3484
|
+
const arr = Array.isArray(val) ? val : [val];
|
|
3485
|
+
for (const p of arr) {
|
|
3486
|
+
const s = String(p);
|
|
3487
|
+
if (!RELATIVE_PATH_REGEX.test(s)) {
|
|
3488
|
+
errors.push(`${field}: path "${s}" must start with "./"`);
|
|
3489
|
+
} else if (s.includes("..")) {
|
|
3490
|
+
errors.push(`${field}: path "${s}" must not use ".." (paths are confined to the plugin tree after cache copy)`);
|
|
3491
|
+
} else if (existsSync14(resolve5(dir, s))) {
|
|
3492
|
+
passes.push(`${field}: path "${s}" exists`);
|
|
3493
|
+
} else {
|
|
3494
|
+
warnings.push(`${field}: path "${s}" does not exist on disk`);
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
if (field === "skills") {
|
|
3498
|
+
passes.push(`${field}: augments the default skills/ (both are scanned)`);
|
|
3499
|
+
} else if (REPLACES_DEFAULT.has(field)) {
|
|
3500
|
+
passes.push(`${field}: custom path replaces default ${field}/ scan`);
|
|
2866
3501
|
} else {
|
|
2867
|
-
|
|
3502
|
+
passes.push(`${field}: custom path or config (merge rules apply)`);
|
|
2868
3503
|
}
|
|
3504
|
+
} else if (typeof val === "object") {
|
|
3505
|
+
passes.push(`${field}: inline ${field} config present`);
|
|
2869
3506
|
}
|
|
2870
3507
|
};
|
|
2871
|
-
|
|
2872
|
-
if (manifest[
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
3508
|
+
["skills", "commands", "agents", "hooks", "mcpServers", "outputStyles", "lspServers"].forEach((f) => {
|
|
3509
|
+
if (manifest[f] !== undefined)
|
|
3510
|
+
handleField(f, manifest[f]);
|
|
3511
|
+
});
|
|
3512
|
+
if (manifest.experimental && typeof manifest.experimental === "object") {
|
|
3513
|
+
const exp = manifest.experimental;
|
|
3514
|
+
if (exp.themes !== undefined)
|
|
3515
|
+
handleField("experimental.themes", exp.themes);
|
|
3516
|
+
if (exp.monitors !== undefined)
|
|
3517
|
+
handleField("experimental.monitors", exp.monitors);
|
|
3518
|
+
passes.push("experimental section present (themes and monitors are experimental components)");
|
|
3519
|
+
}
|
|
3520
|
+
if (manifest.userConfig && typeof manifest.userConfig === "object") {
|
|
3521
|
+
const keys = Object.keys(manifest.userConfig);
|
|
3522
|
+
passes.push(`userConfig: ${keys.length} user-configurable value(s) declared`);
|
|
3523
|
+
for (const k of keys) {
|
|
3524
|
+
const opt = manifest.userConfig[k];
|
|
3525
|
+
if (!opt || !opt.type || !opt.title) {
|
|
3526
|
+
warnings.push(`userConfig.${k} is missing required "type" and/or "title"`);
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
if (Array.isArray(manifest.channels)) {
|
|
3531
|
+
passes.push(`channels: ${manifest.channels.length} channel(s) (each binds to an mcpServer)`);
|
|
3532
|
+
manifest.channels.forEach((ch, i) => {
|
|
3533
|
+
if (!ch?.server)
|
|
3534
|
+
warnings.push(`channels[${i}]: "server" is required and must match an mcpServers key`);
|
|
3535
|
+
});
|
|
3536
|
+
}
|
|
3537
|
+
if (Array.isArray(manifest.dependencies)) {
|
|
3538
|
+
passes.push(`dependencies: declares ${manifest.dependencies.length} plugin dependency/ies`);
|
|
3539
|
+
}
|
|
3540
|
+
const skillsDir = resolve5(dir, "skills");
|
|
3541
|
+
if (existsSync14(skillsDir)) {
|
|
3542
|
+
const entries = readdirSync6(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
3543
|
+
for (const e of entries) {
|
|
3544
|
+
const md = join12(skillsDir, e.name, "SKILL.md");
|
|
3545
|
+
if (existsSync14(md)) {
|
|
3546
|
+
passes.push(`skills/${e.name}/SKILL.md exists`);
|
|
2883
3547
|
} else {
|
|
2884
|
-
errors.push(`skills/${
|
|
3548
|
+
errors.push(`skills/${e.name}/ is missing SKILL.md`);
|
|
2885
3549
|
}
|
|
2886
3550
|
}
|
|
3551
|
+
if (manifest.skills !== undefined) {
|
|
3552
|
+
warnings.push('Default skills/ dir co-exists with manifest "skills" \u2014 manifest path is authoritative; default folder ignored for loading');
|
|
3553
|
+
}
|
|
2887
3554
|
}
|
|
2888
|
-
const commandsDir =
|
|
2889
|
-
if (
|
|
2890
|
-
const
|
|
2891
|
-
if (
|
|
2892
|
-
passes.push(`commands/ has ${
|
|
2893
|
-
}
|
|
2894
|
-
|
|
3555
|
+
const commandsDir = resolve5(dir, "commands");
|
|
3556
|
+
if (existsSync14(commandsDir)) {
|
|
3557
|
+
const mds = readdirSync6(commandsDir).filter((f) => f.endsWith(".md"));
|
|
3558
|
+
if (mds.length) {
|
|
3559
|
+
passes.push(`commands/ has ${mds.length} .md file(s)`);
|
|
3560
|
+
}
|
|
3561
|
+
if (manifest.commands !== undefined) {
|
|
3562
|
+
warnings.push('commands/ co-exists with manifest "commands" \u2014 manifest replaces default (dir ignored)');
|
|
2895
3563
|
}
|
|
2896
3564
|
}
|
|
2897
|
-
const agentsDir =
|
|
2898
|
-
if (
|
|
2899
|
-
const
|
|
2900
|
-
if (
|
|
2901
|
-
passes.push(`agents/ has ${
|
|
2902
|
-
} else {
|
|
2903
|
-
warnings.push("agents/ directory exists but has no .md files");
|
|
3565
|
+
const agentsDir = resolve5(dir, "agents");
|
|
3566
|
+
if (existsSync14(agentsDir)) {
|
|
3567
|
+
const mds = readdirSync6(agentsDir).filter((f) => f.endsWith(".md"));
|
|
3568
|
+
if (mds.length) {
|
|
3569
|
+
passes.push(`agents/ has ${mds.length} .md file(s)`);
|
|
2904
3570
|
}
|
|
3571
|
+
if (manifest.agents !== undefined) {
|
|
3572
|
+
warnings.push('agents/ co-exists with manifest "agents" \u2014 manifest replaces default (dir ignored)');
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
if (existsSync14(resolve5(dir, "output-styles"))) {
|
|
3576
|
+
passes.push("output-styles/ directory present");
|
|
3577
|
+
if (manifest.outputStyles)
|
|
3578
|
+
warnings.push("output-styles/ co-exists with manifest outputStyles \u2014 manifest wins");
|
|
3579
|
+
}
|
|
3580
|
+
if (existsSync14(resolve5(dir, "themes")))
|
|
3581
|
+
passes.push("themes/ present (experimental)");
|
|
3582
|
+
if (existsSync14(resolve5(dir, "monitors")) || manifest.experimental?.monitors) {
|
|
3583
|
+
passes.push("monitors config present (experimental)");
|
|
3584
|
+
}
|
|
3585
|
+
if (existsSync14(resolve5(dir, "bin")))
|
|
3586
|
+
passes.push("bin/ present (adds executables to Bash tool $PATH)");
|
|
3587
|
+
if (existsSync14(resolve5(dir, "settings.json")))
|
|
3588
|
+
passes.push("settings.json present (plugin defaults for agent/statusline)");
|
|
3589
|
+
if (existsSync14(resolve5(dir, "README.md")))
|
|
3590
|
+
passes.push("README.md present");
|
|
3591
|
+
if (existsSync14(resolve5(dir, ".mcp.json")))
|
|
3592
|
+
passes.push(".mcp.json present (validated by claude:mcp)");
|
|
3593
|
+
if (existsSync14(resolve5(dir, ".lsp.json")))
|
|
3594
|
+
passes.push(".lsp.json present (validated by claude:lsp when registered)");
|
|
3595
|
+
if (existsSync14(resolve5(dir, "hooks/hooks.json")) || existsSync14(resolve5(dir, "hooks.json"))) {
|
|
3596
|
+
passes.push("hooks config present (validated by claude:hooks)");
|
|
3597
|
+
}
|
|
3598
|
+
if (existsSync14(resolve5(dir, "SKILL.md")) && !existsSync14(skillsDir) && manifest.skills === undefined) {
|
|
3599
|
+
passes.push('Root SKILL.md detected \u2014 plugin will be treated as a single-skill plugin (prefer frontmatter "name" for stable /command)');
|
|
2905
3600
|
}
|
|
2906
3601
|
return { errors, warnings, passes };
|
|
2907
3602
|
}
|
|
@@ -2909,8 +3604,8 @@ var init_plugin = __esm(() => {
|
|
|
2909
3604
|
});
|
|
2910
3605
|
|
|
2911
3606
|
// src/validators/claude/marketplace.ts
|
|
2912
|
-
import { existsSync as
|
|
2913
|
-
import { resolve as
|
|
3607
|
+
import { existsSync as existsSync15, readdirSync as readdirSync7 } from "fs";
|
|
3608
|
+
import { resolve as resolve6, join as join13 } from "path";
|
|
2914
3609
|
var claudeMarketplaceValidator;
|
|
2915
3610
|
var init_marketplace = __esm(() => {
|
|
2916
3611
|
claudeMarketplaceValidator = {
|
|
@@ -2919,16 +3614,16 @@ var init_marketplace = __esm(() => {
|
|
|
2919
3614
|
name: "Claude Plugin Marketplace",
|
|
2920
3615
|
description: "Validates marketplace structure: plugins/ directory with valid plugin subdirectories",
|
|
2921
3616
|
detect(dir) {
|
|
2922
|
-
const pluginsDir =
|
|
2923
|
-
if (!
|
|
3617
|
+
const pluginsDir = resolve6(dir, "plugins");
|
|
3618
|
+
if (!existsSync15(pluginsDir))
|
|
2924
3619
|
return false;
|
|
2925
3620
|
try {
|
|
2926
|
-
const entries =
|
|
3621
|
+
const entries = readdirSync7(pluginsDir, { withFileTypes: true });
|
|
2927
3622
|
for (const entry of entries) {
|
|
2928
3623
|
if (!entry.isDirectory())
|
|
2929
3624
|
continue;
|
|
2930
|
-
const hasSkills =
|
|
2931
|
-
const hasManifest =
|
|
3625
|
+
const hasSkills = existsSync15(join13(pluginsDir, entry.name, "skills"));
|
|
3626
|
+
const hasManifest = existsSync15(join13(pluginsDir, entry.name, ".claude-plugin", "plugin.json"));
|
|
2932
3627
|
if (hasSkills || hasManifest)
|
|
2933
3628
|
return true;
|
|
2934
3629
|
}
|
|
@@ -2939,33 +3634,33 @@ var init_marketplace = __esm(() => {
|
|
|
2939
3634
|
const errors = [];
|
|
2940
3635
|
const warnings = [];
|
|
2941
3636
|
const passes = [];
|
|
2942
|
-
const pluginsDir =
|
|
2943
|
-
if (!
|
|
3637
|
+
const pluginsDir = resolve6(dir, "plugins");
|
|
3638
|
+
if (!existsSync15(pluginsDir)) {
|
|
2944
3639
|
errors.push("Missing plugins/ directory");
|
|
2945
3640
|
return { errors, warnings, passes };
|
|
2946
3641
|
}
|
|
2947
3642
|
passes.push("plugins/ directory exists");
|
|
2948
|
-
const pluginEntries =
|
|
3643
|
+
const pluginEntries = readdirSync7(pluginsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
2949
3644
|
if (pluginEntries.length === 0) {
|
|
2950
3645
|
errors.push("plugins/ directory is empty \u2014 expected at least one plugin");
|
|
2951
3646
|
return { errors, warnings, passes };
|
|
2952
3647
|
}
|
|
2953
3648
|
passes.push(`${pluginEntries.length} plugin(s) found`);
|
|
2954
|
-
if (
|
|
3649
|
+
if (existsSync15(resolve6(dir, "README.md"))) {
|
|
2955
3650
|
passes.push("README.md exists at marketplace root");
|
|
2956
3651
|
} else {
|
|
2957
3652
|
warnings.push("No README.md at marketplace root \u2014 recommended for discoverability");
|
|
2958
3653
|
}
|
|
2959
|
-
if (
|
|
3654
|
+
if (existsSync15(resolve6(dir, "LICENSE"))) {
|
|
2960
3655
|
passes.push("LICENSE exists at marketplace root");
|
|
2961
3656
|
} else {
|
|
2962
3657
|
warnings.push("No LICENSE at marketplace root \u2014 recommended");
|
|
2963
3658
|
}
|
|
2964
3659
|
for (const plugin of pluginEntries) {
|
|
2965
|
-
const pluginPath =
|
|
2966
|
-
const hasSkills =
|
|
2967
|
-
const hasManifest =
|
|
2968
|
-
const hasReadme =
|
|
3660
|
+
const pluginPath = join13(pluginsDir, plugin.name);
|
|
3661
|
+
const hasSkills = existsSync15(join13(pluginPath, "skills"));
|
|
3662
|
+
const hasManifest = existsSync15(join13(pluginPath, ".claude-plugin", "plugin.json"));
|
|
3663
|
+
const hasReadme = existsSync15(join13(pluginPath, "README.md"));
|
|
2969
3664
|
if (hasManifest || hasSkills) {
|
|
2970
3665
|
passes.push(`Plugin "${plugin.name}" has ${hasManifest ? "manifest" : "skills/"}`);
|
|
2971
3666
|
} else {
|
|
@@ -2981,35 +3676,55 @@ var init_marketplace = __esm(() => {
|
|
|
2981
3676
|
});
|
|
2982
3677
|
|
|
2983
3678
|
// src/validators/claude/hooks.ts
|
|
2984
|
-
import { existsSync as
|
|
2985
|
-
import { resolve as
|
|
3679
|
+
import { existsSync as existsSync16 } from "fs";
|
|
3680
|
+
import { resolve as resolve7 } from "path";
|
|
2986
3681
|
var KNOWN_EVENTS, claudeHooksValidator;
|
|
2987
3682
|
var init_hooks = __esm(() => {
|
|
2988
3683
|
KNOWN_EVENTS = [
|
|
3684
|
+
"SessionStart",
|
|
3685
|
+
"Setup",
|
|
3686
|
+
"UserPromptSubmit",
|
|
3687
|
+
"UserPromptExpansion",
|
|
2989
3688
|
"PreToolUse",
|
|
3689
|
+
"PermissionRequest",
|
|
3690
|
+
"PermissionDenied",
|
|
2990
3691
|
"PostToolUse",
|
|
2991
|
-
"
|
|
3692
|
+
"PostToolUseFailure",
|
|
3693
|
+
"PostToolBatch",
|
|
3694
|
+
"Notification",
|
|
3695
|
+
"MessageDisplay",
|
|
3696
|
+
"SubagentStart",
|
|
2992
3697
|
"SubagentStop",
|
|
2993
|
-
"
|
|
2994
|
-
"
|
|
2995
|
-
"
|
|
3698
|
+
"TaskCreated",
|
|
3699
|
+
"TaskCompleted",
|
|
3700
|
+
"Stop",
|
|
3701
|
+
"StopFailure",
|
|
3702
|
+
"TeammateIdle",
|
|
3703
|
+
"InstructionsLoaded",
|
|
3704
|
+
"ConfigChange",
|
|
3705
|
+
"CwdChanged",
|
|
3706
|
+
"FileChanged",
|
|
3707
|
+
"WorktreeCreate",
|
|
3708
|
+
"WorktreeRemove",
|
|
2996
3709
|
"PreCompact",
|
|
2997
|
-
"
|
|
2998
|
-
"
|
|
3710
|
+
"PostCompact",
|
|
3711
|
+
"Elicitation",
|
|
3712
|
+
"ElicitationResult",
|
|
3713
|
+
"SessionEnd"
|
|
2999
3714
|
];
|
|
3000
3715
|
claudeHooksValidator = {
|
|
3001
3716
|
id: "claude:hooks",
|
|
3002
3717
|
provider: "claude",
|
|
3003
3718
|
name: "Claude Hooks",
|
|
3004
|
-
description: "Validates hooks/hooks.json:
|
|
3719
|
+
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)",
|
|
3005
3720
|
detect(dir) {
|
|
3006
|
-
return
|
|
3721
|
+
return existsSync16(resolve7(dir, "hooks", "hooks.json")) || existsSync16(resolve7(dir, "hooks.json"));
|
|
3007
3722
|
},
|
|
3008
3723
|
async validate(dir, _opts) {
|
|
3009
3724
|
const errors = [];
|
|
3010
3725
|
const warnings = [];
|
|
3011
3726
|
const passes = [];
|
|
3012
|
-
const hooksPath =
|
|
3727
|
+
const hooksPath = existsSync16(resolve7(dir, "hooks", "hooks.json")) ? resolve7(dir, "hooks", "hooks.json") : resolve7(dir, "hooks.json");
|
|
3013
3728
|
let config;
|
|
3014
3729
|
try {
|
|
3015
3730
|
const raw = await Bun.file(hooksPath).text();
|
|
@@ -3024,32 +3739,74 @@ var init_hooks = __esm(() => {
|
|
|
3024
3739
|
if (KNOWN_EVENTS.includes(name)) {
|
|
3025
3740
|
passes.push(`Event "${name}" is a known lifecycle event`);
|
|
3026
3741
|
} else {
|
|
3027
|
-
warnings.push(`Unknown event name: "${name}" \u2014
|
|
3742
|
+
warnings.push(`Unknown event name: "${name}" \u2014 see full list in Plugins reference (SessionStart, PreToolUse, PostToolUse, Stop, ...)`);
|
|
3028
3743
|
}
|
|
3029
3744
|
}
|
|
3745
|
+
for (const [event, groups] of Object.entries(config)) {
|
|
3746
|
+
if (!Array.isArray(groups)) {
|
|
3747
|
+
errors.push(`Event "${event}": value must be an array of hook groups`);
|
|
3748
|
+
continue;
|
|
3749
|
+
}
|
|
3750
|
+
groups.forEach((group, gi) => {
|
|
3751
|
+
if (!group || typeof group !== "object") {
|
|
3752
|
+
errors.push(`${event}[${gi}]: hook group must be an object`);
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
3755
|
+
if (group.matcher !== undefined && typeof group.matcher !== "string") {
|
|
3756
|
+
warnings.push(`${event}[${gi}]: "matcher" should be a string (e.g. "Write|Edit" or glob)`);
|
|
3757
|
+
}
|
|
3758
|
+
const hooksArr = group.hooks;
|
|
3759
|
+
if (!Array.isArray(hooksArr)) {
|
|
3760
|
+
errors.push(`${event}[${gi}]: missing or invalid "hooks" array`);
|
|
3761
|
+
return;
|
|
3762
|
+
}
|
|
3763
|
+
hooksArr.forEach((h, hi) => {
|
|
3764
|
+
if (!h || typeof h !== "object" || !h.type) {
|
|
3765
|
+
errors.push(`${event}[${gi}].hooks[${hi}]: must have "type"`);
|
|
3766
|
+
return;
|
|
3767
|
+
}
|
|
3768
|
+
const t = String(h.type);
|
|
3769
|
+
if (!["command", "http", "mcp_tool", "prompt", "agent"].includes(t)) {
|
|
3770
|
+
warnings.push(`${event}[${gi}].hooks[${hi}]: unknown type "${t}" (valid: command, http, mcp_tool, prompt, agent)`);
|
|
3771
|
+
}
|
|
3772
|
+
if (t === "command" && !h.command) {
|
|
3773
|
+
errors.push(`${event}[${gi}].hooks[${hi}]: type=command requires "command"`);
|
|
3774
|
+
}
|
|
3775
|
+
if (t === "http" && !h.url) {
|
|
3776
|
+
errors.push(`${event}[${gi}].hooks[${hi}]: type=http requires "url"`);
|
|
3777
|
+
}
|
|
3778
|
+
if (h.command && typeof h.command === "string" && /\$\{CLAUDE_/.test(h.command)) {
|
|
3779
|
+
passes.push(`${event}[${gi}].hooks[${hi}]: uses plugin env substitution`);
|
|
3780
|
+
}
|
|
3781
|
+
});
|
|
3782
|
+
if (hooksArr.length > 0) {
|
|
3783
|
+
passes.push(`Event "${event}" has ${hooksArr.length} hook action(s)`);
|
|
3784
|
+
}
|
|
3785
|
+
});
|
|
3786
|
+
}
|
|
3030
3787
|
return { errors, warnings, passes };
|
|
3031
3788
|
}
|
|
3032
3789
|
};
|
|
3033
3790
|
});
|
|
3034
3791
|
|
|
3035
3792
|
// src/validators/claude/mcp.ts
|
|
3036
|
-
import { existsSync as
|
|
3037
|
-
import { resolve as
|
|
3793
|
+
import { existsSync as existsSync17 } from "fs";
|
|
3794
|
+
import { resolve as resolve8 } from "path";
|
|
3038
3795
|
var claudeMcpValidator;
|
|
3039
3796
|
var init_mcp = __esm(() => {
|
|
3040
3797
|
claudeMcpValidator = {
|
|
3041
3798
|
id: "claude:mcp",
|
|
3042
3799
|
provider: "claude",
|
|
3043
3800
|
name: "Claude MCP Config",
|
|
3044
|
-
description: "Validates .mcp.json: server
|
|
3801
|
+
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",
|
|
3045
3802
|
detect(dir) {
|
|
3046
|
-
return
|
|
3803
|
+
return existsSync17(resolve8(dir, ".mcp.json"));
|
|
3047
3804
|
},
|
|
3048
3805
|
async validate(dir, _opts) {
|
|
3049
3806
|
const errors = [];
|
|
3050
3807
|
const warnings = [];
|
|
3051
3808
|
const passes = [];
|
|
3052
|
-
const mcpPath =
|
|
3809
|
+
const mcpPath = resolve8(dir, ".mcp.json");
|
|
3053
3810
|
let config;
|
|
3054
3811
|
try {
|
|
3055
3812
|
const raw = await Bun.file(mcpPath).text();
|
|
@@ -3069,14 +3826,42 @@ var init_mcp = __esm(() => {
|
|
|
3069
3826
|
return { errors, warnings, passes };
|
|
3070
3827
|
}
|
|
3071
3828
|
passes.push(`${serverNames.length} server(s) defined`);
|
|
3829
|
+
for (const [name, entry] of Object.entries(config)) {
|
|
3830
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
3831
|
+
errors.push(`mcp server "${name}": definition must be an object`);
|
|
3832
|
+
continue;
|
|
3833
|
+
}
|
|
3834
|
+
const e = entry;
|
|
3835
|
+
const hasCommand = typeof e.command === "string";
|
|
3836
|
+
const hasUrl = typeof e.url === "string";
|
|
3837
|
+
if (!hasCommand && !hasUrl) {
|
|
3838
|
+
errors.push(`mcp server "${name}": must have either "command" (for stdio) or "url" (for SSE/HTTP)`);
|
|
3839
|
+
}
|
|
3840
|
+
if (hasCommand && !Array.isArray(e.args)) {
|
|
3841
|
+
warnings.push(`mcp server "${name}": "command" present but no "args" array (ok for some servers)`);
|
|
3842
|
+
}
|
|
3843
|
+
if (hasUrl && hasCommand) {
|
|
3844
|
+
warnings.push(`mcp server "${name}": both "command" and "url" present \u2014 usually one or the other`);
|
|
3845
|
+
}
|
|
3846
|
+
if (e.env && typeof e.env === "object") {
|
|
3847
|
+
passes.push(`mcp server "${name}": has env`);
|
|
3848
|
+
}
|
|
3849
|
+
if (typeof e.cwd === "string") {
|
|
3850
|
+
passes.push(`mcp server "${name}": has cwd`);
|
|
3851
|
+
}
|
|
3852
|
+
const hasSubs = JSON.stringify(e).match(/\$\{CLAUDE_PLUGIN_(ROOT|DATA)|CLAUDE_PROJECT_DIR|user_config\.|ENV_VAR\}/);
|
|
3853
|
+
if (hasSubs) {
|
|
3854
|
+
passes.push(`mcp server "${name}": uses \${CLAUDE_PLUGIN_*} / user_config / env substitution`);
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3072
3857
|
return { errors, warnings, passes };
|
|
3073
3858
|
}
|
|
3074
3859
|
};
|
|
3075
3860
|
});
|
|
3076
3861
|
|
|
3077
3862
|
// src/validators/claude/subagent.ts
|
|
3078
|
-
import { existsSync as
|
|
3079
|
-
import { resolve as
|
|
3863
|
+
import { existsSync as existsSync18, readdirSync as readdirSync8 } from "fs";
|
|
3864
|
+
import { resolve as resolve9, join as join14 } from "path";
|
|
3080
3865
|
var claudeSubagentValidator;
|
|
3081
3866
|
var init_subagent = __esm(() => {
|
|
3082
3867
|
init_frontmatter();
|
|
@@ -3084,13 +3869,13 @@ var init_subagent = __esm(() => {
|
|
|
3084
3869
|
id: "claude:subagent",
|
|
3085
3870
|
provider: "claude",
|
|
3086
3871
|
name: "Claude Subagents",
|
|
3087
|
-
description: "Validates agents
|
|
3872
|
+
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",
|
|
3088
3873
|
detect(dir) {
|
|
3089
|
-
const agentsDir =
|
|
3090
|
-
if (!
|
|
3874
|
+
const agentsDir = resolve9(dir, "agents");
|
|
3875
|
+
if (!existsSync18(agentsDir))
|
|
3091
3876
|
return false;
|
|
3092
3877
|
try {
|
|
3093
|
-
return
|
|
3878
|
+
return readdirSync8(agentsDir).some((f) => f.endsWith(".md"));
|
|
3094
3879
|
} catch {
|
|
3095
3880
|
return false;
|
|
3096
3881
|
}
|
|
@@ -3099,27 +3884,63 @@ var init_subagent = __esm(() => {
|
|
|
3099
3884
|
const errors = [];
|
|
3100
3885
|
const warnings = [];
|
|
3101
3886
|
const passes = [];
|
|
3102
|
-
const agentsDir =
|
|
3103
|
-
const mdFiles =
|
|
3887
|
+
const agentsDir = resolve9(dir, "agents");
|
|
3888
|
+
const mdFiles = readdirSync8(agentsDir).filter((f) => f.endsWith(".md"));
|
|
3104
3889
|
if (mdFiles.length === 0) {
|
|
3105
3890
|
errors.push("agents/ directory has no .md files");
|
|
3106
3891
|
return { errors, warnings, passes };
|
|
3107
3892
|
}
|
|
3108
3893
|
passes.push(`${mdFiles.length} agent definition(s) found`);
|
|
3894
|
+
const SUPPORTED = new Set([
|
|
3895
|
+
"name",
|
|
3896
|
+
"description",
|
|
3897
|
+
"model",
|
|
3898
|
+
"effort",
|
|
3899
|
+
"maxTurns",
|
|
3900
|
+
"tools",
|
|
3901
|
+
"disallowedTools",
|
|
3902
|
+
"skills",
|
|
3903
|
+
"memory",
|
|
3904
|
+
"background",
|
|
3905
|
+
"isolation"
|
|
3906
|
+
]);
|
|
3907
|
+
const DISALLOWED = new Set(["hooks", "mcpServers", "permissionMode"]);
|
|
3109
3908
|
for (const file of mdFiles) {
|
|
3110
|
-
const filePath =
|
|
3909
|
+
const filePath = join14(agentsDir, file);
|
|
3111
3910
|
const raw = await Bun.file(filePath).text();
|
|
3112
3911
|
try {
|
|
3113
3912
|
const parsed = parseFrontmatter(raw);
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
warnings.push(`${file}: missing "description" in frontmatter`);
|
|
3913
|
+
const fm = parsed.data;
|
|
3914
|
+
if (Object.keys(fm).length === 0) {
|
|
3915
|
+
warnings.push(`${file}: no YAML frontmatter (description recommended so Claude knows when to invoke)`);
|
|
3118
3916
|
} else {
|
|
3119
|
-
|
|
3917
|
+
if (fm.description) {
|
|
3918
|
+
passes.push(`${file}: has frontmatter with description`);
|
|
3919
|
+
} else {
|
|
3920
|
+
warnings.push(`${file}: missing "description" in frontmatter`);
|
|
3921
|
+
}
|
|
3922
|
+
const usedSupported = [];
|
|
3923
|
+
Object.keys(fm).forEach((k) => {
|
|
3924
|
+
if (SUPPORTED.has(k))
|
|
3925
|
+
usedSupported.push(k);
|
|
3926
|
+
if (DISALLOWED.has(k)) {
|
|
3927
|
+
errors.push(`${file}: frontmatter "${k}" is not supported for plugin-shipped agents (security restriction)`);
|
|
3928
|
+
}
|
|
3929
|
+
});
|
|
3930
|
+
if (usedSupported.length) {
|
|
3931
|
+
passes.push(`${file}: frontmatter fields: ${usedSupported.join(", ")}`);
|
|
3932
|
+
}
|
|
3933
|
+
if (fm.isolation !== undefined && fm.isolation !== "worktree") {
|
|
3934
|
+
errors.push(`${file}: "isolation" must be "worktree" if present (only supported value for plugin agents)`);
|
|
3935
|
+
}
|
|
3936
|
+
if (fm.name && typeof fm.name === "string") {
|
|
3937
|
+
passes.push(`${file}: name: "${fm.name}"`);
|
|
3938
|
+
}
|
|
3120
3939
|
}
|
|
3121
3940
|
if (!parsed.content.trim()) {
|
|
3122
3941
|
errors.push(`${file}: body is empty`);
|
|
3942
|
+
} else {
|
|
3943
|
+
passes.push(`${file}: has agent system prompt body`);
|
|
3123
3944
|
}
|
|
3124
3945
|
} catch {
|
|
3125
3946
|
errors.push(`${file}: failed to parse`);
|
|
@@ -3131,8 +3952,8 @@ var init_subagent = __esm(() => {
|
|
|
3131
3952
|
});
|
|
3132
3953
|
|
|
3133
3954
|
// src/validators/claude/command.ts
|
|
3134
|
-
import { existsSync as
|
|
3135
|
-
import { resolve as
|
|
3955
|
+
import { existsSync as existsSync19, readdirSync as readdirSync9 } from "fs";
|
|
3956
|
+
import { resolve as resolve10, join as join15 } from "path";
|
|
3136
3957
|
var claudeCommandValidator;
|
|
3137
3958
|
var init_command = __esm(() => {
|
|
3138
3959
|
init_frontmatter();
|
|
@@ -3142,11 +3963,11 @@ var init_command = __esm(() => {
|
|
|
3142
3963
|
name: "Claude Commands",
|
|
3143
3964
|
description: "Validates commands/ (or legacy .claude/commands/) .md files: frontmatter (including rich skill fields), description, body",
|
|
3144
3965
|
detect(dir) {
|
|
3145
|
-
const commandsDir =
|
|
3146
|
-
if (!
|
|
3966
|
+
const commandsDir = resolve10(dir, "commands");
|
|
3967
|
+
if (!existsSync19(commandsDir))
|
|
3147
3968
|
return false;
|
|
3148
3969
|
try {
|
|
3149
|
-
return
|
|
3970
|
+
return readdirSync9(commandsDir).some((f) => f.endsWith(".md"));
|
|
3150
3971
|
} catch {
|
|
3151
3972
|
return false;
|
|
3152
3973
|
}
|
|
@@ -3155,15 +3976,15 @@ var init_command = __esm(() => {
|
|
|
3155
3976
|
const errors = [];
|
|
3156
3977
|
const warnings = [];
|
|
3157
3978
|
const passes = [];
|
|
3158
|
-
const commandsDir =
|
|
3159
|
-
const mdFiles =
|
|
3979
|
+
const commandsDir = resolve10(dir, "commands");
|
|
3980
|
+
const mdFiles = readdirSync9(commandsDir).filter((f) => f.endsWith(".md"));
|
|
3160
3981
|
if (mdFiles.length === 0) {
|
|
3161
3982
|
errors.push("commands/ directory has no .md files");
|
|
3162
3983
|
return { errors, warnings, passes };
|
|
3163
3984
|
}
|
|
3164
3985
|
passes.push(`${mdFiles.length} command definition(s) found`);
|
|
3165
3986
|
for (const file of mdFiles) {
|
|
3166
|
-
const filePath =
|
|
3987
|
+
const filePath = join15(commandsDir, file);
|
|
3167
3988
|
const raw = await Bun.file(filePath).text();
|
|
3168
3989
|
try {
|
|
3169
3990
|
const parsed = parseFrontmatter(raw);
|
|
@@ -3192,8 +4013,8 @@ var init_command = __esm(() => {
|
|
|
3192
4013
|
});
|
|
3193
4014
|
|
|
3194
4015
|
// src/validators/claude/memory.ts
|
|
3195
|
-
import { existsSync as
|
|
3196
|
-
import { resolve as
|
|
4016
|
+
import { existsSync as existsSync20 } from "fs";
|
|
4017
|
+
import { resolve as resolve11 } from "path";
|
|
3197
4018
|
var claudeMemoryValidator;
|
|
3198
4019
|
var init_memory = __esm(() => {
|
|
3199
4020
|
claudeMemoryValidator = {
|
|
@@ -3202,13 +4023,13 @@ var init_memory = __esm(() => {
|
|
|
3202
4023
|
name: "Claude CLAUDE.md",
|
|
3203
4024
|
description: "Validates CLAUDE.md: non-empty, length recommendations, @path imports",
|
|
3204
4025
|
detect(dir) {
|
|
3205
|
-
return
|
|
4026
|
+
return existsSync20(resolve11(dir, "CLAUDE.md"));
|
|
3206
4027
|
},
|
|
3207
4028
|
async validate(dir, _opts) {
|
|
3208
4029
|
const errors = [];
|
|
3209
4030
|
const warnings = [];
|
|
3210
4031
|
const passes = [];
|
|
3211
|
-
const filePath =
|
|
4032
|
+
const filePath = resolve11(dir, "CLAUDE.md");
|
|
3212
4033
|
const raw = await Bun.file(filePath).text();
|
|
3213
4034
|
if (!raw.trim()) {
|
|
3214
4035
|
errors.push("CLAUDE.md is empty");
|
|
@@ -3226,8 +4047,8 @@ var init_memory = __esm(() => {
|
|
|
3226
4047
|
let match;
|
|
3227
4048
|
while ((match = importRegex.exec(raw)) !== null) {
|
|
3228
4049
|
const importPath = match[1];
|
|
3229
|
-
const resolvedImport =
|
|
3230
|
-
if (
|
|
4050
|
+
const resolvedImport = resolve11(dir, importPath);
|
|
4051
|
+
if (existsSync20(resolvedImport)) {
|
|
3231
4052
|
passes.push(`@import "${importPath}" exists`);
|
|
3232
4053
|
} else {
|
|
3233
4054
|
warnings.push(`@import "${importPath}" \u2014 file not found at ${resolvedImport}`);
|
|
@@ -3238,6 +4059,165 @@ var init_memory = __esm(() => {
|
|
|
3238
4059
|
};
|
|
3239
4060
|
});
|
|
3240
4061
|
|
|
4062
|
+
// src/validators/claude/lsp.ts
|
|
4063
|
+
import { existsSync as existsSync21 } from "fs";
|
|
4064
|
+
import { resolve as resolve12 } from "path";
|
|
4065
|
+
var claudeLspValidator;
|
|
4066
|
+
var init_lsp = __esm(() => {
|
|
4067
|
+
claudeLspValidator = {
|
|
4068
|
+
id: "claude:lsp",
|
|
4069
|
+
provider: "claude",
|
|
4070
|
+
name: "Claude LSP Servers",
|
|
4071
|
+
description: "Validates .lsp.json (or plugin.json lspServers): language server configs with required command + extensionToLanguage; optional transport, env, settings, diagnostics etc. (binaries installed separately)",
|
|
4072
|
+
detect(dir) {
|
|
4073
|
+
return existsSync21(resolve12(dir, ".lsp.json")) || existsSync21(resolve12(dir, ".claude-plugin", "plugin.json"));
|
|
4074
|
+
},
|
|
4075
|
+
async validate(dir, _opts) {
|
|
4076
|
+
const errors = [];
|
|
4077
|
+
const warnings = [];
|
|
4078
|
+
const passes = [];
|
|
4079
|
+
let cfg = null;
|
|
4080
|
+
const lspPath = resolve12(dir, ".lsp.json");
|
|
4081
|
+
if (existsSync21(lspPath)) {
|
|
4082
|
+
try {
|
|
4083
|
+
cfg = JSON.parse(await Bun.file(lspPath).text());
|
|
4084
|
+
passes.push(".lsp.json is valid JSON");
|
|
4085
|
+
} catch {
|
|
4086
|
+
errors.push(".lsp.json is invalid JSON");
|
|
4087
|
+
return { errors, warnings, passes };
|
|
4088
|
+
}
|
|
4089
|
+
} else {
|
|
4090
|
+
const manifestPath = resolve12(dir, ".claude-plugin", "plugin.json");
|
|
4091
|
+
if (existsSync21(manifestPath)) {
|
|
4092
|
+
try {
|
|
4093
|
+
const m = JSON.parse(await Bun.file(manifestPath).text());
|
|
4094
|
+
if (m && m.lspServers && typeof m.lspServers === "object") {
|
|
4095
|
+
cfg = m.lspServers;
|
|
4096
|
+
passes.push("lspServers present inline in plugin.json");
|
|
4097
|
+
}
|
|
4098
|
+
} catch {}
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
if (!cfg) {
|
|
4102
|
+
if (!existsSync21(lspPath)) {
|
|
4103
|
+
return { errors, warnings, passes };
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4106
|
+
if (cfg && typeof cfg === "object") {
|
|
4107
|
+
const langs = Object.keys(cfg);
|
|
4108
|
+
passes.push(`${langs.length} language server(s) configured`);
|
|
4109
|
+
for (const lang of langs) {
|
|
4110
|
+
const entry = cfg[lang];
|
|
4111
|
+
if (!entry || !entry.command) {
|
|
4112
|
+
errors.push(`lsp "${lang}": "command" (the LSP binary) is required`);
|
|
4113
|
+
}
|
|
4114
|
+
if (!entry.extensionToLanguage || typeof entry.extensionToLanguage !== "object") {
|
|
4115
|
+
errors.push(`lsp "${lang}": "extensionToLanguage" map is required (e.g. { ".ts": "typescript" })`);
|
|
4116
|
+
} else {
|
|
4117
|
+
passes.push(`lsp "${lang}": has extensionToLanguage mapping`);
|
|
4118
|
+
}
|
|
4119
|
+
if (entry.diagnostics === false) {
|
|
4120
|
+
passes.push(`lsp "${lang}": diagnostics disabled (navigation only)`);
|
|
4121
|
+
}
|
|
4122
|
+
}
|
|
4123
|
+
}
|
|
4124
|
+
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".');
|
|
4125
|
+
return { errors, warnings, passes };
|
|
4126
|
+
}
|
|
4127
|
+
};
|
|
4128
|
+
});
|
|
4129
|
+
|
|
4130
|
+
// src/validators/claude/monitors.ts
|
|
4131
|
+
import { existsSync as existsSync22 } from "fs";
|
|
4132
|
+
import { resolve as resolve13 } from "path";
|
|
4133
|
+
var claudeMonitorsValidator;
|
|
4134
|
+
var init_monitors = __esm(() => {
|
|
4135
|
+
claudeMonitorsValidator = {
|
|
4136
|
+
id: "claude:monitors",
|
|
4137
|
+
provider: "claude",
|
|
4138
|
+
name: "Claude Monitors (experimental)",
|
|
4139
|
+
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.",
|
|
4140
|
+
detect(dir) {
|
|
4141
|
+
return existsSync22(resolve13(dir, "monitors", "monitors.json")) || existsSync22(resolve13(dir, "monitors.json")) || existsSync22(resolve13(dir, ".claude-plugin", "plugin.json"));
|
|
4142
|
+
},
|
|
4143
|
+
async validate(dir, _opts) {
|
|
4144
|
+
const errors = [];
|
|
4145
|
+
const warnings = [];
|
|
4146
|
+
const passes = [];
|
|
4147
|
+
let arr = null;
|
|
4148
|
+
const candidates = [
|
|
4149
|
+
resolve13(dir, "monitors", "monitors.json"),
|
|
4150
|
+
resolve13(dir, "monitors.json")
|
|
4151
|
+
];
|
|
4152
|
+
for (const p of candidates) {
|
|
4153
|
+
if (existsSync22(p)) {
|
|
4154
|
+
try {
|
|
4155
|
+
const parsed = JSON.parse(await Bun.file(p).text());
|
|
4156
|
+
if (Array.isArray(parsed)) {
|
|
4157
|
+
arr = parsed;
|
|
4158
|
+
passes.push("monitors config is valid JSON array");
|
|
4159
|
+
}
|
|
4160
|
+
break;
|
|
4161
|
+
} catch {
|
|
4162
|
+
errors.push("monitors config is invalid JSON");
|
|
4163
|
+
return { errors, warnings, passes };
|
|
4164
|
+
}
|
|
4165
|
+
}
|
|
4166
|
+
}
|
|
4167
|
+
if (!arr) {
|
|
4168
|
+
const mp = resolve13(dir, ".claude-plugin", "plugin.json");
|
|
4169
|
+
if (existsSync22(mp)) {
|
|
4170
|
+
try {
|
|
4171
|
+
const m = JSON.parse(await Bun.file(mp).text());
|
|
4172
|
+
const exp = m?.experimental;
|
|
4173
|
+
const inline = typeof exp === "string" ? null : exp?.monitors;
|
|
4174
|
+
if (Array.isArray(inline))
|
|
4175
|
+
arr = inline;
|
|
4176
|
+
else if (typeof inline === "string") {
|
|
4177
|
+
passes.push("experimental.monitors declared as path in manifest (content not validated here)");
|
|
4178
|
+
}
|
|
4179
|
+
} catch {}
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
if (!arr) {
|
|
4183
|
+
return { errors, warnings, passes };
|
|
4184
|
+
}
|
|
4185
|
+
if (!Array.isArray(arr)) {
|
|
4186
|
+
errors.push("monitors config must be a JSON array");
|
|
4187
|
+
return { errors, warnings, passes };
|
|
4188
|
+
}
|
|
4189
|
+
const seen = new Set;
|
|
4190
|
+
arr.forEach((mon, i) => {
|
|
4191
|
+
if (!mon || typeof mon !== "object") {
|
|
4192
|
+
errors.push(`monitors[${i}]: entry must be an object`);
|
|
4193
|
+
return;
|
|
4194
|
+
}
|
|
4195
|
+
if (!mon.name || typeof mon.name !== "string") {
|
|
4196
|
+
errors.push(`monitors[${i}]: "name" (unique id) is required`);
|
|
4197
|
+
} else {
|
|
4198
|
+
if (seen.has(mon.name))
|
|
4199
|
+
errors.push(`monitors: duplicate name "${mon.name}"`);
|
|
4200
|
+
seen.add(mon.name);
|
|
4201
|
+
}
|
|
4202
|
+
if (!mon.command || typeof mon.command !== "string") {
|
|
4203
|
+
errors.push(`monitors[${i}]: "command" (shell command) is required`);
|
|
4204
|
+
} else if (/\$\{CLAUDE_/.test(mon.command)) {
|
|
4205
|
+
passes.push(`monitors[${i}] "${mon.name || i}": uses CLAUDE_PLUGIN_* substitution`);
|
|
4206
|
+
}
|
|
4207
|
+
if (!mon.description) {
|
|
4208
|
+
warnings.push(`monitors[${i}]: "description" recommended (shown in task panel)`);
|
|
4209
|
+
}
|
|
4210
|
+
if (mon.when && !/^always$|^on-skill-invoke:/.test(String(mon.when))) {
|
|
4211
|
+
warnings.push(`monitors[${i}]: "when" should be "always" (default) or "on-skill-invoke:<skill>"`);
|
|
4212
|
+
}
|
|
4213
|
+
});
|
|
4214
|
+
passes.push(`${arr.length} monitor(s) declared`);
|
|
4215
|
+
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.");
|
|
4216
|
+
return { errors, warnings, passes };
|
|
4217
|
+
}
|
|
4218
|
+
};
|
|
4219
|
+
});
|
|
4220
|
+
|
|
3241
4221
|
// src/validators/index.ts
|
|
3242
4222
|
function resolveFor(forFlag, allValidators = validators) {
|
|
3243
4223
|
if (!forFlag) {
|
|
@@ -3272,6 +4252,8 @@ var init_validators = __esm(() => {
|
|
|
3272
4252
|
init_subagent();
|
|
3273
4253
|
init_command();
|
|
3274
4254
|
init_memory();
|
|
4255
|
+
init_lsp();
|
|
4256
|
+
init_monitors();
|
|
3275
4257
|
validators = [
|
|
3276
4258
|
claudeSkillValidator,
|
|
3277
4259
|
claudePluginValidator,
|
|
@@ -3280,14 +4262,16 @@ var init_validators = __esm(() => {
|
|
|
3280
4262
|
claudeMcpValidator,
|
|
3281
4263
|
claudeSubagentValidator,
|
|
3282
4264
|
claudeCommandValidator,
|
|
3283
|
-
claudeMemoryValidator
|
|
4265
|
+
claudeMemoryValidator,
|
|
4266
|
+
claudeLspValidator,
|
|
4267
|
+
claudeMonitorsValidator
|
|
3284
4268
|
];
|
|
3285
4269
|
});
|
|
3286
4270
|
|
|
3287
4271
|
// src/core/remote.ts
|
|
3288
4272
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
3289
4273
|
import { mkdtempSync, rmSync } from "fs";
|
|
3290
|
-
import { join as
|
|
4274
|
+
import { join as join16 } from "path";
|
|
3291
4275
|
import { tmpdir } from "os";
|
|
3292
4276
|
function parseRemoteUrl(input) {
|
|
3293
4277
|
if (input.startsWith(".") || input.startsWith("/") || input.startsWith("~")) {
|
|
@@ -3324,7 +4308,7 @@ function isGhAvailable() {
|
|
|
3324
4308
|
return ghAvailable;
|
|
3325
4309
|
}
|
|
3326
4310
|
async function cloneToTemp(parsed) {
|
|
3327
|
-
const tmpDir = mkdtempSync(
|
|
4311
|
+
const tmpDir = mkdtempSync(join16(tmpdir(), "dora-"));
|
|
3328
4312
|
const cleanup = () => {
|
|
3329
4313
|
try {
|
|
3330
4314
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
@@ -3372,15 +4356,15 @@ var exports_validate_top = {};
|
|
|
3372
4356
|
__export(exports_validate_top, {
|
|
3373
4357
|
default: () => validate_top_default
|
|
3374
4358
|
});
|
|
3375
|
-
import { existsSync as
|
|
3376
|
-
import { resolve as
|
|
3377
|
-
var
|
|
4359
|
+
import { existsSync as existsSync24 } from "fs";
|
|
4360
|
+
import { resolve as resolve14 } from "path";
|
|
4361
|
+
var import_picocolors13, validate_top_default;
|
|
3378
4362
|
var init_validate_top = __esm(() => {
|
|
3379
4363
|
init_dist();
|
|
3380
4364
|
init_out();
|
|
3381
4365
|
init_validators();
|
|
3382
4366
|
init_remote();
|
|
3383
|
-
|
|
4367
|
+
import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
3384
4368
|
validate_top_default = defineCommand({
|
|
3385
4369
|
meta: {
|
|
3386
4370
|
name: "validate",
|
|
@@ -3420,24 +4404,24 @@ var init_validate_top = __esm(() => {
|
|
|
3420
4404
|
let cleanup;
|
|
3421
4405
|
if (remote) {
|
|
3422
4406
|
ui.info(`
|
|
3423
|
-
Cloning ${
|
|
4407
|
+
Cloning ${import_picocolors13.default.dim(args.path)}...`);
|
|
3424
4408
|
try {
|
|
3425
4409
|
const result = await cloneToTemp(remote);
|
|
3426
|
-
fullPath = remote.subpath ?
|
|
4410
|
+
fullPath = remote.subpath ? resolve14(result.dir, remote.subpath) : result.dir;
|
|
3427
4411
|
cleanup = result.cleanup;
|
|
3428
4412
|
} catch (err) {
|
|
3429
4413
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3430
4414
|
ui.fail(msg);
|
|
3431
4415
|
process.exit(1);
|
|
3432
4416
|
}
|
|
3433
|
-
if (!
|
|
4417
|
+
if (!existsSync24(fullPath)) {
|
|
3434
4418
|
cleanup();
|
|
3435
4419
|
ui.fail(`Subdirectory not found in repo: ${remote.subpath}`);
|
|
3436
4420
|
process.exit(1);
|
|
3437
4421
|
}
|
|
3438
4422
|
} else {
|
|
3439
|
-
fullPath =
|
|
3440
|
-
if (!
|
|
4423
|
+
fullPath = resolve14(args.path);
|
|
4424
|
+
if (!existsSync24(fullPath)) {
|
|
3441
4425
|
ui.fail(`Path not found: ${args.path}
|
|
3442
4426
|
|
|
3443
4427
|
Check that the path is correct and the directory exists.`);
|
|
@@ -3468,13 +4452,13 @@ Check that the path is correct and the directory exists.`);
|
|
|
3468
4452
|
` + `Available providers:
|
|
3469
4453
|
` + providers.map((p) => {
|
|
3470
4454
|
const pvs = validators.filter((v) => v.provider === p);
|
|
3471
|
-
return ` ${
|
|
3472
|
-
` + pvs.map((v) => ` \u2022 ${
|
|
4455
|
+
return ` ${import_picocolors13.default.bold(p)}
|
|
4456
|
+
` + pvs.map((v) => ` \u2022 ${import_picocolors13.default.dim(v.id)} \u2014 ${v.description}`).join(`
|
|
3473
4457
|
`);
|
|
3474
4458
|
}).join(`
|
|
3475
4459
|
`) + `
|
|
3476
4460
|
|
|
3477
|
-
Use ${
|
|
4461
|
+
Use ${import_picocolors13.default.dim("--for <provider>")} or ${import_picocolors13.default.dim("--for <provider:type>")} to target explicitly.`);
|
|
3478
4462
|
process.exit(1);
|
|
3479
4463
|
}
|
|
3480
4464
|
const allResults = [];
|
|
@@ -3495,7 +4479,7 @@ Use ${import_picocolors11.default.dim("--for <provider>")} or ${import_picocolor
|
|
|
3495
4479
|
} else {
|
|
3496
4480
|
for (const { id, name, result } of allResults) {
|
|
3497
4481
|
ui.write(`
|
|
3498
|
-
${
|
|
4482
|
+
${import_picocolors13.default.bold("dora validate")} \u2014 ${import_picocolors13.default.white(name)} ${import_picocolors13.default.dim(`(${id})`)}
|
|
3499
4483
|
`);
|
|
3500
4484
|
ui.info(` Path: ${args.path}
|
|
3501
4485
|
`);
|
|
@@ -3510,7 +4494,7 @@ Use ${import_picocolors11.default.dim("--for <provider>")} or ${import_picocolor
|
|
|
3510
4494
|
}
|
|
3511
4495
|
if (result.errors.length === 0 && result.warnings.length === 0) {
|
|
3512
4496
|
ui.write(`
|
|
3513
|
-
${
|
|
4497
|
+
${import_picocolors13.default.green("\u2713")} ${import_picocolors13.default.white("All checks passed.")}
|
|
3514
4498
|
`);
|
|
3515
4499
|
} else {
|
|
3516
4500
|
ui.info(`
|
|
@@ -3532,16 +4516,16 @@ var exports_init2 = {};
|
|
|
3532
4516
|
__export(exports_init2, {
|
|
3533
4517
|
default: () => init_default2
|
|
3534
4518
|
});
|
|
3535
|
-
import { basename as
|
|
4519
|
+
import { basename as basename4, join as join17 } from "path";
|
|
3536
4520
|
var {spawnSync: spawnSync5 } = globalThis.Bun;
|
|
3537
|
-
var
|
|
4521
|
+
var import_picocolors14, init_default2;
|
|
3538
4522
|
var init_init2 = __esm(() => {
|
|
3539
4523
|
init_dist();
|
|
3540
4524
|
init_out();
|
|
3541
4525
|
init_journal_config();
|
|
3542
4526
|
init_journal_remote();
|
|
3543
4527
|
init_prompt();
|
|
3544
|
-
|
|
4528
|
+
import_picocolors14 = __toESM(require_picocolors(), 1);
|
|
3545
4529
|
init_default2 = defineCommand({
|
|
3546
4530
|
meta: {
|
|
3547
4531
|
name: "init",
|
|
@@ -3568,17 +4552,17 @@ var init_init2 = __esm(() => {
|
|
|
3568
4552
|
ui.heading("dora init \u2014 Set up doraval, your journal, and the coding agent dora should use on the fly");
|
|
3569
4553
|
const ghCheck = ensureGhCli();
|
|
3570
4554
|
if (!ghCheck.ok) {
|
|
3571
|
-
ui.write(` ${
|
|
4555
|
+
ui.write(` ${import_picocolors14.default.red("\u2717")} ${import_picocolors14.default.white("The GitHub CLI (")}${import_picocolors14.default.bold("gh")}${import_picocolors14.default.white(") is not installed.")}
|
|
3572
4556
|
`);
|
|
3573
|
-
ui.info(` doraval uses ${
|
|
4557
|
+
ui.info(` doraval uses ${import_picocolors14.default.bold("gh")} to fetch and sync journal files with GitHub.
|
|
3574
4558
|
`);
|
|
3575
4559
|
ui.info(` Install it:
|
|
3576
4560
|
`);
|
|
3577
|
-
ui.info(` macOS: ${
|
|
3578
|
-
ui.info(` Linux: ${
|
|
3579
|
-
ui.info(` Windows: ${
|
|
4561
|
+
ui.info(` macOS: ${import_picocolors14.default.dim("brew install gh")}`);
|
|
4562
|
+
ui.info(` Linux: ${import_picocolors14.default.dim("https://github.com/cli/cli/blob/trunk/docs/install_linux.md")}`);
|
|
4563
|
+
ui.info(` Windows: ${import_picocolors14.default.dim("winget install --id GitHub.cli")}
|
|
3580
4564
|
`);
|
|
3581
|
-
ui.info(` Then authenticate: ${
|
|
4565
|
+
ui.info(` Then authenticate: ${import_picocolors14.default.dim("gh auth login")}
|
|
3582
4566
|
`);
|
|
3583
4567
|
process.exit(1);
|
|
3584
4568
|
}
|
|
@@ -3591,44 +4575,44 @@ var init_init2 = __esm(() => {
|
|
|
3591
4575
|
if (gitOwner) {
|
|
3592
4576
|
defaultRepo = `${gitOwner}/${gitOwner}.md`;
|
|
3593
4577
|
if (ghLogin && ghLogin !== gitOwner) {
|
|
3594
|
-
sourceNote = ` ${
|
|
4578
|
+
sourceNote = ` ${import_picocolors14.default.dim("(from git remote; your active gh account is " + ghLogin + ")")}
|
|
3595
4579
|
`;
|
|
3596
4580
|
} else {
|
|
3597
|
-
sourceNote = ` ${
|
|
4581
|
+
sourceNote = ` ${import_picocolors14.default.dim("(from git remote)")}
|
|
3598
4582
|
`;
|
|
3599
4583
|
}
|
|
3600
4584
|
} else if (ghLogin) {
|
|
3601
4585
|
defaultRepo = `${ghLogin}/${ghLogin}.md`;
|
|
3602
|
-
sourceNote = ` ${
|
|
4586
|
+
sourceNote = ` ${import_picocolors14.default.dim("(from your active gh account)")}
|
|
3603
4587
|
`;
|
|
3604
4588
|
} else {
|
|
3605
|
-
ui.warn(`Not logged in to GitHub. Run ${
|
|
4589
|
+
ui.warn(`Not logged in to GitHub. Run ${import_picocolors14.default.dim("gh auth login")} first.
|
|
3606
4590
|
`);
|
|
3607
4591
|
process.exit(1);
|
|
3608
4592
|
}
|
|
3609
4593
|
const existingConfig = await readConfig();
|
|
3610
4594
|
if (existingConfig?.journal.repo) {
|
|
3611
4595
|
defaultRepo = existingConfig.journal.repo;
|
|
3612
|
-
sourceNote = ` ${
|
|
4596
|
+
sourceNote = ` ${import_picocolors14.default.dim("(from your previous journal setup)")}
|
|
3613
4597
|
`;
|
|
3614
4598
|
}
|
|
3615
|
-
ui.info(` Journal repo ${
|
|
4599
|
+
ui.info(` Journal repo ${import_picocolors14.default.dim("(owner/name)")}`);
|
|
3616
4600
|
if (sourceNote)
|
|
3617
4601
|
ui.write(sourceNote);
|
|
3618
4602
|
repo = prompt(" >", defaultRepo);
|
|
3619
4603
|
}
|
|
3620
4604
|
let project = args.project || process.env.DORAVAL_PROJECT;
|
|
3621
4605
|
if (!project) {
|
|
3622
|
-
const defaultProject =
|
|
4606
|
+
const defaultProject = basename4(process.cwd());
|
|
3623
4607
|
project = prompt(" Project name", defaultProject);
|
|
3624
4608
|
}
|
|
3625
4609
|
project = sanitizeProjectName(project);
|
|
3626
4610
|
if (!repoExists(repo)) {
|
|
3627
|
-
ui.write(` ${
|
|
4611
|
+
ui.write(` ${import_picocolors14.default.red("\u2717")} ${import_picocolors14.default.white("Repository")} ${import_picocolors14.default.bold(repo)} ${import_picocolors14.default.white("not found on GitHub.")}
|
|
3628
4612
|
`);
|
|
3629
4613
|
ui.info(` Create it first:
|
|
3630
4614
|
`);
|
|
3631
|
-
ui.info(` ${
|
|
4615
|
+
ui.info(` ${import_picocolors14.default.dim(`gh repo create ${repo} --private --description "Personal journal for agent decisions"`)}
|
|
3632
4616
|
`);
|
|
3633
4617
|
process.exit(1);
|
|
3634
4618
|
}
|
|
@@ -3636,16 +4620,16 @@ var init_init2 = __esm(() => {
|
|
|
3636
4620
|
const alreadyRegistered = existing?.journal.projects[project];
|
|
3637
4621
|
const isRefresh = alreadyRegistered && args.refresh;
|
|
3638
4622
|
if (alreadyRegistered && !isRefresh) {
|
|
3639
|
-
ui.write(` ${
|
|
4623
|
+
ui.write(` ${import_picocolors14.default.yellow("\u26A0")} ${import_picocolors14.default.white("Project")} ${import_picocolors14.default.bold(project)} ${import_picocolors14.default.white("is already registered.")}
|
|
3640
4624
|
`);
|
|
3641
4625
|
ui.info(` Repo: ${existing.journal.repo}
|
|
3642
4626
|
`);
|
|
3643
|
-
ui.info(` To refresh journal files, use ${
|
|
4627
|
+
ui.info(` To refresh journal files, use ${import_picocolors14.default.dim("dora journal update")} (or ${import_picocolors14.default.dim("dora init --refresh")}).
|
|
3644
4628
|
`);
|
|
3645
4629
|
}
|
|
3646
4630
|
const journalsDir = getJournalsDir();
|
|
3647
4631
|
const remotePath = `projects/${project}.md`;
|
|
3648
|
-
const localPath =
|
|
4632
|
+
const localPath = join17(journalsDir, `${project}.md`);
|
|
3649
4633
|
const effectiveRepo = isRefresh && !args.repo ? existing.journal.repo : repo;
|
|
3650
4634
|
const config = existing ?? {
|
|
3651
4635
|
journal: { repo: effectiveRepo, projects: {} }
|
|
@@ -3656,9 +4640,9 @@ var init_init2 = __esm(() => {
|
|
|
3656
4640
|
local_path: localPath
|
|
3657
4641
|
};
|
|
3658
4642
|
ensureDoravalDirs();
|
|
3659
|
-
ui.write(` ${
|
|
4643
|
+
ui.write(` ${import_picocolors14.default.dim(import_picocolors14.default.gray("Fetching journal files from"))} ${import_picocolors14.default.gray(effectiveRepo)}${import_picocolors14.default.dim(import_picocolors14.default.gray("..."))}
|
|
3660
4644
|
`);
|
|
3661
|
-
const globalDest =
|
|
4645
|
+
const globalDest = join17(journalsDir, "global.md");
|
|
3662
4646
|
const refreshGlobalRes = await refreshLocalJournalFile(effectiveRepo, "global.md", globalDest);
|
|
3663
4647
|
let wroteGlobal;
|
|
3664
4648
|
if (!refreshGlobalRes.ok) {
|
|
@@ -3675,7 +4659,7 @@ var init_init2 = __esm(() => {
|
|
|
3675
4659
|
if (wroteGlobal) {
|
|
3676
4660
|
ui.success("global.md");
|
|
3677
4661
|
} else {
|
|
3678
|
-
ui.write(` ${
|
|
4662
|
+
ui.write(` ${import_picocolors14.default.dim("\xB7")} global.md ${import_picocolors14.default.dim("(not found \u2014 will be created on first sync)")}`);
|
|
3679
4663
|
await Bun.write(globalDest, `# Global Journal
|
|
3680
4664
|
|
|
3681
4665
|
Cross-project principles.
|
|
@@ -3697,7 +4681,7 @@ Cross-project principles.
|
|
|
3697
4681
|
if (wroteProject) {
|
|
3698
4682
|
ui.success(remotePath);
|
|
3699
4683
|
} else {
|
|
3700
|
-
ui.write(` ${
|
|
4684
|
+
ui.write(` ${import_picocolors14.default.dim("\xB7")} ${remotePath} ${import_picocolors14.default.dim("(not found \u2014 will be created on first sync)")}`);
|
|
3701
4685
|
await Bun.write(localPath, `# ${project} Journal
|
|
3702
4686
|
|
|
3703
4687
|
Project-specific decisions.
|
|
@@ -3705,13 +4689,13 @@ Project-specific decisions.
|
|
|
3705
4689
|
}
|
|
3706
4690
|
await writeConfig(config);
|
|
3707
4691
|
ui.write(`
|
|
3708
|
-
${
|
|
4692
|
+
${import_picocolors14.default.green("\u2713")} ${import_picocolors14.default.white("Journal ready for project")} ${import_picocolors14.default.bold(import_picocolors14.default.white(project))}.
|
|
3709
4693
|
`);
|
|
3710
4694
|
const existingAgent = (await readConfig())?.agent;
|
|
3711
4695
|
if (existingAgent?.command) {
|
|
3712
|
-
ui.write(` ${
|
|
4696
|
+
ui.write(` ${import_picocolors14.default.bold(import_picocolors14.default.white("Coding agent (already configured)"))}
|
|
3713
4697
|
`);
|
|
3714
|
-
ui.write(` Current: ${
|
|
4698
|
+
ui.write(` Current: ${import_picocolors14.default.dim(import_picocolors14.default.gray(existingAgent.command))} template: ${import_picocolors14.default.dim(import_picocolors14.default.gray(existingAgent.prompt_template || "(default)"))}
|
|
3715
4699
|
`);
|
|
3716
4700
|
const change = prompt(" Reconfigure / change the coding agent for on-the-fly enrichment? (y/N)", "n");
|
|
3717
4701
|
if (!/^y/i.test(String(change))) {
|
|
@@ -3721,16 +4705,16 @@ Project-specific decisions.
|
|
|
3721
4705
|
if (existingAgent)
|
|
3722
4706
|
cfg.agent = existingAgent;
|
|
3723
4707
|
await writeConfig(cfg);
|
|
3724
|
-
ui.write(` ${
|
|
4708
|
+
ui.write(` ${import_picocolors14.default.green("\u2713")} ${import_picocolors14.default.white("Try:")} ${import_picocolors14.default.dim(import_picocolors14.default.gray('dora journal add "short decision"'))}
|
|
3725
4709
|
`);
|
|
3726
4710
|
process.exit(0);
|
|
3727
4711
|
return;
|
|
3728
4712
|
}
|
|
3729
4713
|
ui.blank();
|
|
3730
4714
|
} else {
|
|
3731
|
-
ui.write(` ${
|
|
4715
|
+
ui.write(` ${import_picocolors14.default.bold(import_picocolors14.default.white("Coding agent for journal add"))}
|
|
3732
4716
|
`);
|
|
3733
|
-
ui.info(` When configured, ${
|
|
4717
|
+
ui.info(` When configured, ${import_picocolors14.default.dim(import_picocolors14.default.gray('dora journal add ".."'))} will use your agent to enrich entries with tags and rationale automatically.
|
|
3734
4718
|
`);
|
|
3735
4719
|
}
|
|
3736
4720
|
const common = [
|
|
@@ -3749,7 +4733,7 @@ Project-specific decisions.
|
|
|
3749
4733
|
}
|
|
3750
4734
|
}
|
|
3751
4735
|
let agentCmd = detected || "claude";
|
|
3752
|
-
ui.write(` Detected / default agent command: ${
|
|
4736
|
+
ui.write(` Detected / default agent command: ${import_picocolors14.default.dim(import_picocolors14.default.gray(agentCmd))}`);
|
|
3753
4737
|
agentCmd = prompt(" Agent command (the binary you run for prompts)", agentCmd);
|
|
3754
4738
|
let template = detected ? common.find((c) => c.name === detected)?.template || '-p "{{prompt}}" --output-format json' : '-p "{{prompt}}" --output-format json';
|
|
3755
4739
|
ui.info(` Prompt template (use {{prompt}} placeholder):`);
|
|
@@ -3761,11 +4745,11 @@ Project-specific decisions.
|
|
|
3761
4745
|
};
|
|
3762
4746
|
await writeConfig(finalConfig);
|
|
3763
4747
|
ui.write(`
|
|
3764
|
-
${
|
|
4748
|
+
${import_picocolors14.default.green("\u2713")} ${import_picocolors14.default.white("Agent configured.")}
|
|
3765
4749
|
`);
|
|
3766
|
-
ui.info(` Re-run ${
|
|
4750
|
+
ui.info(` Re-run ${import_picocolors14.default.dim(import_picocolors14.default.gray("dora init"))} anytime to change it.
|
|
3767
4751
|
`);
|
|
3768
|
-
ui.info(` Next: ${
|
|
4752
|
+
ui.info(` Next: ${import_picocolors14.default.dim(import_picocolors14.default.gray('dora journal add ".."'))}, ${import_picocolors14.default.dim(import_picocolors14.default.gray("dora journal list"))}, or ${import_picocolors14.default.dim(import_picocolors14.default.gray("dora journal update"))}.
|
|
3769
4753
|
`);
|
|
3770
4754
|
process.exit(0);
|
|
3771
4755
|
}
|
|
@@ -3777,7 +4761,7 @@ init_dist();
|
|
|
3777
4761
|
// package.json
|
|
3778
4762
|
var package_default = {
|
|
3779
4763
|
name: "@hacksmith/doraval",
|
|
3780
|
-
version: "0.2.
|
|
4764
|
+
version: "0.2.23",
|
|
3781
4765
|
author: "Saif",
|
|
3782
4766
|
repository: {
|
|
3783
4767
|
type: "git",
|
|
@@ -3838,7 +4822,7 @@ var package_default = {
|
|
|
3838
4822
|
};
|
|
3839
4823
|
|
|
3840
4824
|
// src/cli/index.ts
|
|
3841
|
-
var
|
|
4825
|
+
var import_picocolors15 = __toESM(require_picocolors(), 1);
|
|
3842
4826
|
var skill = defineCommand({
|
|
3843
4827
|
meta: {
|
|
3844
4828
|
name: "skill",
|
|
@@ -3875,12 +4859,26 @@ var claude = defineCommand({
|
|
|
3875
4859
|
description: "Claude Code-specific commands (packaging, scaffolding, distribution)"
|
|
3876
4860
|
},
|
|
3877
4861
|
subCommands: {
|
|
3878
|
-
new: () => Promise.resolve().then(() => (init_new(), exports_new)).then((m) => m.default)
|
|
4862
|
+
new: () => Promise.resolve().then(() => (init_new(), exports_new)).then((m) => m.default),
|
|
4863
|
+
bump: () => Promise.resolve().then(() => (init_bump(), exports_bump)).then((m) => m.default)
|
|
3879
4864
|
},
|
|
3880
4865
|
run() {
|
|
3881
4866
|
showUsage(claude);
|
|
3882
4867
|
}
|
|
3883
4868
|
});
|
|
4869
|
+
var codex = defineCommand({
|
|
4870
|
+
meta: {
|
|
4871
|
+
name: "codex",
|
|
4872
|
+
description: "Codex (OpenAI)-specific commands (packaging, scaffolding, distribution)"
|
|
4873
|
+
},
|
|
4874
|
+
subCommands: {
|
|
4875
|
+
new: () => Promise.resolve().then(() => (init_new2(), exports_new2)).then((m) => m.default),
|
|
4876
|
+
bump: () => Promise.resolve().then(() => (init_bump(), exports_bump)).then((m) => m.default)
|
|
4877
|
+
},
|
|
4878
|
+
run() {
|
|
4879
|
+
showUsage(codex);
|
|
4880
|
+
}
|
|
4881
|
+
});
|
|
3884
4882
|
var doraemonArt = `
|
|
3885
4883
|
\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
|
|
3886
4884
|
\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
|
|
@@ -3902,13 +4900,15 @@ var main = defineCommand({
|
|
|
3902
4900
|
subCommands: {
|
|
3903
4901
|
validate: () => Promise.resolve().then(() => (init_validate_top(), exports_validate_top)).then((m) => m.default),
|
|
3904
4902
|
init: () => Promise.resolve().then(() => (init_init2(), exports_init2)).then((m) => m.default),
|
|
4903
|
+
bump: () => Promise.resolve().then(() => (init_bump(), exports_bump)).then((m) => m.default),
|
|
3905
4904
|
skill: () => Promise.resolve(skill),
|
|
3906
4905
|
journal: () => Promise.resolve(journal),
|
|
3907
|
-
claude: () => Promise.resolve(claude)
|
|
4906
|
+
claude: () => Promise.resolve(claude),
|
|
4907
|
+
codex: () => Promise.resolve(codex)
|
|
3908
4908
|
},
|
|
3909
4909
|
run() {
|
|
3910
4910
|
console.log(`
|
|
3911
|
-
` +
|
|
4911
|
+
` + import_picocolors15.default.blue(doraemonArt) + `
|
|
3912
4912
|
`);
|
|
3913
4913
|
showUsage(main);
|
|
3914
4914
|
}
|