skill-tree 0.1.5 → 0.1.7

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.
Files changed (40) hide show
  1. package/dist/cli/index.js +1827 -1410
  2. package/dist/cli/index.js.map +1 -0
  3. package/dist/cli/index.mjs +9538 -1657
  4. package/dist/cli/index.mjs.map +1 -0
  5. package/dist/index.d.mts +318 -7
  6. package/dist/index.d.ts +318 -7
  7. package/dist/index.js +1539 -92
  8. package/dist/index.js.map +1 -0
  9. package/dist/index.mjs +9614 -70
  10. package/dist/index.mjs.map +1 -0
  11. package/package.json +3 -2
  12. package/dist/chunk-3SRB47JW.mjs +0 -8344
  13. package/dist/chunk-43YOKLZP.mjs +0 -6081
  14. package/dist/chunk-4AGZU52D.mjs +0 -7918
  15. package/dist/chunk-4OC5QFIF.mjs +0 -11267
  16. package/dist/chunk-55SMGVTP.mjs +0 -7126
  17. package/dist/chunk-6FX4IK4Z.mjs +0 -5368
  18. package/dist/chunk-7EGDKOHV.mjs +0 -9439
  19. package/dist/chunk-7LMOQW5H.mjs +0 -4893
  20. package/dist/chunk-7QIQJVNP.mjs +0 -14206
  21. package/dist/chunk-7VB4ZRZO.mjs +0 -7127
  22. package/dist/chunk-BPVRW25O.mjs +0 -6089
  23. package/dist/chunk-CI4476KM.mjs +0 -6607
  24. package/dist/chunk-DDXYQ74I.mjs +0 -13969
  25. package/dist/chunk-DQOFJXBX.mjs +0 -6595
  26. package/dist/chunk-E2CVK23F.mjs +0 -8751
  27. package/dist/chunk-F3YEUQAP.mjs +0 -654
  28. package/dist/chunk-FKJJ4RJG.mjs +0 -13874
  29. package/dist/chunk-II7DECZQ.mjs +0 -9111
  30. package/dist/chunk-INKVOZXK.mjs +0 -15898
  31. package/dist/chunk-K6NRCSAZ.mjs +0 -4355
  32. package/dist/chunk-OYHYXKXO.mjs +0 -7297
  33. package/dist/chunk-PDPN7FW7.mjs +0 -1045
  34. package/dist/chunk-TEUB6DZR.mjs +0 -6453
  35. package/dist/chunk-TWPEHDW4.mjs +0 -1067
  36. package/dist/chunk-Y54UK2J3.mjs +0 -13071
  37. package/dist/chunk-ZQVS7MQK.mjs +0 -6081
  38. package/dist/sqlite-OLU72GHB.mjs +0 -6
  39. package/dist/sqlite-XJRPMNAJ.mjs +0 -6
  40. package/dist/sync-BSWMMDA6.mjs +0 -14
package/dist/cli/index.js CHANGED
@@ -1777,7 +1777,7 @@ var init_sync = __esm({
1777
1777
  });
1778
1778
 
1779
1779
  // src/cli/index.ts
1780
- var import_commander38 = require("commander");
1780
+ var import_commander39 = require("commander");
1781
1781
 
1782
1782
  // src/types.ts
1783
1783
  function hasTaxonomySupport(storage) {
@@ -2852,8 +2852,214 @@ var SyncManager = class {
2852
2852
  }
2853
2853
  };
2854
2854
 
2855
- // src/serving/loadout-compiler.ts
2855
+ // src/serving/xml-utils.ts
2856
+ function escapeXml(text) {
2857
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
2858
+ }
2859
+ function getSkillSummary(skill, maxLength = 150) {
2860
+ if (skill.serving?.summary) {
2861
+ return skill.serving.summary;
2862
+ }
2863
+ const firstSentence = skill.description.split(/[.!?]/)[0];
2864
+ if (firstSentence.length <= maxLength) {
2865
+ return firstSentence;
2866
+ }
2867
+ return skill.description.substring(0, maxLength - 3) + "...";
2868
+ }
2869
+
2870
+ // src/serving/catalog-renderer.ts
2871
+ function hasCatalogSupport(storage) {
2872
+ const s = storage;
2873
+ return typeof s.getTaxonomyTree === "function" && typeof s.getSkillsInTaxonomyNode === "function";
2874
+ }
2875
+ var UNTAGGED_CATEGORY = "other";
2876
+ var CATALOG_USAGE_LINES = [
2877
+ " Use skill_browse to explore categories.",
2878
+ " Use skill_search to find by keyword.",
2879
+ " Use skill_expand to load a skill into your context."
2880
+ ];
2856
2881
  var DEFAULT_CONFIG = {
2882
+ maxCategoriesPerLevel: 12,
2883
+ maxSkillsAtLeaf: 8,
2884
+ maxSummaryLength: 80,
2885
+ format: "xml"
2886
+ };
2887
+ var CatalogRenderer = class _CatalogRenderer {
2888
+ constructor(storage, config2) {
2889
+ this.storage = storage;
2890
+ this.overviewCache = null;
2891
+ this.config = { ...DEFAULT_CONFIG, ...config2 };
2892
+ }
2893
+ static {
2894
+ this.CACHE_TTL_MS = 6e4;
2895
+ }
2896
+ /**
2897
+ * Render level-0 catalog overview for system prompt injection.
2898
+ * Shows top-level categories with counts. ~200 tokens.
2899
+ * Returns empty string if no skills exist.
2900
+ * Results are cached for 60 seconds to avoid repeated storage queries.
2901
+ */
2902
+ async renderOverview() {
2903
+ if (this.overviewCache && Date.now() - this.overviewCache.timestamp < _CatalogRenderer.CACHE_TTL_MS) {
2904
+ return this.overviewCache.value;
2905
+ }
2906
+ let result;
2907
+ if (hasCatalogSupport(this.storage)) {
2908
+ const tree = await this.storage.getTaxonomyTree();
2909
+ if (tree.length > 0) {
2910
+ result = this.renderOverviewFromNodes(tree);
2911
+ this.overviewCache = { value: result, timestamp: Date.now() };
2912
+ return result;
2913
+ }
2914
+ }
2915
+ result = await this.renderOverviewFromTags();
2916
+ this.overviewCache = { value: result, timestamp: Date.now() };
2917
+ return result;
2918
+ }
2919
+ /**
2920
+ * Render a specific category path for browse drill-down.
2921
+ * Shows subcategories at intermediate nodes, or skill summaries at leaf nodes.
2922
+ */
2923
+ async renderCategory(path18) {
2924
+ if (hasCatalogSupport(this.storage)) {
2925
+ return this.renderCategoryFromTaxonomy(this.storage, path18);
2926
+ }
2927
+ return this.renderCategoryFromTags(path18);
2928
+ }
2929
+ /**
2930
+ * Invalidate the overview cache (e.g., after skill changes).
2931
+ */
2932
+ invalidateCache() {
2933
+ this.overviewCache = null;
2934
+ }
2935
+ // ===========================================================================
2936
+ // OVERVIEW RENDERING (shared structure)
2937
+ // ===========================================================================
2938
+ /**
2939
+ * Render the overview XML shell with a list of categories.
2940
+ * Used by both taxonomy-based and tag-based paths.
2941
+ */
2942
+ renderOverviewXml(totalSkills, categories) {
2943
+ const lines = [];
2944
+ lines.push(`<skill_catalog total="${totalSkills}">`);
2945
+ lines.push(" <usage>");
2946
+ lines.push(...CATALOG_USAGE_LINES);
2947
+ lines.push(" </usage>");
2948
+ lines.push(" <categories>");
2949
+ for (const cat of categories) {
2950
+ lines.push(` <category path="${escapeXml(cat.name)}" count="${cat.count}" />`);
2951
+ }
2952
+ lines.push(" </categories>");
2953
+ lines.push("</skill_catalog>");
2954
+ return lines.join("\n");
2955
+ }
2956
+ // ===========================================================================
2957
+ // TAXONOMY-BASED RENDERING
2958
+ // ===========================================================================
2959
+ renderOverviewFromNodes(roots) {
2960
+ const counted = this.withCounts(roots);
2961
+ const totalSkills = counted.reduce((sum, c) => sum + c.count, 0);
2962
+ const categories = counted.sort((a, b) => b.count - a.count).slice(0, this.config.maxCategoriesPerLevel).map((c) => ({ name: c.node.name, count: c.count }));
2963
+ return this.renderOverviewXml(totalSkills, categories);
2964
+ }
2965
+ async renderCategoryFromTaxonomy(storage, path18) {
2966
+ const tree = await storage.getTaxonomyTree(path18);
2967
+ const pathStr = path18.join("/");
2968
+ if (tree.length > 0 && tree.some((n) => n.children.length > 0)) {
2969
+ const root = tree[0];
2970
+ const rootCount = this.countNodeSkills(root);
2971
+ const counted = this.withCounts(root.children);
2972
+ const children = counted.sort((a, b) => b.count - a.count).slice(0, this.config.maxCategoriesPerLevel);
2973
+ const lines = [];
2974
+ lines.push(`<catalog_browse path="${escapeXml(pathStr)}" count="${rootCount}">`);
2975
+ lines.push(" <subcategories>");
2976
+ for (const { node: child, count } of children) {
2977
+ const childPath = [...path18, child.name].join("/");
2978
+ lines.push(` <category path="${escapeXml(childPath)}" count="${count}" />`);
2979
+ }
2980
+ lines.push(" </subcategories>");
2981
+ lines.push("</catalog_browse>");
2982
+ return lines.join("\n");
2983
+ }
2984
+ if (tree.length > 0) {
2985
+ const nodeId = tree[0].id;
2986
+ const skills = await storage.getSkillsInTaxonomyNode(nodeId);
2987
+ return this.renderLeafSkills(skills, pathStr, tree[0].skillCount);
2988
+ }
2989
+ return `<catalog_browse path="${escapeXml(pathStr)}" count="0" />`;
2990
+ }
2991
+ // ===========================================================================
2992
+ // TAG-BASED FALLBACK
2993
+ // ===========================================================================
2994
+ async renderOverviewFromTags() {
2995
+ const skills = await this.storage.listSkills({ status: ["active"] });
2996
+ if (skills.length === 0) return "";
2997
+ const tagCounts = this.groupByPrimaryTag(skills);
2998
+ const categories = Array.from(tagCounts.entries()).sort((a, b) => b[1] - a[1]).slice(0, this.config.maxCategoriesPerLevel).map(([name, count]) => ({ name, count }));
2999
+ return this.renderOverviewXml(skills.length, categories);
3000
+ }
3001
+ async renderCategoryFromTags(path18) {
3002
+ if (path18.length === 0) {
3003
+ return this.renderOverviewFromTags();
3004
+ }
3005
+ const tag = path18[0];
3006
+ const matching = await this.storage.listSkills({ status: ["active"], tags: [tag] });
3007
+ const pathStr = path18.join("/");
3008
+ return this.renderLeafSkills(matching, pathStr, matching.length);
3009
+ }
3010
+ // ===========================================================================
3011
+ // SHARED RENDERING HELPERS
3012
+ // ===========================================================================
3013
+ renderLeafSkills(skills, pathStr, totalCount) {
3014
+ const displayed = skills.slice(0, this.config.maxSkillsAtLeaf);
3015
+ const remaining = totalCount - displayed.length;
3016
+ const lines = [];
3017
+ lines.push(`<catalog_browse path="${escapeXml(pathStr)}" count="${totalCount}">`);
3018
+ lines.push(" <skills>");
3019
+ for (const skill of displayed) {
3020
+ const tags = skill.tags.length > 0 ? ` tags="${escapeXml(skill.tags.join(", "))}"` : "";
3021
+ const desc = getSkillSummary(skill, this.config.maxSummaryLength);
3022
+ lines.push(` <skill id="${escapeXml(skill.id)}"${tags}>`);
3023
+ lines.push(` ${escapeXml(desc)}`);
3024
+ lines.push(" </skill>");
3025
+ }
3026
+ lines.push(" </skills>");
3027
+ if (remaining > 0) {
3028
+ lines.push(` <more remaining="${remaining}">`);
3029
+ lines.push(" Use skill_search for more, or skill_expand {id} to load.");
3030
+ lines.push(" </more>");
3031
+ }
3032
+ lines.push("</catalog_browse>");
3033
+ return lines.join("\n");
3034
+ }
3035
+ // ===========================================================================
3036
+ // UTILITIES
3037
+ // ===========================================================================
3038
+ groupByPrimaryTag(skills) {
3039
+ const tagCounts = /* @__PURE__ */ new Map();
3040
+ for (const skill of skills) {
3041
+ const tag = skill.tags[0] || UNTAGGED_CATEGORY;
3042
+ tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
3043
+ }
3044
+ return tagCounts;
3045
+ }
3046
+ /**
3047
+ * Pre-compute counts for a list of nodes, avoiding redundant recursive walks.
3048
+ */
3049
+ withCounts(nodes) {
3050
+ return nodes.map((node) => ({ node, count: this.countNodeSkills(node) }));
3051
+ }
3052
+ countNodeSkills(node) {
3053
+ let count = node.skillCount;
3054
+ for (const child of node.children) {
3055
+ count += this.countNodeSkills(child);
3056
+ }
3057
+ return count;
3058
+ }
3059
+ };
3060
+
3061
+ // src/serving/loadout-compiler.ts
3062
+ var DEFAULT_CONFIG2 = {
2857
3063
  defaultMaxSkills: 15,
2858
3064
  defaultStatus: ["active"],
2859
3065
  semanticThreshold: 0.6
@@ -2862,7 +3068,7 @@ var LoadoutCompiler = class {
2862
3068
  constructor(storage, config2) {
2863
3069
  this.storage = storage;
2864
3070
  this.config = {
2865
- ...DEFAULT_CONFIG,
3071
+ ...DEFAULT_CONFIG2,
2866
3072
  ...config2
2867
3073
  };
2868
3074
  }
@@ -3083,11 +3289,76 @@ var LoadoutCompiler = class {
3083
3289
  // src/serving/project-detector.ts
3084
3290
  var import_fs = require("fs");
3085
3291
  var import_path = require("path");
3086
- var _ProjectDetector = class _ProjectDetector {
3292
+ var ProjectDetector = class _ProjectDetector {
3087
3293
  constructor() {
3088
3294
  /** Cache for project context */
3089
3295
  this.cache = /* @__PURE__ */ new Map();
3090
3296
  }
3297
+ static {
3298
+ /** Project type patterns */
3299
+ this.PROJECT_TYPES = [
3300
+ { manifestFile: "package.json", type: "nodejs", tags: ["nodejs", "javascript"], packageManager: "npm" },
3301
+ { manifestFile: "pyproject.toml", type: "python", tags: ["python"], packageManager: "pip" },
3302
+ { manifestFile: "requirements.txt", type: "python", tags: ["python"], packageManager: "pip" },
3303
+ { manifestFile: "Cargo.toml", type: "rust", tags: ["rust"], packageManager: "cargo" },
3304
+ { manifestFile: "go.mod", type: "go", tags: ["go", "golang"] },
3305
+ { manifestFile: "pom.xml", type: "java", tags: ["java", "maven"], packageManager: "maven" },
3306
+ { manifestFile: "build.gradle", type: "java", tags: ["java", "gradle"], packageManager: "gradle" },
3307
+ { manifestFile: "build.gradle.kts", type: "kotlin", tags: ["kotlin", "gradle"], packageManager: "gradle" }
3308
+ ];
3309
+ }
3310
+ static {
3311
+ /** TypeScript detection */
3312
+ this.TYPESCRIPT_FILES = ["tsconfig.json", "tsconfig.base.json"];
3313
+ }
3314
+ static {
3315
+ /** Node.js framework patterns */
3316
+ this.NODE_FRAMEWORKS = [
3317
+ { name: "react", packageName: "react", tags: ["react", "frontend"] },
3318
+ { name: "next", packageName: "next", tags: ["nextjs", "react", "fullstack"] },
3319
+ { name: "vue", packageName: "vue", tags: ["vue", "frontend"] },
3320
+ { name: "nuxt", packageName: "nuxt", tags: ["nuxt", "vue", "fullstack"] },
3321
+ { name: "angular", packageName: "@angular/core", tags: ["angular", "frontend"] },
3322
+ { name: "svelte", packageName: "svelte", tags: ["svelte", "frontend"] },
3323
+ { name: "express", packageName: "express", tags: ["express", "backend", "api"] },
3324
+ { name: "fastify", packageName: "fastify", tags: ["fastify", "backend", "api"] },
3325
+ { name: "nestjs", packageName: "@nestjs/core", tags: ["nestjs", "backend", "api"] },
3326
+ { name: "hono", packageName: "hono", tags: ["hono", "backend", "api"] },
3327
+ { name: "prisma", packageName: "@prisma/client", tags: ["prisma", "database", "orm"] },
3328
+ { name: "drizzle", packageName: "drizzle-orm", tags: ["drizzle", "database", "orm"] },
3329
+ { name: "typeorm", packageName: "typeorm", tags: ["typeorm", "database", "orm"] },
3330
+ { name: "jest", packageName: "jest", tags: ["testing", "jest"] },
3331
+ { name: "vitest", packageName: "vitest", tags: ["testing", "vitest"] },
3332
+ { name: "playwright", packageName: "@playwright/test", tags: ["testing", "e2e", "playwright"] },
3333
+ { name: "cypress", packageName: "cypress", tags: ["testing", "e2e", "cypress"] }
3334
+ ];
3335
+ }
3336
+ static {
3337
+ /** Python framework patterns (from pyproject.toml or requirements.txt) */
3338
+ this.PYTHON_FRAMEWORKS = [
3339
+ { name: "fastapi", packageName: "fastapi", tags: ["fastapi", "backend", "api"] },
3340
+ { name: "django", packageName: "django", tags: ["django", "backend", "fullstack"] },
3341
+ { name: "flask", packageName: "flask", tags: ["flask", "backend", "api"] },
3342
+ { name: "sqlalchemy", packageName: "sqlalchemy", tags: ["sqlalchemy", "database", "orm"] },
3343
+ { name: "pytest", packageName: "pytest", tags: ["testing", "pytest"] },
3344
+ { name: "pydantic", packageName: "pydantic", tags: ["pydantic", "validation"] }
3345
+ ];
3346
+ }
3347
+ static {
3348
+ /** Directory patterns */
3349
+ this.DIRECTORY_PATTERNS = [
3350
+ { pattern: ".github/workflows", feature: "github-actions", tags: ["ci", "github-actions"] },
3351
+ { pattern: ".gitlab-ci.yml", feature: "gitlab-ci", tags: ["ci", "gitlab"] },
3352
+ { pattern: "Dockerfile", feature: "docker", tags: ["docker", "containers"] },
3353
+ { pattern: "docker-compose.yml", feature: "docker-compose", tags: ["docker", "containers"] },
3354
+ { pattern: "docker-compose.yaml", feature: "docker-compose", tags: ["docker", "containers"] },
3355
+ { pattern: "terraform", feature: "terraform", tags: ["terraform", "infrastructure"] },
3356
+ { pattern: "kubernetes", feature: "kubernetes", tags: ["kubernetes", "infrastructure"] },
3357
+ { pattern: "k8s", feature: "kubernetes", tags: ["kubernetes", "infrastructure"] },
3358
+ { pattern: ".env.example", feature: "env-config", tags: ["configuration"] },
3359
+ { pattern: "prisma/schema.prisma", feature: "prisma", tags: ["prisma", "database"] }
3360
+ ];
3361
+ }
3091
3362
  /**
3092
3363
  * Detect project context from a directory
3093
3364
  */
@@ -3249,71 +3520,15 @@ var _ProjectDetector = class _ProjectDetector {
3249
3520
  }
3250
3521
  }
3251
3522
  };
3252
- /** Project type patterns */
3253
- _ProjectDetector.PROJECT_TYPES = [
3254
- { manifestFile: "package.json", type: "nodejs", tags: ["nodejs", "javascript"], packageManager: "npm" },
3255
- { manifestFile: "pyproject.toml", type: "python", tags: ["python"], packageManager: "pip" },
3256
- { manifestFile: "requirements.txt", type: "python", tags: ["python"], packageManager: "pip" },
3257
- { manifestFile: "Cargo.toml", type: "rust", tags: ["rust"], packageManager: "cargo" },
3258
- { manifestFile: "go.mod", type: "go", tags: ["go", "golang"] },
3259
- { manifestFile: "pom.xml", type: "java", tags: ["java", "maven"], packageManager: "maven" },
3260
- { manifestFile: "build.gradle", type: "java", tags: ["java", "gradle"], packageManager: "gradle" },
3261
- { manifestFile: "build.gradle.kts", type: "kotlin", tags: ["kotlin", "gradle"], packageManager: "gradle" }
3262
- ];
3263
- /** TypeScript detection */
3264
- _ProjectDetector.TYPESCRIPT_FILES = ["tsconfig.json", "tsconfig.base.json"];
3265
- /** Node.js framework patterns */
3266
- _ProjectDetector.NODE_FRAMEWORKS = [
3267
- { name: "react", packageName: "react", tags: ["react", "frontend"] },
3268
- { name: "next", packageName: "next", tags: ["nextjs", "react", "fullstack"] },
3269
- { name: "vue", packageName: "vue", tags: ["vue", "frontend"] },
3270
- { name: "nuxt", packageName: "nuxt", tags: ["nuxt", "vue", "fullstack"] },
3271
- { name: "angular", packageName: "@angular/core", tags: ["angular", "frontend"] },
3272
- { name: "svelte", packageName: "svelte", tags: ["svelte", "frontend"] },
3273
- { name: "express", packageName: "express", tags: ["express", "backend", "api"] },
3274
- { name: "fastify", packageName: "fastify", tags: ["fastify", "backend", "api"] },
3275
- { name: "nestjs", packageName: "@nestjs/core", tags: ["nestjs", "backend", "api"] },
3276
- { name: "hono", packageName: "hono", tags: ["hono", "backend", "api"] },
3277
- { name: "prisma", packageName: "@prisma/client", tags: ["prisma", "database", "orm"] },
3278
- { name: "drizzle", packageName: "drizzle-orm", tags: ["drizzle", "database", "orm"] },
3279
- { name: "typeorm", packageName: "typeorm", tags: ["typeorm", "database", "orm"] },
3280
- { name: "jest", packageName: "jest", tags: ["testing", "jest"] },
3281
- { name: "vitest", packageName: "vitest", tags: ["testing", "vitest"] },
3282
- { name: "playwright", packageName: "@playwright/test", tags: ["testing", "e2e", "playwright"] },
3283
- { name: "cypress", packageName: "cypress", tags: ["testing", "e2e", "cypress"] }
3284
- ];
3285
- /** Python framework patterns (from pyproject.toml or requirements.txt) */
3286
- _ProjectDetector.PYTHON_FRAMEWORKS = [
3287
- { name: "fastapi", packageName: "fastapi", tags: ["fastapi", "backend", "api"] },
3288
- { name: "django", packageName: "django", tags: ["django", "backend", "fullstack"] },
3289
- { name: "flask", packageName: "flask", tags: ["flask", "backend", "api"] },
3290
- { name: "sqlalchemy", packageName: "sqlalchemy", tags: ["sqlalchemy", "database", "orm"] },
3291
- { name: "pytest", packageName: "pytest", tags: ["testing", "pytest"] },
3292
- { name: "pydantic", packageName: "pydantic", tags: ["pydantic", "validation"] }
3293
- ];
3294
- /** Directory patterns */
3295
- _ProjectDetector.DIRECTORY_PATTERNS = [
3296
- { pattern: ".github/workflows", feature: "github-actions", tags: ["ci", "github-actions"] },
3297
- { pattern: ".gitlab-ci.yml", feature: "gitlab-ci", tags: ["ci", "gitlab"] },
3298
- { pattern: "Dockerfile", feature: "docker", tags: ["docker", "containers"] },
3299
- { pattern: "docker-compose.yml", feature: "docker-compose", tags: ["docker", "containers"] },
3300
- { pattern: "docker-compose.yaml", feature: "docker-compose", tags: ["docker", "containers"] },
3301
- { pattern: "terraform", feature: "terraform", tags: ["terraform", "infrastructure"] },
3302
- { pattern: "kubernetes", feature: "kubernetes", tags: ["kubernetes", "infrastructure"] },
3303
- { pattern: "k8s", feature: "kubernetes", tags: ["kubernetes", "infrastructure"] },
3304
- { pattern: ".env.example", feature: "env-config", tags: ["configuration"] },
3305
- { pattern: "prisma/schema.prisma", feature: "prisma", tags: ["prisma", "database"] }
3306
- ];
3307
- var ProjectDetector = _ProjectDetector;
3308
3523
 
3309
3524
  // src/serving/view-renderer.ts
3310
- var DEFAULT_CONFIG2 = {
3525
+ var DEFAULT_CONFIG3 = {
3311
3526
  includeTokenEstimates: false,
3312
3527
  maxSummaryLength: 150
3313
3528
  };
3314
3529
  var ViewRenderer = class {
3315
3530
  constructor(config2) {
3316
- this.config = { ...DEFAULT_CONFIG2, ...config2 };
3531
+ this.config = { ...DEFAULT_CONFIG3, ...config2 };
3317
3532
  }
3318
3533
  /**
3319
3534
  * Render loadout state as OpenSkills-compatible XML
@@ -3340,6 +3555,18 @@ var ViewRenderer = class {
3340
3555
  lines.push(" <content>");
3341
3556
  lines.push(skill.instructions.split("\n").map((line) => " " + line).join("\n"));
3342
3557
  lines.push(" </content>");
3558
+ if (skill.relationships && skill.relationships.length > 0) {
3559
+ const hints = skill.relationships.filter((r) => r.confidence >= 0.5).sort((a, b) => b.confidence - a.confidence).slice(0, 5);
3560
+ if (hints.length > 0) {
3561
+ lines.push(" <related>");
3562
+ for (const rel of hints) {
3563
+ const relatedSkill = state.available.get(rel.targetSkillId);
3564
+ const desc = relatedSkill ? this.getSummary(relatedSkill) : rel.targetSkillId;
3565
+ lines.push(` <see_also id="${this.escapeXml(rel.targetSkillId)}" type="${rel.type}">${this.escapeXml(desc)}</see_also>`);
3566
+ }
3567
+ lines.push(" </related>");
3568
+ }
3569
+ }
3343
3570
  lines.push("</skill>");
3344
3571
  } else {
3345
3572
  const summary = this.getSummary(skill);
@@ -3452,23 +3679,18 @@ var ViewRenderer = class {
3452
3679
  // Private helpers
3453
3680
  // ===========================================================================
3454
3681
  /**
3455
- * Get summary for a skill (short description)
3682
+ * Get summary for a skill (short description).
3683
+ * Delegates to shared getSkillSummary utility.
3456
3684
  */
3457
3685
  getSummary(skill) {
3458
- if (skill.serving?.summary) {
3459
- return skill.serving.summary;
3460
- }
3461
- const firstSentence = skill.description.split(/[.!?]/)[0];
3462
- if (firstSentence.length <= this.config.maxSummaryLength) {
3463
- return firstSentence;
3464
- }
3465
- return skill.description.substring(0, this.config.maxSummaryLength - 3) + "...";
3686
+ return getSkillSummary(skill, this.config.maxSummaryLength);
3466
3687
  }
3467
3688
  /**
3468
- * Escape special XML characters
3689
+ * Escape special XML characters.
3690
+ * Delegates to shared escapeXml utility.
3469
3691
  */
3470
3692
  escapeXml(str) {
3471
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
3693
+ return escapeXml(str);
3472
3694
  }
3473
3695
  /**
3474
3696
  * Estimate tokens for a full skill
@@ -3558,7 +3780,7 @@ var builtInProfiles = {
3558
3780
  };
3559
3781
 
3560
3782
  // src/serving/graph-server.ts
3561
- var DEFAULT_CONFIG3 = {
3783
+ var DEFAULT_CONFIG4 = {
3562
3784
  agentCanModify: true,
3563
3785
  agentCanSetLoadout: false,
3564
3786
  agentCanSwitchProfile: true,
@@ -3570,24 +3792,29 @@ var DEFAULT_CONFIG3 = {
3570
3792
  persistState: false,
3571
3793
  outputFormat: "xml",
3572
3794
  includeTokenEstimates: false,
3795
+ enableCatalog: true,
3573
3796
  profiles: {}
3574
3797
  };
3575
3798
  var SkillGraphServer = class {
3576
3799
  // Track LRU for eviction
3577
3800
  constructor(storage, config2) {
3578
3801
  this.storage = storage;
3802
+ this.catalogRenderer = null;
3579
3803
  this.handlers = /* @__PURE__ */ new Set();
3580
3804
  this.lruOrder = [];
3581
3805
  this.config = {
3582
- ...DEFAULT_CONFIG3,
3806
+ ...DEFAULT_CONFIG4,
3583
3807
  ...config2,
3584
- profiles: { ...builtInProfiles, ...DEFAULT_CONFIG3.profiles, ...config2?.profiles }
3808
+ profiles: { ...builtInProfiles, ...DEFAULT_CONFIG4.profiles, ...config2?.profiles }
3585
3809
  };
3586
3810
  this.compiler = new LoadoutCompiler(storage);
3587
3811
  this.projectDetector = new ProjectDetector();
3588
3812
  this.viewRenderer = new ViewRenderer({
3589
3813
  includeTokenEstimates: this.config.includeTokenEstimates
3590
3814
  });
3815
+ if (this.config.enableCatalog !== false) {
3816
+ this.catalogRenderer = new CatalogRenderer(storage, config2?.catalogConfig);
3817
+ }
3591
3818
  this.state = {
3592
3819
  available: /* @__PURE__ */ new Map(),
3593
3820
  expanded: /* @__PURE__ */ new Set(),
@@ -3876,17 +4103,70 @@ var SkillGraphServer = class {
3876
4103
  profiles: this.getProfiles()
3877
4104
  };
3878
4105
  }
4106
+ /**
4107
+ * Agent browses the skill catalog at a given path.
4108
+ * Returns rendered category view (subcategories or skill summaries at leaf).
4109
+ * Pass no path for the top-level overview.
4110
+ */
4111
+ async agentBrowseCatalog(path18) {
4112
+ if (!this.catalogRenderer) {
4113
+ return "<error>Catalog browsing is not enabled</error>";
4114
+ }
4115
+ if (!path18 || path18.length === 0) {
4116
+ const result2 = await this.catalogRenderer.renderOverview();
4117
+ if (result2) {
4118
+ this.emit({ type: "catalog:browsed", path: [] });
4119
+ }
4120
+ return result2;
4121
+ }
4122
+ const result = await this.catalogRenderer.renderCategory(path18);
4123
+ this.emit({ type: "catalog:browsed", path: path18 });
4124
+ return result;
4125
+ }
4126
+ /**
4127
+ * Agent adds a skill discovered via browsing directly to loadout and expands it.
4128
+ * Bridges browse → loadout: found it in catalog, now load it.
4129
+ */
4130
+ async agentAddFromCatalog(skillId) {
4131
+ if (!this.config.agentCanModify) {
4132
+ return { added: false, pending: false, expanded: false };
4133
+ }
4134
+ if (this.state.available.has(skillId)) {
4135
+ const expanded = this.expandSkill(skillId);
4136
+ return { added: false, pending: false, expanded };
4137
+ }
4138
+ const result = await this.agentRequestSkills([skillId]);
4139
+ if (result.added.length > 0) {
4140
+ const expanded = this.expandSkill(skillId);
4141
+ this.emit({ type: "catalog:added", skillId });
4142
+ return { added: true, pending: false, expanded };
4143
+ }
4144
+ if (result.pending.length > 0) {
4145
+ return { added: false, pending: true, expanded: false };
4146
+ }
4147
+ return { added: false, pending: false, expanded: false };
4148
+ }
3879
4149
  // ===========================================================================
3880
4150
  // RENDERING
3881
4151
  // ===========================================================================
3882
4152
  /**
3883
- * Render current state as system prompt content
4153
+ * Render current state as system prompt content.
4154
+ * Includes catalog overview when catalog is enabled.
3884
4155
  */
3885
- renderSystemPrompt() {
4156
+ async renderSystemPrompt() {
4157
+ let prompt;
3886
4158
  if (this.config.outputFormat === "markdown") {
3887
- return this.viewRenderer.renderMarkdown(this.state);
4159
+ prompt = this.viewRenderer.renderMarkdown(this.state);
4160
+ } else {
4161
+ prompt = this.viewRenderer.renderXml(this.state);
4162
+ }
4163
+ if (this.catalogRenderer) {
4164
+ const overview = await this.catalogRenderer.renderOverview();
4165
+ if (overview) {
4166
+ prompt += "\n" + overview;
4167
+ }
3888
4168
  }
3889
- return this.viewRenderer.renderXml(this.state);
4169
+ return prompt;
3890
4170
  }
3891
4171
  /**
3892
4172
  * Estimate total tokens for current loadout
@@ -4257,7 +4537,7 @@ function resolveSkilltreeDir(repoRoot) {
4257
4537
  if (fs4.existsSync(swarmDir)) return swarmDir;
4258
4538
  return path4.join(repoRoot, ".skilltree");
4259
4539
  }
4260
- var DEFAULT_CONFIG4 = {
4540
+ var DEFAULT_CONFIG5 = {
4261
4541
  version: 1,
4262
4542
  discovery: "default",
4263
4543
  paths: ["skills"],
@@ -4285,7 +4565,7 @@ function getSkilltreeDir(repoRoot) {
4285
4565
  }
4286
4566
  function getSkillsDir(repoRoot, config2) {
4287
4567
  const skilltreeDir = getSkilltreeDir(repoRoot);
4288
- const paths = config2?.paths || DEFAULT_CONFIG4.paths;
4568
+ const paths = config2?.paths || DEFAULT_CONFIG5.paths;
4289
4569
  return path4.join(skilltreeDir, paths[0]);
4290
4570
  }
4291
4571
  function parseSkilltreeConfig(content) {
@@ -4298,10 +4578,10 @@ function validateConfig(config2) {
4298
4578
  }
4299
4579
  return {
4300
4580
  version: 1,
4301
- discovery: config2.discovery || DEFAULT_CONFIG4.discovery,
4302
- paths: config2.paths || DEFAULT_CONFIG4.paths,
4303
- exclude: [...DEFAULT_CONFIG4.exclude || [], ...config2.exclude || []],
4304
- skillFilePatterns: config2.skillFilePatterns || DEFAULT_CONFIG4.skillFilePatterns,
4581
+ discovery: config2.discovery || DEFAULT_CONFIG5.discovery,
4582
+ paths: config2.paths || DEFAULT_CONFIG5.paths,
4583
+ exclude: [...DEFAULT_CONFIG5.exclude || [], ...config2.exclude || []],
4584
+ skillFilePatterns: config2.skillFilePatterns || DEFAULT_CONFIG5.skillFilePatterns,
4305
4585
  namespace: config2.namespace
4306
4586
  };
4307
4587
  }
@@ -4312,7 +4592,7 @@ async function loadSkilltreeConfig(repoRoot) {
4312
4592
  return parseSkilltreeConfig(content);
4313
4593
  } catch (error) {
4314
4594
  if (error.code === "ENOENT") {
4315
- return { ...DEFAULT_CONFIG4 };
4595
+ return { ...DEFAULT_CONFIG5 };
4316
4596
  }
4317
4597
  throw error;
4318
4598
  }
@@ -4347,7 +4627,7 @@ async function initSkilltreeDir(repoRoot, config2) {
4347
4627
  );
4348
4628
  }
4349
4629
  }
4350
- function isSkillFile(filename, patterns = DEFAULT_CONFIG4.skillFilePatterns) {
4630
+ function isSkillFile(filename, patterns = DEFAULT_CONFIG5.skillFilePatterns) {
4351
4631
  const lower = filename.toLowerCase();
4352
4632
  for (const pattern of patterns) {
4353
4633
  if (pattern.includes("*")) {
@@ -4380,7 +4660,7 @@ async function discoverSkills(repoRoot) {
4380
4660
  if (config2.discovery === "scan") {
4381
4661
  await scanForSkills(skilltreeDir, skilltreeDir, config2, discovered);
4382
4662
  } else {
4383
- const paths = config2.paths || DEFAULT_CONFIG4.paths;
4663
+ const paths = config2.paths || DEFAULT_CONFIG5.paths;
4384
4664
  for (const searchPath of paths) {
4385
4665
  const fullPath = path4.join(skilltreeDir, searchPath);
4386
4666
  await scanForSkills(skilltreeDir, fullPath, config2, discovered);
@@ -7496,11 +7776,12 @@ function createDefaultSyncConfig(remoteUrl, agentId, options) {
7496
7776
  };
7497
7777
  }
7498
7778
 
7499
- // src/index.ts
7500
- var VERSION = "0.1.0";
7779
+ // src/services/indexer.ts
7780
+ var path12 = __toESM(require("path"));
7781
+ var fs12 = __toESM(require("fs"));
7501
7782
 
7502
7783
  // src/config/types.ts
7503
- var DEFAULT_CONFIG5 = {
7784
+ var DEFAULT_CONFIG6 = {
7504
7785
  storage: {
7505
7786
  path: "~/.skill-tree"
7506
7787
  },
@@ -7701,7 +7982,7 @@ var ConfigLoader = class {
7701
7982
  constructor(configPath) {
7702
7983
  this.loaded = false;
7703
7984
  this.configPath = configPath || getConfigPath();
7704
- this.config = { ...DEFAULT_CONFIG5 };
7985
+ this.config = { ...DEFAULT_CONFIG6 };
7705
7986
  }
7706
7987
  /**
7707
7988
  * Load configuration from all sources
@@ -7714,7 +7995,7 @@ var ConfigLoader = class {
7714
7995
  if (this.loaded) {
7715
7996
  return this.config;
7716
7997
  }
7717
- let config2 = deepMerge({}, DEFAULT_CONFIG5);
7998
+ let config2 = deepMerge({}, DEFAULT_CONFIG6);
7718
7999
  const fileConfig = loadConfigFromFile(this.configPath);
7719
8000
  if (fileConfig) {
7720
8001
  config2 = deepMerge(config2, fileConfig);
@@ -7828,1426 +8109,1538 @@ function loadConfig(configPath) {
7828
8109
  return getConfigLoader(configPath).load();
7829
8110
  }
7830
8111
 
7831
- // src/cli/commands/list.ts
7832
- var import_commander = require("commander");
7833
-
7834
- // src/cli/utils/paths.ts
7835
- var fs12 = __toESM(require("fs"));
7836
- var path12 = __toESM(require("path"));
7837
- var os2 = __toESM(require("os"));
7838
- var DEFAULT_PATHS = [
7839
- ".claude/skills",
7840
- // Project local (Claude)
7841
- ".agent/skills",
7842
- // Project local (universal)
7843
- "~/.claude/skills",
7844
- // User global (Claude)
7845
- "~/.agent/skills"
7846
- // User global (universal)
7847
- ];
7848
- function expandHome(p) {
7849
- if (p.startsWith("~/")) {
7850
- return path12.join(os2.homedir(), p.slice(2));
8112
+ // src/import/converter.ts
8113
+ function convertIndexerSkill(indexerSkill) {
8114
+ const warnings = [];
8115
+ const instructions = indexerSkill.content || "";
8116
+ const hasStructuredContent = instructions.trim().length > 0;
8117
+ if (!hasStructuredContent) {
8118
+ warnings.push("No content found, instructions will be empty");
7851
8119
  }
7852
- return p;
7853
- }
7854
- function dirExists(p) {
7855
- try {
7856
- return fs12.statSync(p).isDirectory();
7857
- } catch {
7858
- return false;
8120
+ const tags = [...indexerSkill.tags || []];
8121
+ if (indexerSkill.sourceRepo) {
8122
+ const repoName = indexerSkill.sourceRepo.split("/").pop();
8123
+ if (repoName && !tags.includes(repoName)) {
8124
+ tags.push(repoName);
8125
+ }
7859
8126
  }
8127
+ const status = indexerSkill.status === "indexed" ? "active" : indexerSkill.status === "raw" ? "draft" : "draft";
8128
+ const skill = {
8129
+ id: indexerSkill.slug,
8130
+ name: indexerSkill.displayName || indexerSkill.name,
8131
+ version: indexerSkill.version,
8132
+ description: indexerSkill.description,
8133
+ instructions,
8134
+ author: indexerSkill.author,
8135
+ tags,
8136
+ createdAt: new Date(indexerSkill.scrapedAt),
8137
+ updatedAt: new Date(indexerSkill.updatedAt),
8138
+ status,
8139
+ metrics: {
8140
+ usageCount: 0,
8141
+ successRate: 0,
8142
+ feedbackScores: []
8143
+ },
8144
+ source: {
8145
+ type: "imported",
8146
+ location: indexerSkill.sourceUrl,
8147
+ importedAt: /* @__PURE__ */ new Date()
8148
+ },
8149
+ // Extended fields for indexed skills
8150
+ taxonomy: indexerSkill.primaryPath ? {
8151
+ primaryPath: indexerSkill.primaryPath,
8152
+ secondaryPaths: indexerSkill.secondaryPaths,
8153
+ confidence: indexerSkill.confidence
8154
+ } : void 0,
8155
+ externalSource: {
8156
+ url: indexerSkill.sourceUrl,
8157
+ repo: indexerSkill.sourceRepo,
8158
+ scrapedAt: new Date(indexerSkill.scrapedAt)
8159
+ }
8160
+ };
8161
+ return { skill, warnings, hasStructuredContent };
7860
8162
  }
7861
- function resolveSkillPath(explicitPath) {
7862
- if (explicitPath) {
7863
- const resolved = expandHome(explicitPath);
7864
- return path12.resolve(resolved);
7865
- }
7866
- const envPath = process.env.SKILL_TREE_PATH;
7867
- if (envPath) {
7868
- return path12.resolve(expandHome(envPath));
8163
+ function convertIndexerSkills(skills) {
8164
+ const results = [];
8165
+ const converted = [];
8166
+ for (const skill of skills) {
8167
+ const result = convertIndexerSkill(skill);
8168
+ results.push(result);
8169
+ converted.push(result.skill);
7869
8170
  }
7870
- for (const defaultPath of DEFAULT_PATHS) {
7871
- const resolved = path12.resolve(expandHome(defaultPath));
7872
- if (dirExists(resolved)) {
7873
- return resolved;
8171
+ return {
8172
+ skills: converted,
8173
+ results,
8174
+ stats: {
8175
+ total: results.length,
8176
+ withStructuredContent: results.filter((r) => r.hasStructuredContent).length,
8177
+ withWarnings: results.filter((r) => r.warnings.length > 0).length
7874
8178
  }
7875
- }
7876
- return path12.resolve(".claude/skills");
8179
+ };
7877
8180
  }
7878
- function ensureDir(dir) {
7879
- if (!dirExists(dir)) {
7880
- fs12.mkdirSync(dir, { recursive: true });
8181
+ function parseIndexerExport(content) {
8182
+ const data = JSON.parse(content);
8183
+ let indexerSkills;
8184
+ let exportMetadata;
8185
+ if (Array.isArray(data)) {
8186
+ indexerSkills = data;
8187
+ } else if (data.skills && Array.isArray(data.skills)) {
8188
+ indexerSkills = data.skills;
8189
+ exportMetadata = {
8190
+ exportedAt: data.exportedAt,
8191
+ originalStats: data.stats
8192
+ };
8193
+ } else {
8194
+ throw new Error("Invalid indexer export format: expected array or object with skills array");
7881
8195
  }
8196
+ const { skills, stats } = convertIndexerSkills(indexerSkills);
8197
+ return { skills, stats, exportMetadata };
7882
8198
  }
7883
8199
 
7884
- // src/cli/utils/skillbank.ts
7885
- function getMaterializationConfig() {
7886
- const config2 = loadConfig();
7887
- const mat = config2.materialization;
7888
- if (!mat?.enabled) return void 0;
7889
- return {
7890
- enabled: true,
7891
- mode: mat.mode ?? "symlink",
7892
- symlinkPaths: mat.symlink_paths?.length ? mat.symlink_paths : void 0,
7893
- agentsMdPath: mat.agents_md_path || void 0,
7894
- agentsMdFormat: mat.agents_md_format ?? "xml",
7895
- debounceMs: mat.debounce_ms ?? 500
7896
- };
8200
+ // src/services/indexer.ts
8201
+ function hasIndexerSupport(storage) {
8202
+ const s = storage;
8203
+ return typeof s.addRelationship === "function" && typeof s.getTaxonomyTree === "function";
7897
8204
  }
7898
- async function createSkillBankFromOptions(options) {
7899
- const skillPath = resolveSkillPath(options.path);
7900
- ensureDir(skillPath);
7901
- const skillBank = new SkillBank({
7902
- storage: {
7903
- basePath: skillPath,
7904
- openSkillsCompatible: true
7905
- },
7906
- materialization: getMaterializationConfig()
7907
- });
7908
- await skillBank.initialize();
7909
- return skillBank;
7910
- }
7911
- function getSkillPath(options) {
7912
- return resolveSkillPath(options.path);
7913
- }
7914
- var getSkillBank = createSkillBankFromOptions;
7915
-
7916
- // src/cli/utils/output.ts
7917
- var import_chalk = __toESM(require("chalk"));
7918
- function formatSkillLine(skill) {
7919
- const status = formatStatus(skill.status);
7920
- const version = import_chalk.default.dim(`v${skill.version}`);
7921
- const id = import_chalk.default.bold(skill.id);
7922
- const name = skill.name !== skill.id ? import_chalk.default.dim(` (${skill.name})`) : "";
7923
- const tags = skill.tags.length > 0 ? import_chalk.default.dim(` [${skill.tags.join(", ")}]`) : "";
7924
- return `${status} ${id}${name} ${version}${tags}`;
7925
- }
7926
- function formatStatus(status) {
7927
- switch (status) {
7928
- case "active":
7929
- return import_chalk.default.green("\u25CF");
7930
- case "draft":
7931
- return import_chalk.default.yellow("\u25CB");
7932
- case "deprecated":
7933
- return import_chalk.default.red("\u2717");
7934
- case "experimental":
7935
- return import_chalk.default.blue("\u25D0");
7936
- default:
7937
- return import_chalk.default.gray("?");
7938
- }
7939
- }
7940
- function formatSkillDetail(skill) {
7941
- const lines = [];
7942
- lines.push(import_chalk.default.bold(`${skill.name} v${skill.version}`));
7943
- lines.push(import_chalk.default.dim("\u2500".repeat(50)));
7944
- lines.push(`${import_chalk.default.dim("Status:")} ${formatStatusLabel(skill.status)}`);
7945
- lines.push(`${import_chalk.default.dim("Author:")} ${skill.author}`);
7946
- if (skill.tags.length > 0) {
7947
- lines.push(`${import_chalk.default.dim("Tags:")} ${skill.tags.join(", ")}`);
7948
- }
7949
- if (skill.parentVersion) {
7950
- lines.push(`${import_chalk.default.dim("Parent:")} v${skill.parentVersion}`);
7951
- }
7952
- if (skill.derivedFrom && skill.derivedFrom.length > 0) {
7953
- lines.push(`${import_chalk.default.dim("Derived:")} ${skill.derivedFrom.join(", ")}`);
7954
- }
7955
- const { usageCount, successRate } = skill.metrics;
7956
- if (usageCount > 0) {
7957
- lines.push(`${import_chalk.default.dim("Usage:")} ${usageCount} times, ${Math.round(successRate * 100)}% success`);
7958
- }
7959
- lines.push("");
7960
- lines.push(import_chalk.default.dim("Description"));
7961
- lines.push(import_chalk.default.dim("\u2500".repeat(50)));
7962
- lines.push(skill.description);
7963
- lines.push("");
7964
- if (skill.instructions) {
7965
- lines.push(import_chalk.default.dim("Instructions"));
7966
- lines.push(import_chalk.default.dim("\u2500".repeat(50)));
7967
- lines.push(skill.instructions);
7968
- }
7969
- return lines.join("\n");
7970
- }
7971
- function formatStatusLabel(status) {
7972
- switch (status) {
7973
- case "active":
7974
- return import_chalk.default.green("active");
7975
- case "draft":
7976
- return import_chalk.default.yellow("draft");
7977
- case "deprecated":
7978
- return import_chalk.default.red("deprecated");
7979
- case "experimental":
7980
- return import_chalk.default.blue("experimental");
7981
- default:
7982
- return import_chalk.default.gray(status);
7983
- }
7984
- }
7985
- function formatVersionHistory(versions) {
7986
- const lines = [];
7987
- lines.push(import_chalk.default.bold("Version History"));
7988
- lines.push(import_chalk.default.dim("\u2500".repeat(50)));
7989
- for (const v of versions.reverse()) {
7990
- const date = v.createdAt.toISOString().split("T")[0];
7991
- const hash = import_chalk.default.dim(`#${v.contentHash.slice(0, 7)}`);
7992
- const changelog = v.changelog ? ` - ${v.changelog}` : "";
7993
- lines.push(` ${import_chalk.default.cyan(`v${v.version}`)} ${import_chalk.default.dim(date)} ${hash}${changelog}`);
7994
- }
7995
- return lines.join("\n");
7996
- }
7997
- function formatDiff(versionA, versionB, changes) {
7998
- const lines = [];
7999
- lines.push(import_chalk.default.bold(`Diff: v${versionA} \u2192 v${versionB}`));
8000
- lines.push(import_chalk.default.dim("\u2500".repeat(50)));
8001
- if (changes.modified.length === 0 && changes.added.length === 0 && changes.removed.length === 0) {
8002
- lines.push(import_chalk.default.dim("No changes"));
8003
- return lines.join("\n");
8004
- }
8005
- for (const mod of changes.modified) {
8006
- lines.push(import_chalk.default.yellow(`~ ${mod.field}`));
8007
- lines.push(import_chalk.default.red(` - ${truncate(mod.oldValue, 60)}`));
8008
- lines.push(import_chalk.default.green(` + ${truncate(mod.newValue, 60)}`));
8009
- }
8010
- for (const add of changes.added) {
8011
- lines.push(import_chalk.default.green(`+ ${add.type}: ${add.value}`));
8012
- }
8013
- for (const rem of changes.removed) {
8014
- lines.push(import_chalk.default.red(`- ${rem.type}: ${rem.value}`));
8015
- }
8016
- return lines.join("\n");
8017
- }
8018
- function formatStats(stats) {
8019
- const lines = [];
8020
- lines.push(import_chalk.default.bold("Skill Bank Statistics"));
8021
- lines.push(import_chalk.default.dim("\u2500".repeat(50)));
8022
- lines.push(`${import_chalk.default.dim("Total skills:")} ${stats.totalSkills}`);
8023
- lines.push("");
8024
- lines.push(import_chalk.default.dim("By Status:"));
8025
- lines.push(` ${import_chalk.default.green("\u25CF")} Active: ${stats.byStatus.active}`);
8026
- lines.push(` ${import_chalk.default.yellow("\u25CB")} Draft: ${stats.byStatus.draft}`);
8027
- lines.push(` ${import_chalk.default.blue("\u25D0")} Experimental: ${stats.byStatus.experimental}`);
8028
- lines.push(` ${import_chalk.default.red("\u2717")} Deprecated: ${stats.byStatus.deprecated}`);
8029
- lines.push("");
8030
- if (Object.keys(stats.byTag).length > 0) {
8031
- lines.push(import_chalk.default.dim("By Tag:"));
8032
- const sortedTags = Object.entries(stats.byTag).sort((a, b) => b[1] - a[1]);
8033
- for (const [tag, count] of sortedTags.slice(0, 10)) {
8034
- lines.push(` ${tag}: ${count}`);
8035
- }
8036
- lines.push("");
8037
- }
8038
- if (stats.totalUsage > 0) {
8039
- lines.push(import_chalk.default.dim("Usage:"));
8040
- lines.push(` Total uses: ${stats.totalUsage}`);
8041
- lines.push(` Avg success: ${Math.round(stats.avgSuccessRate * 100)}%`);
8205
+ var IndexerService = class {
8206
+ constructor(config2 = {}, skillBank) {
8207
+ this.initialized = false;
8208
+ this.globalConfig = loadConfig();
8209
+ this.serviceConfig = {
8210
+ githubToken: config2.githubToken || this.globalConfig.indexer.github_token,
8211
+ anthropicApiKey: config2.anthropicApiKey || this.globalConfig.indexer.anthropic_key,
8212
+ batchSize: config2.batchSize || this.globalConfig.indexer.batch_size,
8213
+ minConfidence: config2.minConfidence || this.globalConfig.indexer.min_confidence,
8214
+ concurrency: config2.concurrency || 3,
8215
+ cacheTtlSeconds: config2.cacheTtlSeconds || 3600,
8216
+ databasePath: config2.databasePath,
8217
+ cacheDir: config2.cacheDir,
8218
+ claudeModel: config2.claudeModel,
8219
+ scraperModules: config2.scraperModules
8220
+ };
8221
+ this.skillBank = skillBank;
8042
8222
  }
8043
- return lines.join("\n");
8044
- }
8045
- function printError(message) {
8046
- console.error(import_chalk.default.red(`Error: ${message}`));
8047
- }
8048
- function printSuccess(message) {
8049
- console.log(import_chalk.default.green(`\u2713 ${message}`));
8050
- }
8051
- function printWarning(message) {
8052
- console.log(import_chalk.default.yellow(`\u26A0 ${message}`));
8053
- }
8054
- function printInfo(message) {
8055
- console.log(import_chalk.default.dim(message));
8056
- }
8057
- function truncate(text, maxLength) {
8058
- const oneLine = text.replace(/\n/g, " ");
8059
- if (oneLine.length <= maxLength) return oneLine;
8060
- return oneLine.slice(0, maxLength - 3) + "...";
8061
- }
8062
-
8063
- // src/cli/commands/list.ts
8064
- var listCommand = new import_commander.Command("list").description("List all skills").option("-s, --status <status>", "Filter by status (active, draft, deprecated, experimental)").option("-t, --tag <tag>", "Filter by tag").option("-a, --author <author>", "Filter by author").action(async (options, command) => {
8065
- const globalOpts = command.optsWithGlobals();
8066
- try {
8067
- const skillBank = await createSkillBankFromOptions(globalOpts);
8068
- const filter = {};
8069
- if (options.status) {
8070
- filter.status = [options.status];
8071
- }
8072
- if (options.tag) {
8073
- filter.tags = [options.tag];
8074
- }
8075
- if (options.author) {
8076
- filter.author = options.author;
8077
- }
8078
- const skills = await skillBank.listSkills(
8079
- Object.keys(filter).length > 0 ? filter : void 0
8080
- );
8081
- if (globalOpts.json) {
8082
- console.log(JSON.stringify(skills, null, 2));
8083
- return;
8084
- }
8085
- if (skills.length === 0) {
8086
- printInfo(`No skills found in ${getSkillPath(globalOpts)}`);
8087
- return;
8088
- }
8089
- if (!globalOpts.quiet) {
8090
- printInfo(`Skills in ${getSkillPath(globalOpts)}:
8091
- `);
8092
- }
8093
- for (const skill of skills) {
8094
- console.log(formatSkillLine(skill));
8095
- }
8096
- if (!globalOpts.quiet) {
8097
- console.log();
8098
- printInfo(`${skills.length} skill(s)`);
8099
- }
8100
- } catch (error) {
8101
- printError(error.message);
8102
- process.exit(1);
8223
+ /**
8224
+ * Get effective configuration
8225
+ */
8226
+ getConfig() {
8227
+ return { ...this.serviceConfig };
8103
8228
  }
8104
- });
8105
-
8106
- // src/cli/commands/show.ts
8107
- var import_commander2 = require("commander");
8108
- var showCommand = new import_commander2.Command("show").description("Show skill details").argument("<id>", "Skill ID").option("-v, --version <version>", "Show specific version").action(async (id, options, command) => {
8109
- const globalOpts = command.optsWithGlobals();
8110
- try {
8111
- const skillBank = await createSkillBankFromOptions(globalOpts);
8112
- const skill = await skillBank.getSkill(id, options.version);
8113
- if (!skill) {
8114
- printError(`Skill not found: ${id}${options.version ? `@${options.version}` : ""}`);
8115
- process.exit(1);
8116
- }
8117
- if (globalOpts.json) {
8118
- console.log(JSON.stringify(skill, null, 2));
8119
- return;
8229
+ /**
8230
+ * Initialize the service (lazy load scraper modules)
8231
+ */
8232
+ async initialize() {
8233
+ if (this.initialized) return;
8234
+ try {
8235
+ if (this.serviceConfig.scraperModules) {
8236
+ const mod = this.serviceConfig.scraperModules;
8237
+ this.databaseModule = mod;
8238
+ if (mod.createDatabase) {
8239
+ const dbPath = this.serviceConfig.databasePath || path12.join(process.cwd(), "scraper/data/skills.db");
8240
+ const dbDir = path12.dirname(dbPath);
8241
+ if (!fs12.existsSync(dbDir)) fs12.mkdirSync(dbDir, { recursive: true });
8242
+ this.db = mod.createDatabase({ type: "sqlite", path: dbPath });
8243
+ if (this.db.connect) await this.db.connect();
8244
+ if (this.db.migrate) await this.db.migrate();
8245
+ }
8246
+ const localDb = this.db;
8247
+ if (mod.ScraperService) {
8248
+ this.scraperModule = {
8249
+ createScraper: (config2) => {
8250
+ const scraperConfig = {
8251
+ githubToken: config2.githubToken || "",
8252
+ cacheEnabled: true,
8253
+ cacheDir: config2.cacheDir || path12.join(process.cwd(), ".cache/scraper"),
8254
+ cacheTtlSeconds: config2.cacheTtlSeconds || 3600,
8255
+ requestDelayMs: 100,
8256
+ maxRetries: 3
8257
+ };
8258
+ const svc = new mod.ScraperService(localDb, scraperConfig);
8259
+ let initialized = false;
8260
+ const ensureInit = async () => {
8261
+ if (!initialized) {
8262
+ await svc.init();
8263
+ initialized = true;
8264
+ }
8265
+ };
8266
+ return {
8267
+ scrapeAwesomeList: async (url, opts) => {
8268
+ await ensureInit();
8269
+ await svc.scrape({ type: "awesome-list", url }, opts);
8270
+ return await localDb.listSkills() || [];
8271
+ },
8272
+ scrapeRepository: async (url, opts) => {
8273
+ await ensureInit();
8274
+ await svc.scrape({ type: "repository", url }, opts);
8275
+ return await localDb.listSkills() || [];
8276
+ }
8277
+ };
8278
+ }
8279
+ };
8280
+ }
8281
+ if (mod.SkillClassifier) {
8282
+ this.indexerModule = {
8283
+ createIndexer: (config2) => {
8284
+ const classifier = new mod.SkillClassifier(config2);
8285
+ return {
8286
+ classifySkill: async (skill) => {
8287
+ let tree = null;
8288
+ if (mod.TaxonomyManager && localDb) {
8289
+ try {
8290
+ const tm = new mod.TaxonomyManager(localDb);
8291
+ tree = await tm.getTree();
8292
+ } catch {
8293
+ }
8294
+ }
8295
+ return classifier.classify(skill, tree);
8296
+ }
8297
+ };
8298
+ }
8299
+ };
8300
+ }
8301
+ this.initialized = true;
8302
+ return;
8303
+ }
8304
+ const possiblePaths = [
8305
+ // Relative to this file in dist
8306
+ path12.resolve(__dirname, "../../scraper/dist"),
8307
+ // Relative to project root
8308
+ path12.resolve(process.cwd(), "scraper/dist"),
8309
+ // Absolute paths from config
8310
+ this.serviceConfig.cacheDir ? path12.resolve(this.serviceConfig.cacheDir, "../scraper/dist") : null
8311
+ ].filter(Boolean);
8312
+ let scraperBasePath = null;
8313
+ for (const basePath of possiblePaths) {
8314
+ const scraperIndex = path12.join(basePath, "scraper/index.js");
8315
+ if (fs12.existsSync(scraperIndex)) {
8316
+ scraperBasePath = basePath;
8317
+ break;
8318
+ }
8319
+ }
8320
+ if (!scraperBasePath) {
8321
+ throw new Error(
8322
+ "Scraper modules not found. Run `cd scraper && npm run build` first."
8323
+ );
8324
+ }
8325
+ const scraperPath = path12.join(scraperBasePath, "scraper/index.js");
8326
+ const indexerPath = path12.join(scraperBasePath, "indexer/index.js");
8327
+ const databasePath = path12.join(scraperBasePath, "database/index.js");
8328
+ this.scraperModule = await import(
8329
+ /* webpackIgnore: true */
8330
+ scraperPath
8331
+ );
8332
+ this.indexerModule = await import(
8333
+ /* webpackIgnore: true */
8334
+ indexerPath
8335
+ );
8336
+ this.databaseModule = await import(
8337
+ /* webpackIgnore: true */
8338
+ databasePath
8339
+ );
8340
+ if (this.databaseModule.createDatabase) {
8341
+ const dbPath = this.serviceConfig.databasePath || path12.join(process.cwd(), "scraper/data/skills.db");
8342
+ this.db = this.databaseModule.createDatabase(dbPath);
8343
+ }
8344
+ this.initialized = true;
8345
+ } catch (err) {
8346
+ console.warn(`Scraper modules not available: ${err.message}`);
8347
+ console.warn("Some indexer features will be limited.");
8348
+ this.initialized = true;
8120
8349
  }
8121
- console.log(formatSkillDetail(skill));
8122
- } catch (error) {
8123
- printError(error.message);
8124
- process.exit(1);
8125
8350
  }
8126
- });
8127
-
8128
- // src/cli/commands/search.ts
8129
- var import_commander3 = require("commander");
8130
- var searchCommand = new import_commander3.Command("search").description("Search skills by text").argument("<query>", "Search query").action(async (query, options, command) => {
8131
- const globalOpts = command.optsWithGlobals();
8132
- try {
8133
- const skillBank = await createSkillBankFromOptions(globalOpts);
8134
- const skills = await skillBank.searchSkills(query);
8135
- if (globalOpts.json) {
8136
- console.log(JSON.stringify(skills, null, 2));
8137
- return;
8138
- }
8139
- if (skills.length === 0) {
8140
- printInfo(`No skills found matching "${query}"`);
8141
- return;
8142
- }
8143
- if (!globalOpts.quiet) {
8144
- printInfo(`Results for "${query}":
8145
- `);
8146
- }
8147
- for (const skill of skills) {
8148
- console.log(formatSkillLine(skill));
8149
- }
8150
- if (!globalOpts.quiet) {
8151
- console.log();
8152
- printInfo(`${skills.length} result(s)`);
8153
- }
8154
- } catch (error) {
8155
- printError(error.message);
8156
- process.exit(1);
8351
+ /**
8352
+ * Check if the indexer is available
8353
+ */
8354
+ isAvailable() {
8355
+ return this.initialized && !!this.scraperModule;
8157
8356
  }
8158
- });
8159
-
8160
- // src/cli/commands/stats.ts
8161
- var import_commander4 = require("commander");
8162
- var statsCommand = new import_commander4.Command("stats").description("Show skill bank statistics").action(async (options, command) => {
8163
- const globalOpts = command.optsWithGlobals();
8164
- try {
8165
- const skillBank = await createSkillBankFromOptions(globalOpts);
8166
- const stats = await skillBank.getStats();
8167
- if (globalOpts.json) {
8168
- console.log(JSON.stringify(stats, null, 2));
8169
- return;
8170
- }
8171
- console.log(formatStats(stats));
8172
- } catch (error) {
8173
- printError(error.message);
8174
- process.exit(1);
8357
+ /**
8358
+ * Check if running in degraded mode (no scraper modules)
8359
+ */
8360
+ isDegradedMode() {
8361
+ return this.initialized && !this.scraperModule;
8175
8362
  }
8176
- });
8177
-
8178
- // src/cli/commands/versions.ts
8179
- var import_commander5 = require("commander");
8180
- var versionsCommand = new import_commander5.Command("versions").description("Show version history for a skill").argument("<id>", "Skill ID").action(async (id, options, command) => {
8181
- const globalOpts = command.optsWithGlobals();
8182
- try {
8183
- const skillBank = await createSkillBankFromOptions(globalOpts);
8184
- const versions = await skillBank.getVersionHistory(id);
8185
- if (versions.length === 0) {
8186
- printInfo(`No version history found for: ${id}`);
8187
- return;
8363
+ /**
8364
+ * Scrape skills from GitHub sources
8365
+ */
8366
+ async scrape(sources, options) {
8367
+ await this.initialize();
8368
+ if (!this.scraperModule || !this.db) {
8369
+ throw new Error(
8370
+ "Scraper not available. Build the scraper module first: cd scraper && npm run build"
8371
+ );
8188
8372
  }
8189
- if (globalOpts.json) {
8190
- console.log(JSON.stringify(versions, null, 2));
8191
- return;
8373
+ const result = {
8374
+ discovered: 0,
8375
+ scraped: 0,
8376
+ skipped: 0,
8377
+ failed: 0,
8378
+ unchanged: 0,
8379
+ errors: []
8380
+ };
8381
+ try {
8382
+ const scraper = this.scraperModule.createScraper({
8383
+ githubToken: this.serviceConfig.githubToken,
8384
+ cacheDir: this.serviceConfig.cacheDir,
8385
+ cacheTtlSeconds: this.serviceConfig.cacheTtlSeconds
8386
+ });
8387
+ for (const source of sources) {
8388
+ try {
8389
+ let skills;
8390
+ if (source.type === "awesome-list") {
8391
+ skills = await scraper.scrapeAwesomeList(source.url, {
8392
+ force: options?.force
8393
+ });
8394
+ } else {
8395
+ skills = await scraper.scrapeRepository(source.url, {
8396
+ force: options?.force
8397
+ });
8398
+ }
8399
+ result.discovered += skills.length;
8400
+ for (const skill of skills) {
8401
+ try {
8402
+ const existing = this.db.getSkillBySlug?.(skill.slug);
8403
+ if (existing && !options?.force) {
8404
+ result.unchanged++;
8405
+ } else {
8406
+ this.db.saveSkill?.(skill);
8407
+ result.scraped++;
8408
+ }
8409
+ } catch (err) {
8410
+ result.failed++;
8411
+ result.errors.push(
8412
+ `Failed to save skill ${skill.slug}: ${err.message}`
8413
+ );
8414
+ }
8415
+ }
8416
+ } catch (err) {
8417
+ result.failed++;
8418
+ result.errors.push(
8419
+ `Failed to scrape ${source.url}: ${err.message}`
8420
+ );
8421
+ }
8422
+ }
8423
+ } catch (err) {
8424
+ result.errors.push(`Scrape failed: ${err.message}`);
8192
8425
  }
8193
- console.log(formatVersionHistory(versions));
8194
- } catch (error) {
8195
- printError(error.message);
8196
- process.exit(1);
8426
+ return result;
8197
8427
  }
8198
- });
8199
-
8200
- // src/cli/commands/diff.ts
8201
- var import_commander6 = require("commander");
8202
- var diffCommand = new import_commander6.Command("diff").description("Compare two versions of a skill").argument("<id>", "Skill ID").argument("<version-a>", "First version").argument("<version-b>", "Second version").action(async (id, versionA, versionB, options, command) => {
8203
- const globalOpts = command.optsWithGlobals();
8204
- try {
8205
- const skillBank = await createSkillBankFromOptions(globalOpts);
8206
- const diff = await skillBank.compareVersions(id, versionA, versionB);
8207
- if (globalOpts.json) {
8208
- console.log(JSON.stringify(diff, null, 2));
8209
- return;
8428
+ /**
8429
+ * Classify unindexed skills using AI
8430
+ */
8431
+ async classify(options) {
8432
+ await this.initialize();
8433
+ if (!this.indexerModule || !this.db) {
8434
+ throw new Error(
8435
+ "Indexer not available. Build the scraper module first: cd scraper && npm run build"
8436
+ );
8210
8437
  }
8211
- console.log(formatDiff(diff.versionA, diff.versionB, diff.changes));
8212
- } catch (error) {
8213
- printError(error.message);
8214
- process.exit(1);
8215
- }
8216
- });
8217
-
8218
- // src/cli/commands/rollback.ts
8219
- var import_commander7 = require("commander");
8220
- var rollbackCommand = new import_commander7.Command("rollback").description("Rollback a skill to a previous version").argument("<id>", "Skill ID").requiredOption("--to <version>", "Version to rollback to").action(async (id, options, command) => {
8221
- const globalOpts = command.optsWithGlobals();
8222
- try {
8223
- const skillBank = await createSkillBankFromOptions(globalOpts);
8224
- const skill = await skillBank.rollbackSkill(id, options.to);
8225
- if (globalOpts.json) {
8226
- console.log(JSON.stringify(skill, null, 2));
8227
- return;
8438
+ const result = {
8439
+ indexed: 0,
8440
+ skipped: 0,
8441
+ failed: 0,
8442
+ errors: []
8443
+ };
8444
+ try {
8445
+ const indexer = this.indexerModule.createIndexer({
8446
+ anthropicApiKey: this.serviceConfig.anthropicApiKey,
8447
+ model: this.serviceConfig.claudeModel || "claude-sonnet-4-20250514",
8448
+ minConfidence: this.serviceConfig.minConfidence
8449
+ });
8450
+ let skills;
8451
+ if (options?.skillId) {
8452
+ const skill = this.db.getSkillBySlug?.(options.skillId);
8453
+ skills = skill ? [skill] : [];
8454
+ } else if (options?.all) {
8455
+ skills = this.db.getAllSkills?.() || [];
8456
+ } else {
8457
+ skills = this.db.getSkillsByStatus?.("raw") || [];
8458
+ }
8459
+ const batchSize = this.serviceConfig.batchSize || 10;
8460
+ for (let i = 0; i < skills.length; i += batchSize) {
8461
+ const batch = skills.slice(i, i + batchSize);
8462
+ for (const skill of batch) {
8463
+ if (skill.status === "indexed" && !options?.all) {
8464
+ result.skipped++;
8465
+ continue;
8466
+ }
8467
+ try {
8468
+ const classification = await indexer.classifySkill(skill);
8469
+ skill.status = "indexed";
8470
+ skill.primaryPath = classification.primaryPath;
8471
+ skill.secondaryPaths = classification.secondaryPaths;
8472
+ skill.confidence = classification.confidence;
8473
+ skill.classificationReasoning = classification.reasoning;
8474
+ skill.indexedAt = (/* @__PURE__ */ new Date()).toISOString();
8475
+ this.db.saveSkill?.(skill);
8476
+ result.indexed++;
8477
+ } catch (err) {
8478
+ result.failed++;
8479
+ result.errors.push(
8480
+ `Failed to classify ${skill.slug}: ${err.message}`
8481
+ );
8482
+ }
8483
+ }
8484
+ }
8485
+ } catch (err) {
8486
+ result.errors.push(`Classification failed: ${err.message}`);
8228
8487
  }
8229
- printSuccess(`Rolled back ${id} to v${options.to} (new version: v${skill.version})`);
8230
- } catch (error) {
8231
- printError(error.message);
8232
- process.exit(1);
8488
+ return result;
8233
8489
  }
8234
- });
8235
-
8236
- // src/cli/commands/fork.ts
8237
- var import_commander8 = require("commander");
8238
- var forkCommand = new import_commander8.Command("fork").description("Fork a skill to create a variant").argument("<id>", "Skill ID to fork from").requiredOption("--new-id <id>", "ID for the new forked skill").requiredOption("--reason <reason>", "Reason for forking").option("--name <name>", "Name for the forked skill").option("--from-version <version>", "Version to fork from (defaults to latest)").action(async (id, options, command) => {
8239
- const globalOpts = command.optsWithGlobals();
8240
- try {
8241
- const skillBank = await createSkillBankFromOptions(globalOpts);
8242
- const forked = await skillBank.forkSkill(id, {
8243
- newId: options.newId,
8244
- newName: options.name,
8245
- reason: options.reason,
8246
- fromVersion: options.fromVersion
8247
- });
8248
- if (globalOpts.json) {
8249
- console.log(JSON.stringify(forked, null, 2));
8250
- return;
8490
+ /**
8491
+ * Detect relationships between skills
8492
+ */
8493
+ async detectRelationships(options) {
8494
+ await this.initialize();
8495
+ const result = {
8496
+ detected: 0,
8497
+ skipped: 0,
8498
+ errors: []
8499
+ };
8500
+ if (this.skillBank) {
8501
+ try {
8502
+ const storage = this.skillBank.getStorage();
8503
+ if (hasIndexerSupport(storage)) {
8504
+ const skills = await this.skillBank.listSkills();
8505
+ const targetSkills = options?.skillId ? skills.filter((s) => s.id === options.skillId) : skills;
8506
+ for (const skill of targetSkills) {
8507
+ const relationships = this.detectSkillRelationships(skill, skills);
8508
+ for (const rel of relationships) {
8509
+ try {
8510
+ await storage.addRelationship(
8511
+ skill.id,
8512
+ rel.targetSkillId,
8513
+ rel.type,
8514
+ rel.confidence,
8515
+ rel.reasoning
8516
+ );
8517
+ result.detected++;
8518
+ } catch (err) {
8519
+ result.skipped++;
8520
+ }
8521
+ }
8522
+ }
8523
+ }
8524
+ } catch (err) {
8525
+ result.errors.push(
8526
+ `Relationship detection failed: ${err.message}`
8527
+ );
8528
+ }
8529
+ return result;
8251
8530
  }
8252
- printSuccess(`Forked ${id} \u2192 ${forked.id} (v${forked.version})`);
8253
- } catch (error) {
8254
- printError(error.message);
8255
- process.exit(1);
8531
+ if (!this.db) {
8532
+ throw new Error("Neither SkillBank nor scraper database available.");
8533
+ }
8534
+ try {
8535
+ const skills = this.db.getAllSkills?.() || [];
8536
+ const targetSkills = options?.skillId ? skills.filter((s) => s.slug === options.skillId) : skills.filter((s) => s.status === "indexed");
8537
+ for (const skill of targetSkills) {
8538
+ const relationships = this.detectSkillRelationships(skill, skills);
8539
+ for (const rel of relationships) {
8540
+ try {
8541
+ this.db.saveRelationship?.({
8542
+ sourceSkillId: skill.id,
8543
+ ...rel
8544
+ });
8545
+ result.detected++;
8546
+ } catch (err) {
8547
+ result.skipped++;
8548
+ }
8549
+ }
8550
+ }
8551
+ } catch (err) {
8552
+ result.errors.push(
8553
+ `Relationship detection failed: ${err.message}`
8554
+ );
8555
+ }
8556
+ return result;
8256
8557
  }
8257
- });
8258
-
8259
- // src/cli/commands/deprecate.ts
8260
- var import_commander9 = require("commander");
8261
- var deprecateCommand = new import_commander9.Command("deprecate").description("Mark a skill as deprecated").argument("<id>", "Skill ID").action(async (id, options, command) => {
8262
- const globalOpts = command.optsWithGlobals();
8263
- try {
8264
- const skillBank = await createSkillBankFromOptions(globalOpts);
8265
- const skill = await skillBank.deprecateSkill(id);
8266
- if (globalOpts.json) {
8267
- console.log(JSON.stringify(skill, null, 2));
8268
- return;
8558
+ /**
8559
+ * Detect relationships for a single skill
8560
+ */
8561
+ detectSkillRelationships(skill, allSkills) {
8562
+ const relationships = [];
8563
+ const skillId = skill.id || skill.slug;
8564
+ const skillContent = `${skill.name} ${skill.description} ${skill.instructions || ""} ${skill.content || ""}`.toLowerCase();
8565
+ for (const other of allSkills) {
8566
+ const otherId = other.id || other.slug;
8567
+ if (otherId === skillId) continue;
8568
+ const otherName = (other.name || "").toLowerCase();
8569
+ const dependencyPatterns = [
8570
+ "requires",
8571
+ "depends on",
8572
+ "uses",
8573
+ "needs",
8574
+ "builds on"
8575
+ ];
8576
+ for (const pattern of dependencyPatterns) {
8577
+ if (skillContent.includes(`${pattern} ${otherName}`)) {
8578
+ relationships.push({
8579
+ targetSkillId: otherId,
8580
+ type: "depends_on",
8581
+ confidence: 0.7,
8582
+ reasoning: `Content mentions "${pattern} ${other.name}"`
8583
+ });
8584
+ break;
8585
+ }
8586
+ }
8587
+ const extensionPatterns = [
8588
+ "extends",
8589
+ "improves",
8590
+ "enhances",
8591
+ "builds upon"
8592
+ ];
8593
+ for (const pattern of extensionPatterns) {
8594
+ if (skillContent.includes(`${pattern} ${otherName}`)) {
8595
+ relationships.push({
8596
+ targetSkillId: otherId,
8597
+ type: "extends",
8598
+ confidence: 0.7,
8599
+ reasoning: `Content mentions "${pattern} ${other.name}"`
8600
+ });
8601
+ break;
8602
+ }
8603
+ }
8604
+ const alternativePatterns = [
8605
+ "alternative to",
8606
+ "instead of",
8607
+ "replacement for"
8608
+ ];
8609
+ for (const pattern of alternativePatterns) {
8610
+ if (skillContent.includes(`${pattern} ${otherName}`)) {
8611
+ relationships.push({
8612
+ targetSkillId: otherId,
8613
+ type: "alternative",
8614
+ confidence: 0.6,
8615
+ reasoning: `Content mentions "${pattern} ${other.name}"`
8616
+ });
8617
+ break;
8618
+ }
8619
+ }
8620
+ const skillPath = skill.taxonomy?.primaryPath || skill.primaryPath || [];
8621
+ const otherPath = other.taxonomy?.primaryPath || other.primaryPath || [];
8622
+ if (skillPath.length >= 2 && otherPath.length >= 2) {
8623
+ if (skillPath[0] === otherPath[0] && skillPath[1] === otherPath[1]) {
8624
+ relationships.push({
8625
+ targetSkillId: otherId,
8626
+ type: "related",
8627
+ confidence: 0.5,
8628
+ reasoning: `Same taxonomy category: ${skillPath.slice(0, 2).join(" > ")}`
8629
+ });
8630
+ }
8631
+ }
8269
8632
  }
8270
- printSuccess(`Deprecated: ${id} (v${skill.version})`);
8271
- } catch (error) {
8272
- printError(error.message);
8273
- process.exit(1);
8633
+ return relationships;
8274
8634
  }
8275
- });
8276
-
8277
- // src/cli/commands/activate.ts
8278
- var import_commander10 = require("commander");
8279
- var activateCommand = new import_commander10.Command("activate").description("Mark a skill as active").argument("<id>", "Skill ID").action(async (id, options, command) => {
8280
- const globalOpts = command.optsWithGlobals();
8281
- try {
8282
- const skillBank = await createSkillBankFromOptions(globalOpts);
8283
- const skill = await skillBank.getSkill(id);
8284
- if (!skill) {
8285
- printError(`Skill not found: ${id}`);
8286
- process.exit(1);
8287
- }
8288
- skill.status = "active";
8289
- skill.updatedAt = /* @__PURE__ */ new Date();
8290
- await skillBank.saveSkill(skill);
8291
- if (globalOpts.json) {
8292
- console.log(JSON.stringify(skill, null, 2));
8293
- return;
8635
+ /**
8636
+ * Get taxonomy tree
8637
+ */
8638
+ async getTaxonomyTree(rootPath) {
8639
+ await this.initialize();
8640
+ if (this.skillBank) {
8641
+ const storage = this.skillBank.getStorage();
8642
+ if (hasIndexerSupport(storage)) {
8643
+ const nodes = await storage.getTaxonomyTree(rootPath);
8644
+ return this.wrapTreeNodes(nodes, rootPath);
8645
+ }
8294
8646
  }
8295
- printSuccess(`Activated: ${id} (v${skill.version})`);
8296
- } catch (error) {
8297
- printError(error.message);
8298
- process.exit(1);
8647
+ const skills = this.skillBank ? await this.skillBank.listSkills() : this.db?.getAllSkills?.() || [];
8648
+ return this.buildTaxonomyTreeFromSkills(skills, rootPath);
8299
8649
  }
8300
- });
8301
-
8302
- // src/cli/commands/delete.ts
8303
- var import_commander11 = require("commander");
8304
- var deleteCommand = new import_commander11.Command("delete").description("Delete a skill").argument("<id>", "Skill ID").option("-f, --force", "Skip confirmation").option("-v, --version <version>", "Delete specific version only").action(async (id, options, command) => {
8305
- const globalOpts = command.optsWithGlobals();
8306
- try {
8307
- const skillBank = await createSkillBankFromOptions(globalOpts);
8308
- const skill = await skillBank.getSkill(id, options.version);
8309
- if (!skill) {
8310
- printError(`Skill not found: ${id}${options.version ? `@${options.version}` : ""}`);
8311
- process.exit(1);
8312
- }
8313
- if (!options.force) {
8314
- printWarning(`This will permanently delete ${id}${options.version ? `@${options.version}` : " and all versions"}`);
8315
- printWarning("Use --force to confirm");
8316
- process.exit(1);
8317
- }
8318
- const deleted = await skillBank.deleteSkill(id, options.version);
8319
- if (!deleted) {
8320
- printError(`Failed to delete: ${id}`);
8321
- process.exit(1);
8322
- }
8323
- if (globalOpts.json) {
8324
- console.log(JSON.stringify({ deleted: true, id, version: options.version }));
8325
- return;
8650
+ /**
8651
+ * Wrap tree nodes returned from SQLite storage into a root node
8652
+ */
8653
+ wrapTreeNodes(nodes, rootPath) {
8654
+ const root = {
8655
+ id: "root",
8656
+ name: rootPath?.join(" > ") || "All Skills",
8657
+ path: rootPath || [],
8658
+ skillCount: 0,
8659
+ children: []
8660
+ };
8661
+ for (const node of nodes) {
8662
+ root.children.push(this.convertToTaxonomyNode(node));
8663
+ root.skillCount += this.countNodeSkills(node);
8326
8664
  }
8327
- printSuccess(`Deleted: ${id}${options.version ? `@${options.version}` : ""}`);
8328
- } catch (error) {
8329
- printError(error.message);
8330
- process.exit(1);
8665
+ return root;
8331
8666
  }
8332
- });
8333
-
8334
- // src/cli/commands/export.ts
8335
- var import_commander12 = require("commander");
8336
- var fs13 = __toESM(require("fs"));
8337
- var exportCommand = new import_commander12.Command("export").description("Export all skills to JSON").option("-o, --output <file>", "Output file path (defaults to stdout)").action(async (options, command) => {
8338
- const globalOpts = command.optsWithGlobals();
8339
- try {
8340
- const skillBank = await createSkillBankFromOptions(globalOpts);
8341
- const skills = await skillBank.exportAll();
8342
- const json = JSON.stringify(skills, null, 2);
8343
- if (options.output) {
8344
- fs13.writeFileSync(options.output, json, "utf-8");
8345
- if (!globalOpts.quiet) {
8346
- printSuccess(`Exported ${skills.length} skill(s) to ${options.output}`);
8347
- }
8348
- } else {
8349
- console.log(json);
8350
- }
8351
- } catch (error) {
8352
- printError(error.message);
8353
- process.exit(1);
8667
+ /**
8668
+ * Convert storage tree node to TaxonomyNode format
8669
+ */
8670
+ convertToTaxonomyNode(node) {
8671
+ return {
8672
+ id: node.id,
8673
+ name: node.name,
8674
+ path: Array.isArray(node.path) ? node.path : (node.path || "").split("/"),
8675
+ skillCount: node.skillCount || 0,
8676
+ children: (node.children || []).map(
8677
+ (child) => this.convertToTaxonomyNode(child)
8678
+ )
8679
+ };
8354
8680
  }
8355
- });
8356
-
8357
- // src/cli/commands/import.ts
8358
- var import_commander13 = require("commander");
8359
- var fs14 = __toESM(require("fs"));
8360
-
8361
- // src/import/converter.ts
8362
- function convertIndexerSkill(indexerSkill) {
8363
- const warnings = [];
8364
- const instructions = indexerSkill.content || "";
8365
- const hasStructuredContent = instructions.trim().length > 0;
8366
- if (!hasStructuredContent) {
8367
- warnings.push("No content found, instructions will be empty");
8681
+ /**
8682
+ * Count total skills in a node tree
8683
+ */
8684
+ countNodeSkills(node) {
8685
+ let count = node.skillCount || 0;
8686
+ for (const child of node.children || []) {
8687
+ count += this.countNodeSkills(child);
8688
+ }
8689
+ return count;
8368
8690
  }
8369
- const tags = [...indexerSkill.tags || []];
8370
- if (indexerSkill.sourceRepo) {
8371
- const repoName = indexerSkill.sourceRepo.split("/").pop();
8372
- if (repoName && !tags.includes(repoName)) {
8373
- tags.push(repoName);
8691
+ /**
8692
+ * Build taxonomy tree from flat nodes
8693
+ */
8694
+ buildTaxonomyTree(nodes, rootPath) {
8695
+ const root = {
8696
+ id: "root",
8697
+ name: rootPath?.join(" > ") || "All Skills",
8698
+ path: rootPath || [],
8699
+ skillCount: 0,
8700
+ children: []
8701
+ };
8702
+ const nodeMap = /* @__PURE__ */ new Map();
8703
+ nodeMap.set("root", root);
8704
+ for (const node of nodes) {
8705
+ const taxNode = {
8706
+ id: node.id,
8707
+ name: node.name,
8708
+ path: node.path,
8709
+ skillCount: node.skillCount || 0,
8710
+ children: []
8711
+ };
8712
+ nodeMap.set(node.id, taxNode);
8713
+ }
8714
+ for (const node of nodes) {
8715
+ const taxNode = nodeMap.get(node.id);
8716
+ const parentId = node.parentId || "root";
8717
+ const parent = nodeMap.get(parentId);
8718
+ if (parent) {
8719
+ parent.children.push(taxNode);
8720
+ parent.skillCount += taxNode.skillCount;
8721
+ }
8374
8722
  }
8723
+ return root;
8375
8724
  }
8376
- const status = indexerSkill.status === "indexed" ? "active" : indexerSkill.status === "raw" ? "draft" : "draft";
8377
- const skill = {
8378
- id: indexerSkill.slug,
8379
- name: indexerSkill.displayName || indexerSkill.name,
8380
- version: indexerSkill.version,
8381
- description: indexerSkill.description,
8382
- instructions,
8383
- author: indexerSkill.author,
8384
- tags,
8385
- createdAt: new Date(indexerSkill.scrapedAt),
8386
- updatedAt: new Date(indexerSkill.updatedAt),
8387
- status,
8388
- metrics: {
8389
- usageCount: 0,
8390
- successRate: 0,
8391
- feedbackScores: []
8392
- },
8393
- source: {
8394
- type: "imported",
8395
- location: indexerSkill.sourceUrl,
8396
- importedAt: /* @__PURE__ */ new Date()
8397
- },
8398
- // Extended fields for indexed skills
8399
- taxonomy: indexerSkill.primaryPath ? {
8400
- primaryPath: indexerSkill.primaryPath,
8401
- secondaryPaths: indexerSkill.secondaryPaths,
8402
- confidence: indexerSkill.confidence
8403
- } : void 0,
8404
- externalSource: {
8405
- url: indexerSkill.sourceUrl,
8406
- repo: indexerSkill.sourceRepo,
8407
- scrapedAt: new Date(indexerSkill.scrapedAt)
8725
+ /**
8726
+ * Build taxonomy tree from skills
8727
+ */
8728
+ buildTaxonomyTreeFromSkills(skills, rootPath) {
8729
+ const root = {
8730
+ id: "root",
8731
+ name: rootPath?.join(" > ") || "All Skills",
8732
+ path: rootPath || [],
8733
+ skillCount: 0,
8734
+ children: []
8735
+ };
8736
+ const nodeMap = /* @__PURE__ */ new Map();
8737
+ for (const skill of skills) {
8738
+ const taxonomy = skill.taxonomy || (skill.primaryPath ? { primaryPath: skill.primaryPath } : null);
8739
+ if (!taxonomy?.primaryPath) continue;
8740
+ const skillPath = taxonomy.primaryPath;
8741
+ if (rootPath && rootPath.length > 0) {
8742
+ let matches = true;
8743
+ for (let i = 0; i < rootPath.length; i++) {
8744
+ if (skillPath[i] !== rootPath[i]) {
8745
+ matches = false;
8746
+ break;
8747
+ }
8748
+ }
8749
+ if (!matches) continue;
8750
+ }
8751
+ let currentPath = [];
8752
+ let parent = root;
8753
+ for (const segment of skillPath) {
8754
+ currentPath = [...currentPath, segment];
8755
+ const pathKey = currentPath.join("/");
8756
+ let node = nodeMap.get(pathKey);
8757
+ if (!node) {
8758
+ node = {
8759
+ id: pathKey,
8760
+ name: segment,
8761
+ path: [...currentPath],
8762
+ skillCount: 0,
8763
+ children: []
8764
+ };
8765
+ nodeMap.set(pathKey, node);
8766
+ parent.children.push(node);
8767
+ }
8768
+ parent = node;
8769
+ }
8770
+ parent.skillCount++;
8771
+ root.skillCount++;
8408
8772
  }
8409
- };
8410
- return { skill, warnings, hasStructuredContent };
8411
- }
8412
- function convertIndexerSkills(skills) {
8413
- const results = [];
8414
- const converted = [];
8415
- for (const skill of skills) {
8416
- const result = convertIndexerSkill(skill);
8417
- results.push(result);
8418
- converted.push(result.skill);
8773
+ return root;
8419
8774
  }
8420
- return {
8421
- skills: converted,
8422
- results,
8423
- stats: {
8424
- total: results.length,
8425
- withStructuredContent: results.filter((r) => r.hasStructuredContent).length,
8426
- withWarnings: results.filter((r) => r.warnings.length > 0).length
8775
+ /**
8776
+ * Get indexer statistics
8777
+ */
8778
+ async getStats() {
8779
+ await this.initialize();
8780
+ if (this.skillBank) {
8781
+ const skills = await this.skillBank.listSkills();
8782
+ const storage = this.skillBank.getStorage();
8783
+ let taxonomyNodes = 0;
8784
+ let relationships = 0;
8785
+ let sources = 0;
8786
+ if (storage) {
8787
+ if (hasIndexerSupport(storage)) {
8788
+ const tree = await storage.getTaxonomyTree();
8789
+ taxonomyNodes = this.countTaxonomyNodes(tree);
8790
+ if (storage.getRelationships) {
8791
+ for (const skill of skills) {
8792
+ const rels = await storage.getRelationships(skill.id);
8793
+ relationships += rels.length;
8794
+ }
8795
+ }
8796
+ }
8797
+ const sourceSet = /* @__PURE__ */ new Set();
8798
+ for (const skill of skills) {
8799
+ if (skill.externalSource?.repo) {
8800
+ sourceSet.add(skill.externalSource.repo);
8801
+ }
8802
+ if (skill.source?.type === "imported" && skill.externalSource?.url) {
8803
+ sourceSet.add(skill.externalSource.url);
8804
+ }
8805
+ }
8806
+ sources = sourceSet.size;
8807
+ }
8808
+ const indexed = skills.filter(
8809
+ (s) => s.status === "active" && (s.taxonomy || s.source?.type === "imported")
8810
+ ).length;
8811
+ return {
8812
+ totalSkills: skills.length,
8813
+ indexedSkills: indexed,
8814
+ rawSkills: skills.filter((s) => s.status === "draft").length,
8815
+ failedSkills: skills.filter((s) => s.status === "deprecated").length,
8816
+ taxonomyNodes,
8817
+ relationships,
8818
+ sources
8819
+ };
8427
8820
  }
8428
- };
8429
- }
8430
- function parseIndexerExport(content) {
8431
- const data = JSON.parse(content);
8432
- let indexerSkills;
8433
- let exportMetadata;
8434
- if (Array.isArray(data)) {
8435
- indexerSkills = data;
8436
- } else if (data.skills && Array.isArray(data.skills)) {
8437
- indexerSkills = data.skills;
8438
- exportMetadata = {
8439
- exportedAt: data.exportedAt,
8440
- originalStats: data.stats
8821
+ if (this.db) {
8822
+ const stats = this.db.getStats?.() || {};
8823
+ return {
8824
+ totalSkills: stats.totalSkills || 0,
8825
+ indexedSkills: stats.indexedSkills || 0,
8826
+ rawSkills: stats.rawSkills || 0,
8827
+ failedSkills: stats.failedSkills || 0,
8828
+ taxonomyNodes: stats.taxonomyNodes || 0,
8829
+ relationships: stats.relationships || 0,
8830
+ sources: stats.sources || 0
8831
+ };
8832
+ }
8833
+ return {
8834
+ totalSkills: 0,
8835
+ indexedSkills: 0,
8836
+ rawSkills: 0,
8837
+ failedSkills: 0,
8838
+ taxonomyNodes: 0,
8839
+ relationships: 0,
8840
+ sources: 0
8441
8841
  };
8442
- } else {
8443
- throw new Error("Invalid indexer export format: expected array or object with skills array");
8444
8842
  }
8445
- const { skills, stats } = convertIndexerSkills(indexerSkills);
8446
- return { skills, stats, exportMetadata };
8447
- }
8448
-
8449
- // src/import/detect.ts
8450
- function isSkillTreeSkill(obj) {
8451
- if (typeof obj !== "object" || obj === null) return false;
8452
- const skill = obj;
8453
- return typeof skill.id === "string" && typeof skill.name === "string" && typeof skill.version === "string" && typeof skill.instructions === "string" && typeof skill.metrics === "object";
8454
- }
8455
- function isIndexerSkill(obj) {
8456
- if (typeof obj !== "object" || obj === null) return false;
8457
- const skill = obj;
8458
- return typeof skill.slug === "string" && typeof skill.sourceRepo === "string" && typeof skill.sourcePath === "string" && typeof skill.sourceUrl === "string" && typeof skill.content === "string" && typeof skill.scrapedAt === "string" && (skill.status === "raw" || skill.status === "indexed" || skill.status === "failed");
8459
- }
8460
- function isIndexerExport(obj) {
8461
- if (typeof obj !== "object" || obj === null) return false;
8462
- const exp = obj;
8463
- return typeof exp.exportedAt === "string" && Array.isArray(exp.skills);
8464
- }
8465
- function detectFormat(data) {
8466
- if (Array.isArray(data)) {
8467
- if (data.length === 0) {
8468
- return { format: "unknown", confidence: 0, details: "Empty array" };
8469
- }
8470
- const first = data[0];
8471
- if (isIndexerSkill(first)) {
8472
- return { format: "indexer", confidence: 0.95, details: "Array of indexer skills" };
8473
- }
8474
- if (isSkillTreeSkill(first)) {
8475
- return { format: "skill-tree", confidence: 0.95, details: "Array of skill-tree skills" };
8843
+ /**
8844
+ * Count nodes in taxonomy tree
8845
+ */
8846
+ countTaxonomyNodes(nodes) {
8847
+ let count = 0;
8848
+ for (const node of nodes) {
8849
+ count++;
8850
+ if (node.children) {
8851
+ count += this.countTaxonomyNodes(node.children);
8852
+ }
8476
8853
  }
8477
- return { format: "unknown", confidence: 0.3, details: "Array of unknown objects" };
8854
+ return count;
8478
8855
  }
8479
- if (typeof data === "object" && data !== null) {
8480
- if (isIndexerExport(data)) {
8481
- return { format: "indexer-export", confidence: 0.98, details: "Indexer export with metadata" };
8856
+ /**
8857
+ * Scrape and index skills directly into SkillBank
8858
+ * This is the streamlined workflow for integrated mode
8859
+ */
8860
+ async scrapeAndIndex(sources, options) {
8861
+ if (!this.skillBank) {
8862
+ throw new Error(
8863
+ "SkillBank required for scrapeAndIndex. Use integrated mode."
8864
+ );
8482
8865
  }
8483
- if (isIndexerSkill(data)) {
8484
- return { format: "indexer", confidence: 0.9, details: "Single indexer skill" };
8866
+ await this.initialize();
8867
+ const skillsAdded = [];
8868
+ const scraped = await this.scrape(sources, { force: options?.force });
8869
+ let indexed = {
8870
+ indexed: 0,
8871
+ skipped: 0,
8872
+ failed: 0,
8873
+ errors: []
8874
+ };
8875
+ if (options?.autoClassify !== false) {
8876
+ indexed = await this.classify({ all: options?.force });
8485
8877
  }
8486
- if (isSkillTreeSkill(data)) {
8487
- return { format: "skill-tree", confidence: 0.9, details: "Single skill-tree skill" };
8878
+ if (this.db) {
8879
+ const skills = await (this.db.getAllSkills?.() || this.db.listSkills?.()) || [];
8880
+ for (const skill of skills) {
8881
+ if (skill.status !== "indexed" && !options?.force && !options?.importAll) continue;
8882
+ try {
8883
+ const converted = convertIndexerSkill(skill);
8884
+ await this.skillBank.saveSkill(converted.skill);
8885
+ skillsAdded.push(converted.skill.id);
8886
+ } catch (err) {
8887
+ indexed.errors.push(
8888
+ `Failed to import ${skill.slug}: ${err.message}`
8889
+ );
8890
+ }
8891
+ }
8488
8892
  }
8893
+ let relationships = {
8894
+ detected: 0,
8895
+ skipped: 0,
8896
+ errors: []
8897
+ };
8898
+ if (options?.detectRelationships !== false) {
8899
+ relationships = await this.detectRelationships();
8900
+ }
8901
+ return {
8902
+ scraped,
8903
+ indexed,
8904
+ relationships,
8905
+ skillsAdded
8906
+ };
8489
8907
  }
8490
- return { format: "unknown", confidence: 0, details: "Unrecognized format" };
8491
- }
8492
- function detectFormatFromString(content) {
8493
- try {
8494
- const data = JSON.parse(content);
8495
- return detectFormat(data);
8496
- } catch {
8497
- return { format: "unknown", confidence: 0, details: "Invalid JSON" };
8498
- }
8499
- }
8500
- function isLikelyIndexerFormat(content) {
8501
- const result = detectFormatFromString(content);
8502
- return result.format === "indexer" || result.format === "indexer-export";
8503
- }
8504
-
8505
- // src/cli/commands/import.ts
8506
- var importCommand = new import_commander13.Command("import").description("Import skills from JSON file").argument("<file>", "JSON file to import").option("--from-indexer", "Import from skill-indexer export format").option("--auto-detect", "Auto-detect format (default: true)", true).action(async (file, options, command) => {
8507
- const globalOpts = command.optsWithGlobals();
8508
- try {
8509
- if (!fs14.existsSync(file)) {
8510
- printError(`File not found: ${file}`);
8511
- process.exit(1);
8908
+ /**
8909
+ * Import skills from indexer database into SkillBank
8910
+ */
8911
+ async importFromIndexerDb(options) {
8912
+ if (!this.skillBank) {
8913
+ throw new Error("SkillBank not configured. Use integrated mode.");
8512
8914
  }
8513
- const content = fs14.readFileSync(file, "utf-8");
8514
- let skills;
8515
- const isIndexerFormat = options.fromIndexer || options.autoDetect !== false && isLikelyIndexerFormat(content);
8516
- if (isIndexerFormat) {
8517
- if (!globalOpts.quiet) {
8518
- printInfo("Detected skill-indexer format, converting...");
8519
- }
8520
- try {
8521
- const { skills: converted, stats, exportMetadata } = parseIndexerExport(content);
8522
- skills = converted;
8523
- if (!globalOpts.quiet) {
8524
- printInfo(`Converted ${stats.total} skills (${stats.withStructuredContent} with structured content)`);
8525
- if (exportMetadata) {
8526
- printInfo(`Original export from: ${exportMetadata.exportedAt}`);
8527
- }
8528
- }
8529
- } catch (err) {
8530
- printError(`Failed to parse indexer export: ${err.message}`);
8531
- process.exit(1);
8915
+ await this.initialize();
8916
+ if (!this.db) {
8917
+ throw new Error(
8918
+ "Scraper database not available. Build the scraper module first."
8919
+ );
8920
+ }
8921
+ const result = { imported: 0, failed: 0, skills: [] };
8922
+ try {
8923
+ let skills;
8924
+ if (options?.status) {
8925
+ skills = this.db.getSkillsByStatus?.(options.status) || [];
8926
+ } else {
8927
+ skills = this.db.getAllSkills?.() || [];
8532
8928
  }
8533
- } else {
8534
- try {
8535
- const parsed = JSON.parse(content);
8536
- skills = Array.isArray(parsed) ? parsed : [parsed];
8537
- } catch {
8538
- printError("Invalid JSON file");
8539
- process.exit(1);
8929
+ if (options?.limit) {
8930
+ skills = skills.slice(0, options.limit);
8540
8931
  }
8541
8932
  for (const skill of skills) {
8542
- skill.createdAt = new Date(skill.createdAt);
8543
- skill.updatedAt = new Date(skill.updatedAt);
8544
- if (skill.metrics.lastUsed) {
8545
- skill.metrics.lastUsed = new Date(skill.metrics.lastUsed);
8546
- }
8547
- if (skill.source?.importedAt) {
8548
- skill.source.importedAt = new Date(skill.source.importedAt);
8549
- }
8550
- if (skill.externalSource?.scrapedAt) {
8551
- skill.externalSource.scrapedAt = new Date(skill.externalSource.scrapedAt);
8933
+ try {
8934
+ const converted = convertIndexerSkill(skill);
8935
+ await this.skillBank.saveSkill(converted.skill);
8936
+ result.imported++;
8937
+ result.skills.push(converted.skill);
8938
+ } catch (err) {
8939
+ result.failed++;
8552
8940
  }
8553
8941
  }
8942
+ } catch (err) {
8943
+ throw new Error(`Import failed: ${err.message}`);
8554
8944
  }
8555
- const skillBank = await createSkillBankFromOptions(globalOpts);
8556
- const result = await skillBank.importSkills(skills);
8557
- if (globalOpts.json) {
8558
- console.log(JSON.stringify({
8559
- ...result,
8560
- format: isIndexerFormat ? "indexer" : "skill-tree"
8561
- }, null, 2));
8562
- return;
8945
+ return result;
8946
+ }
8947
+ /**
8948
+ * Get default skill sources from config
8949
+ */
8950
+ getDefaultSources() {
8951
+ const configSources = this.globalConfig.indexer.sources;
8952
+ if (configSources.length > 0) {
8953
+ return configSources.map((url) => ({
8954
+ type: "awesome-list",
8955
+ url
8956
+ }));
8563
8957
  }
8564
- printSuccess(`Imported ${result.imported} skill(s)`);
8565
- if (result.failed > 0) {
8566
- printWarning(`Failed to import ${result.failed} skill(s)`);
8958
+ return [
8959
+ {
8960
+ type: "awesome-list",
8961
+ url: "https://github.com/VoltAgent/awesome-agent-skills"
8962
+ }
8963
+ ];
8964
+ }
8965
+ /**
8966
+ * Close database connections
8967
+ */
8968
+ async close() {
8969
+ if (this.db && typeof this.db.close === "function") {
8970
+ this.db.close();
8567
8971
  }
8568
- } catch (error) {
8569
- printError(error.message);
8570
- process.exit(1);
8972
+ this.db = void 0;
8973
+ this.initialized = false;
8571
8974
  }
8572
- });
8975
+ };
8976
+ function createIntegratedIndexer(skillBank, config2 = {}) {
8977
+ return new IndexerService(config2, skillBank);
8978
+ }
8573
8979
 
8574
- // src/cli/commands/indexer/index.ts
8575
- var import_commander20 = require("commander");
8980
+ // src/index.ts
8981
+ var VERSION = "0.1.0";
8576
8982
 
8577
- // src/cli/commands/indexer/scrape.ts
8578
- var import_commander14 = require("commander");
8579
- var import_child_process2 = require("child_process");
8983
+ // src/cli/commands/list.ts
8984
+ var import_commander = require("commander");
8580
8985
 
8581
- // src/services/indexer.ts
8986
+ // src/cli/utils/paths.ts
8987
+ var fs13 = __toESM(require("fs"));
8582
8988
  var path13 = __toESM(require("path"));
8583
- var fs15 = __toESM(require("fs"));
8584
- function hasIndexerSupport(storage) {
8585
- const s = storage;
8586
- return typeof s.addRelationship === "function" && typeof s.getTaxonomyTree === "function";
8989
+ var os2 = __toESM(require("os"));
8990
+ var DEFAULT_PATHS = [
8991
+ ".claude/skills",
8992
+ // Project local (Claude)
8993
+ ".agent/skills",
8994
+ // Project local (universal)
8995
+ "~/.claude/skills",
8996
+ // User global (Claude)
8997
+ "~/.agent/skills"
8998
+ // User global (universal)
8999
+ ];
9000
+ function expandHome(p) {
9001
+ if (p.startsWith("~/")) {
9002
+ return path13.join(os2.homedir(), p.slice(2));
9003
+ }
9004
+ return p;
8587
9005
  }
8588
- var IndexerService = class {
8589
- constructor(config2 = {}, skillBank) {
8590
- this.initialized = false;
8591
- this.globalConfig = loadConfig();
8592
- this.serviceConfig = {
8593
- githubToken: config2.githubToken || this.globalConfig.indexer.github_token,
8594
- anthropicApiKey: config2.anthropicApiKey || this.globalConfig.indexer.anthropic_key,
8595
- batchSize: config2.batchSize || this.globalConfig.indexer.batch_size,
8596
- minConfidence: config2.minConfidence || this.globalConfig.indexer.min_confidence,
8597
- concurrency: config2.concurrency || 3,
8598
- cacheTtlSeconds: config2.cacheTtlSeconds || 3600,
8599
- databasePath: config2.databasePath,
8600
- cacheDir: config2.cacheDir,
8601
- claudeModel: config2.claudeModel
8602
- };
8603
- this.skillBank = skillBank;
9006
+ function dirExists(p) {
9007
+ try {
9008
+ return fs13.statSync(p).isDirectory();
9009
+ } catch {
9010
+ return false;
9011
+ }
9012
+ }
9013
+ function resolveSkillPath(explicitPath) {
9014
+ if (explicitPath) {
9015
+ const resolved = expandHome(explicitPath);
9016
+ return path13.resolve(resolved);
9017
+ }
9018
+ const envPath = process.env.SKILL_TREE_PATH;
9019
+ if (envPath) {
9020
+ return path13.resolve(expandHome(envPath));
9021
+ }
9022
+ for (const defaultPath of DEFAULT_PATHS) {
9023
+ const resolved = path13.resolve(expandHome(defaultPath));
9024
+ if (dirExists(resolved)) {
9025
+ return resolved;
9026
+ }
9027
+ }
9028
+ return path13.resolve(".claude/skills");
9029
+ }
9030
+ function ensureDir(dir) {
9031
+ if (!dirExists(dir)) {
9032
+ fs13.mkdirSync(dir, { recursive: true });
9033
+ }
9034
+ }
9035
+
9036
+ // src/cli/utils/skillbank.ts
9037
+ function getMaterializationConfig() {
9038
+ const config2 = loadConfig();
9039
+ const mat = config2.materialization;
9040
+ if (!mat?.enabled) return void 0;
9041
+ return {
9042
+ enabled: true,
9043
+ mode: mat.mode ?? "symlink",
9044
+ symlinkPaths: mat.symlink_paths?.length ? mat.symlink_paths : void 0,
9045
+ agentsMdPath: mat.agents_md_path || void 0,
9046
+ agentsMdFormat: mat.agents_md_format ?? "xml",
9047
+ debounceMs: mat.debounce_ms ?? 500
9048
+ };
9049
+ }
9050
+ async function createSkillBankFromOptions(options) {
9051
+ const skillPath = resolveSkillPath(options.path);
9052
+ ensureDir(skillPath);
9053
+ const skillBank = new SkillBank({
9054
+ storage: {
9055
+ basePath: skillPath,
9056
+ openSkillsCompatible: true
9057
+ },
9058
+ materialization: getMaterializationConfig()
9059
+ });
9060
+ await skillBank.initialize();
9061
+ return skillBank;
9062
+ }
9063
+ function getSkillPath(options) {
9064
+ return resolveSkillPath(options.path);
9065
+ }
9066
+ var getSkillBank = createSkillBankFromOptions;
9067
+
9068
+ // src/cli/utils/output.ts
9069
+ var import_chalk = __toESM(require("chalk"));
9070
+ function formatSkillLine(skill) {
9071
+ const status = formatStatus(skill.status);
9072
+ const version = import_chalk.default.dim(`v${skill.version}`);
9073
+ const id = import_chalk.default.bold(skill.id);
9074
+ const name = skill.name !== skill.id ? import_chalk.default.dim(` (${skill.name})`) : "";
9075
+ const tags = skill.tags.length > 0 ? import_chalk.default.dim(` [${skill.tags.join(", ")}]`) : "";
9076
+ return `${status} ${id}${name} ${version}${tags}`;
9077
+ }
9078
+ function formatStatus(status) {
9079
+ switch (status) {
9080
+ case "active":
9081
+ return import_chalk.default.green("\u25CF");
9082
+ case "draft":
9083
+ return import_chalk.default.yellow("\u25CB");
9084
+ case "deprecated":
9085
+ return import_chalk.default.red("\u2717");
9086
+ case "experimental":
9087
+ return import_chalk.default.blue("\u25D0");
9088
+ default:
9089
+ return import_chalk.default.gray("?");
9090
+ }
9091
+ }
9092
+ function formatSkillDetail(skill) {
9093
+ const lines = [];
9094
+ lines.push(import_chalk.default.bold(`${skill.name} v${skill.version}`));
9095
+ lines.push(import_chalk.default.dim("\u2500".repeat(50)));
9096
+ lines.push(`${import_chalk.default.dim("Status:")} ${formatStatusLabel(skill.status)}`);
9097
+ lines.push(`${import_chalk.default.dim("Author:")} ${skill.author}`);
9098
+ if (skill.tags.length > 0) {
9099
+ lines.push(`${import_chalk.default.dim("Tags:")} ${skill.tags.join(", ")}`);
9100
+ }
9101
+ if (skill.parentVersion) {
9102
+ lines.push(`${import_chalk.default.dim("Parent:")} v${skill.parentVersion}`);
9103
+ }
9104
+ if (skill.derivedFrom && skill.derivedFrom.length > 0) {
9105
+ lines.push(`${import_chalk.default.dim("Derived:")} ${skill.derivedFrom.join(", ")}`);
9106
+ }
9107
+ const { usageCount, successRate } = skill.metrics;
9108
+ if (usageCount > 0) {
9109
+ lines.push(`${import_chalk.default.dim("Usage:")} ${usageCount} times, ${Math.round(successRate * 100)}% success`);
9110
+ }
9111
+ lines.push("");
9112
+ lines.push(import_chalk.default.dim("Description"));
9113
+ lines.push(import_chalk.default.dim("\u2500".repeat(50)));
9114
+ lines.push(skill.description);
9115
+ lines.push("");
9116
+ if (skill.instructions) {
9117
+ lines.push(import_chalk.default.dim("Instructions"));
9118
+ lines.push(import_chalk.default.dim("\u2500".repeat(50)));
9119
+ lines.push(skill.instructions);
9120
+ }
9121
+ return lines.join("\n");
9122
+ }
9123
+ function formatStatusLabel(status) {
9124
+ switch (status) {
9125
+ case "active":
9126
+ return import_chalk.default.green("active");
9127
+ case "draft":
9128
+ return import_chalk.default.yellow("draft");
9129
+ case "deprecated":
9130
+ return import_chalk.default.red("deprecated");
9131
+ case "experimental":
9132
+ return import_chalk.default.blue("experimental");
9133
+ default:
9134
+ return import_chalk.default.gray(status);
9135
+ }
9136
+ }
9137
+ function formatVersionHistory(versions) {
9138
+ const lines = [];
9139
+ lines.push(import_chalk.default.bold("Version History"));
9140
+ lines.push(import_chalk.default.dim("\u2500".repeat(50)));
9141
+ for (const v of versions.reverse()) {
9142
+ const date = v.createdAt.toISOString().split("T")[0];
9143
+ const hash = import_chalk.default.dim(`#${v.contentHash.slice(0, 7)}`);
9144
+ const changelog = v.changelog ? ` - ${v.changelog}` : "";
9145
+ lines.push(` ${import_chalk.default.cyan(`v${v.version}`)} ${import_chalk.default.dim(date)} ${hash}${changelog}`);
9146
+ }
9147
+ return lines.join("\n");
9148
+ }
9149
+ function formatDiff(versionA, versionB, changes) {
9150
+ const lines = [];
9151
+ lines.push(import_chalk.default.bold(`Diff: v${versionA} \u2192 v${versionB}`));
9152
+ lines.push(import_chalk.default.dim("\u2500".repeat(50)));
9153
+ if (changes.modified.length === 0 && changes.added.length === 0 && changes.removed.length === 0) {
9154
+ lines.push(import_chalk.default.dim("No changes"));
9155
+ return lines.join("\n");
8604
9156
  }
8605
- /**
8606
- * Get effective configuration
8607
- */
8608
- getConfig() {
8609
- return { ...this.serviceConfig };
9157
+ for (const mod of changes.modified) {
9158
+ lines.push(import_chalk.default.yellow(`~ ${mod.field}`));
9159
+ lines.push(import_chalk.default.red(` - ${truncate(mod.oldValue, 60)}`));
9160
+ lines.push(import_chalk.default.green(` + ${truncate(mod.newValue, 60)}`));
8610
9161
  }
8611
- /**
8612
- * Initialize the service (lazy load scraper modules)
8613
- */
8614
- async initialize() {
8615
- if (this.initialized) return;
8616
- try {
8617
- const possiblePaths = [
8618
- // Relative to this file in dist
8619
- path13.resolve(__dirname, "../../scraper/dist"),
8620
- // Relative to project root
8621
- path13.resolve(process.cwd(), "scraper/dist"),
8622
- // Absolute paths from config
8623
- this.serviceConfig.cacheDir ? path13.resolve(this.serviceConfig.cacheDir, "../scraper/dist") : null
8624
- ].filter(Boolean);
8625
- let scraperBasePath = null;
8626
- for (const basePath of possiblePaths) {
8627
- const scraperIndex = path13.join(basePath, "scraper/index.js");
8628
- if (fs15.existsSync(scraperIndex)) {
8629
- scraperBasePath = basePath;
8630
- break;
8631
- }
8632
- }
8633
- if (!scraperBasePath) {
8634
- throw new Error("Scraper modules not found. Run `cd scraper && npm run build` first.");
8635
- }
8636
- const scraperPath = path13.join(scraperBasePath, "scraper/index.js");
8637
- const indexerPath = path13.join(scraperBasePath, "indexer/index.js");
8638
- const databasePath = path13.join(scraperBasePath, "database/index.js");
8639
- this.scraperModule = await import(
8640
- /* webpackIgnore: true */
8641
- scraperPath
8642
- );
8643
- this.indexerModule = await import(
8644
- /* webpackIgnore: true */
8645
- indexerPath
8646
- );
8647
- this.databaseModule = await import(
8648
- /* webpackIgnore: true */
8649
- databasePath
8650
- );
8651
- if (this.databaseModule.createDatabase) {
8652
- const dbPath = this.serviceConfig.databasePath || path13.join(process.cwd(), "scraper/data/skills.db");
8653
- this.db = this.databaseModule.createDatabase(dbPath);
8654
- }
8655
- this.initialized = true;
8656
- } catch (err) {
8657
- console.warn(`Scraper modules not available: ${err.message}`);
8658
- console.warn("Some indexer features will be limited.");
8659
- this.initialized = true;
8660
- }
9162
+ for (const add of changes.added) {
9163
+ lines.push(import_chalk.default.green(`+ ${add.type}: ${add.value}`));
8661
9164
  }
8662
- /**
8663
- * Check if the indexer is available
8664
- */
8665
- isAvailable() {
8666
- return this.initialized && !!this.scraperModule;
9165
+ for (const rem of changes.removed) {
9166
+ lines.push(import_chalk.default.red(`- ${rem.type}: ${rem.value}`));
8667
9167
  }
8668
- /**
8669
- * Check if running in degraded mode (no scraper modules)
8670
- */
8671
- isDegradedMode() {
8672
- return this.initialized && !this.scraperModule;
9168
+ return lines.join("\n");
9169
+ }
9170
+ function formatStats(stats) {
9171
+ const lines = [];
9172
+ lines.push(import_chalk.default.bold("Skill Bank Statistics"));
9173
+ lines.push(import_chalk.default.dim("\u2500".repeat(50)));
9174
+ lines.push(`${import_chalk.default.dim("Total skills:")} ${stats.totalSkills}`);
9175
+ lines.push("");
9176
+ lines.push(import_chalk.default.dim("By Status:"));
9177
+ lines.push(` ${import_chalk.default.green("\u25CF")} Active: ${stats.byStatus.active}`);
9178
+ lines.push(` ${import_chalk.default.yellow("\u25CB")} Draft: ${stats.byStatus.draft}`);
9179
+ lines.push(` ${import_chalk.default.blue("\u25D0")} Experimental: ${stats.byStatus.experimental}`);
9180
+ lines.push(` ${import_chalk.default.red("\u2717")} Deprecated: ${stats.byStatus.deprecated}`);
9181
+ lines.push("");
9182
+ if (Object.keys(stats.byTag).length > 0) {
9183
+ lines.push(import_chalk.default.dim("By Tag:"));
9184
+ const sortedTags = Object.entries(stats.byTag).sort((a, b) => b[1] - a[1]);
9185
+ for (const [tag, count] of sortedTags.slice(0, 10)) {
9186
+ lines.push(` ${tag}: ${count}`);
9187
+ }
9188
+ lines.push("");
8673
9189
  }
8674
- /**
8675
- * Scrape skills from GitHub sources
8676
- */
8677
- async scrape(sources, options) {
8678
- await this.initialize();
8679
- if (!this.scraperModule || !this.db) {
8680
- throw new Error(
8681
- "Scraper not available. Build the scraper module first: cd scraper && npm run build"
8682
- );
9190
+ if (stats.totalUsage > 0) {
9191
+ lines.push(import_chalk.default.dim("Usage:"));
9192
+ lines.push(` Total uses: ${stats.totalUsage}`);
9193
+ lines.push(` Avg success: ${Math.round(stats.avgSuccessRate * 100)}%`);
9194
+ }
9195
+ return lines.join("\n");
9196
+ }
9197
+ function printError(message) {
9198
+ console.error(import_chalk.default.red(`Error: ${message}`));
9199
+ }
9200
+ function printSuccess(message) {
9201
+ console.log(import_chalk.default.green(`\u2713 ${message}`));
9202
+ }
9203
+ function printWarning(message) {
9204
+ console.log(import_chalk.default.yellow(`\u26A0 ${message}`));
9205
+ }
9206
+ function printInfo(message) {
9207
+ console.log(import_chalk.default.dim(message));
9208
+ }
9209
+ function truncate(text, maxLength) {
9210
+ const oneLine = text.replace(/\n/g, " ");
9211
+ if (oneLine.length <= maxLength) return oneLine;
9212
+ return oneLine.slice(0, maxLength - 3) + "...";
9213
+ }
9214
+
9215
+ // src/cli/commands/list.ts
9216
+ var listCommand = new import_commander.Command("list").description("List all skills").option("-s, --status <status>", "Filter by status (active, draft, deprecated, experimental)").option("-t, --tag <tag>", "Filter by tag").option("-a, --author <author>", "Filter by author").action(async (options, command) => {
9217
+ const globalOpts = command.optsWithGlobals();
9218
+ try {
9219
+ const skillBank = await createSkillBankFromOptions(globalOpts);
9220
+ const filter = {};
9221
+ if (options.status) {
9222
+ filter.status = [options.status];
8683
9223
  }
8684
- const result = {
8685
- discovered: 0,
8686
- scraped: 0,
8687
- skipped: 0,
8688
- failed: 0,
8689
- unchanged: 0,
8690
- errors: []
8691
- };
8692
- try {
8693
- const scraper = this.scraperModule.createScraper({
8694
- githubToken: this.serviceConfig.githubToken,
8695
- cacheDir: this.serviceConfig.cacheDir,
8696
- cacheTtlSeconds: this.serviceConfig.cacheTtlSeconds
8697
- });
8698
- for (const source of sources) {
8699
- try {
8700
- let skills;
8701
- if (source.type === "awesome-list") {
8702
- skills = await scraper.scrapeAwesomeList(source.url, {
8703
- force: options?.force
8704
- });
8705
- } else {
8706
- skills = await scraper.scrapeRepository(source.url, {
8707
- force: options?.force
8708
- });
8709
- }
8710
- result.discovered += skills.length;
8711
- for (const skill of skills) {
8712
- try {
8713
- const existing = this.db.getSkillBySlug?.(skill.slug);
8714
- if (existing && !options?.force) {
8715
- result.unchanged++;
8716
- } else {
8717
- this.db.saveSkill?.(skill);
8718
- result.scraped++;
8719
- }
8720
- } catch (err) {
8721
- result.failed++;
8722
- result.errors.push(`Failed to save skill ${skill.slug}: ${err.message}`);
8723
- }
8724
- }
8725
- } catch (err) {
8726
- result.failed++;
8727
- result.errors.push(`Failed to scrape ${source.url}: ${err.message}`);
8728
- }
8729
- }
8730
- } catch (err) {
8731
- result.errors.push(`Scrape failed: ${err.message}`);
9224
+ if (options.tag) {
9225
+ filter.tags = [options.tag];
8732
9226
  }
8733
- return result;
8734
- }
8735
- /**
8736
- * Classify unindexed skills using AI
8737
- */
8738
- async classify(options) {
8739
- await this.initialize();
8740
- if (!this.indexerModule || !this.db) {
8741
- throw new Error(
8742
- "Indexer not available. Build the scraper module first: cd scraper && npm run build"
8743
- );
9227
+ if (options.author) {
9228
+ filter.author = options.author;
8744
9229
  }
8745
- const result = {
8746
- indexed: 0,
8747
- skipped: 0,
8748
- failed: 0,
8749
- errors: []
8750
- };
8751
- try {
8752
- const indexer = this.indexerModule.createIndexer({
8753
- anthropicApiKey: this.serviceConfig.anthropicApiKey,
8754
- model: this.serviceConfig.claudeModel || "claude-sonnet-4-20250514",
8755
- minConfidence: this.serviceConfig.minConfidence
8756
- });
8757
- let skills;
8758
- if (options?.skillId) {
8759
- const skill = this.db.getSkillBySlug?.(options.skillId);
8760
- skills = skill ? [skill] : [];
8761
- } else if (options?.all) {
8762
- skills = this.db.getAllSkills?.() || [];
8763
- } else {
8764
- skills = this.db.getSkillsByStatus?.("raw") || [];
8765
- }
8766
- const batchSize = this.serviceConfig.batchSize || 10;
8767
- for (let i = 0; i < skills.length; i += batchSize) {
8768
- const batch = skills.slice(i, i + batchSize);
8769
- for (const skill of batch) {
8770
- if (skill.status === "indexed" && !options?.all) {
8771
- result.skipped++;
8772
- continue;
8773
- }
8774
- try {
8775
- const classification = await indexer.classifySkill(skill);
8776
- skill.status = "indexed";
8777
- skill.primaryPath = classification.primaryPath;
8778
- skill.secondaryPaths = classification.secondaryPaths;
8779
- skill.confidence = classification.confidence;
8780
- skill.classificationReasoning = classification.reasoning;
8781
- skill.indexedAt = (/* @__PURE__ */ new Date()).toISOString();
8782
- this.db.saveSkill?.(skill);
8783
- result.indexed++;
8784
- } catch (err) {
8785
- result.failed++;
8786
- result.errors.push(`Failed to classify ${skill.slug}: ${err.message}`);
8787
- }
8788
- }
8789
- }
8790
- } catch (err) {
8791
- result.errors.push(`Classification failed: ${err.message}`);
9230
+ const skills = await skillBank.listSkills(
9231
+ Object.keys(filter).length > 0 ? filter : void 0
9232
+ );
9233
+ if (globalOpts.json) {
9234
+ console.log(JSON.stringify(skills, null, 2));
9235
+ return;
9236
+ }
9237
+ if (skills.length === 0) {
9238
+ printInfo(`No skills found in ${getSkillPath(globalOpts)}`);
9239
+ return;
9240
+ }
9241
+ if (!globalOpts.quiet) {
9242
+ printInfo(`Skills in ${getSkillPath(globalOpts)}:
9243
+ `);
9244
+ }
9245
+ for (const skill of skills) {
9246
+ console.log(formatSkillLine(skill));
9247
+ }
9248
+ if (!globalOpts.quiet) {
9249
+ console.log();
9250
+ printInfo(`${skills.length} skill(s)`);
9251
+ }
9252
+ } catch (error) {
9253
+ printError(error.message);
9254
+ process.exit(1);
9255
+ }
9256
+ });
9257
+
9258
+ // src/cli/commands/show.ts
9259
+ var import_commander2 = require("commander");
9260
+ var showCommand = new import_commander2.Command("show").description("Show skill details").argument("<id>", "Skill ID").option("-v, --version <version>", "Show specific version").action(async (id, options, command) => {
9261
+ const globalOpts = command.optsWithGlobals();
9262
+ try {
9263
+ const skillBank = await createSkillBankFromOptions(globalOpts);
9264
+ const skill = await skillBank.getSkill(id, options.version);
9265
+ if (!skill) {
9266
+ printError(`Skill not found: ${id}${options.version ? `@${options.version}` : ""}`);
9267
+ process.exit(1);
8792
9268
  }
8793
- return result;
9269
+ if (globalOpts.json) {
9270
+ console.log(JSON.stringify(skill, null, 2));
9271
+ return;
9272
+ }
9273
+ console.log(formatSkillDetail(skill));
9274
+ } catch (error) {
9275
+ printError(error.message);
9276
+ process.exit(1);
8794
9277
  }
8795
- /**
8796
- * Detect relationships between skills
8797
- */
8798
- async detectRelationships(options) {
8799
- await this.initialize();
8800
- const result = {
8801
- detected: 0,
8802
- skipped: 0,
8803
- errors: []
8804
- };
8805
- if (this.skillBank) {
8806
- try {
8807
- const storage = this.skillBank.getStorage();
8808
- if (hasIndexerSupport(storage)) {
8809
- const skills = await this.skillBank.listSkills();
8810
- const targetSkills = options?.skillId ? skills.filter((s) => s.id === options.skillId) : skills;
8811
- for (const skill of targetSkills) {
8812
- const relationships = this.detectSkillRelationships(skill, skills);
8813
- for (const rel of relationships) {
8814
- try {
8815
- await storage.addRelationship(
8816
- skill.id,
8817
- rel.targetSkillId,
8818
- rel.type,
8819
- rel.confidence,
8820
- rel.reasoning
8821
- );
8822
- result.detected++;
8823
- } catch (err) {
8824
- result.skipped++;
8825
- }
8826
- }
8827
- }
8828
- }
8829
- } catch (err) {
8830
- result.errors.push(`Relationship detection failed: ${err.message}`);
8831
- }
8832
- return result;
9278
+ });
9279
+
9280
+ // src/cli/commands/search.ts
9281
+ var import_commander3 = require("commander");
9282
+ var searchCommand = new import_commander3.Command("search").description("Search skills by text").argument("<query>", "Search query").action(async (query, options, command) => {
9283
+ const globalOpts = command.optsWithGlobals();
9284
+ try {
9285
+ const skillBank = await createSkillBankFromOptions(globalOpts);
9286
+ const skills = await skillBank.searchSkills(query);
9287
+ if (globalOpts.json) {
9288
+ console.log(JSON.stringify(skills, null, 2));
9289
+ return;
8833
9290
  }
8834
- if (!this.db) {
8835
- throw new Error(
8836
- "Neither SkillBank nor scraper database available."
8837
- );
9291
+ if (skills.length === 0) {
9292
+ printInfo(`No skills found matching "${query}"`);
9293
+ return;
8838
9294
  }
8839
- try {
8840
- const skills = this.db.getAllSkills?.() || [];
8841
- const targetSkills = options?.skillId ? skills.filter((s) => s.slug === options.skillId) : skills.filter((s) => s.status === "indexed");
8842
- for (const skill of targetSkills) {
8843
- const relationships = this.detectSkillRelationships(skill, skills);
8844
- for (const rel of relationships) {
8845
- try {
8846
- this.db.saveRelationship?.({
8847
- sourceSkillId: skill.id,
8848
- ...rel
8849
- });
8850
- result.detected++;
8851
- } catch (err) {
8852
- result.skipped++;
8853
- }
8854
- }
8855
- }
8856
- } catch (err) {
8857
- result.errors.push(`Relationship detection failed: ${err.message}`);
9295
+ if (!globalOpts.quiet) {
9296
+ printInfo(`Results for "${query}":
9297
+ `);
8858
9298
  }
8859
- return result;
9299
+ for (const skill of skills) {
9300
+ console.log(formatSkillLine(skill));
9301
+ }
9302
+ if (!globalOpts.quiet) {
9303
+ console.log();
9304
+ printInfo(`${skills.length} result(s)`);
9305
+ }
9306
+ } catch (error) {
9307
+ printError(error.message);
9308
+ process.exit(1);
8860
9309
  }
8861
- /**
8862
- * Detect relationships for a single skill
8863
- */
8864
- detectSkillRelationships(skill, allSkills) {
8865
- const relationships = [];
8866
- const skillId = skill.id || skill.slug;
8867
- const skillContent = `${skill.name} ${skill.description} ${skill.instructions || ""} ${skill.content || ""}`.toLowerCase();
8868
- for (const other of allSkills) {
8869
- const otherId = other.id || other.slug;
8870
- if (otherId === skillId) continue;
8871
- const otherName = (other.name || "").toLowerCase();
8872
- const dependencyPatterns = ["requires", "depends on", "uses", "needs", "builds on"];
8873
- for (const pattern of dependencyPatterns) {
8874
- if (skillContent.includes(`${pattern} ${otherName}`)) {
8875
- relationships.push({
8876
- targetSkillId: otherId,
8877
- type: "depends_on",
8878
- confidence: 0.7,
8879
- reasoning: `Content mentions "${pattern} ${other.name}"`
8880
- });
8881
- break;
8882
- }
8883
- }
8884
- const extensionPatterns = ["extends", "improves", "enhances", "builds upon"];
8885
- for (const pattern of extensionPatterns) {
8886
- if (skillContent.includes(`${pattern} ${otherName}`)) {
8887
- relationships.push({
8888
- targetSkillId: otherId,
8889
- type: "extends",
8890
- confidence: 0.7,
8891
- reasoning: `Content mentions "${pattern} ${other.name}"`
8892
- });
8893
- break;
8894
- }
8895
- }
8896
- const alternativePatterns = ["alternative to", "instead of", "replacement for"];
8897
- for (const pattern of alternativePatterns) {
8898
- if (skillContent.includes(`${pattern} ${otherName}`)) {
8899
- relationships.push({
8900
- targetSkillId: otherId,
8901
- type: "alternative",
8902
- confidence: 0.6,
8903
- reasoning: `Content mentions "${pattern} ${other.name}"`
8904
- });
8905
- break;
8906
- }
8907
- }
8908
- const skillPath = skill.taxonomy?.primaryPath || skill.primaryPath || [];
8909
- const otherPath = other.taxonomy?.primaryPath || other.primaryPath || [];
8910
- if (skillPath.length >= 2 && otherPath.length >= 2) {
8911
- if (skillPath[0] === otherPath[0] && skillPath[1] === otherPath[1]) {
8912
- relationships.push({
8913
- targetSkillId: otherId,
8914
- type: "related",
8915
- confidence: 0.5,
8916
- reasoning: `Same taxonomy category: ${skillPath.slice(0, 2).join(" > ")}`
8917
- });
8918
- }
8919
- }
9310
+ });
9311
+
9312
+ // src/cli/commands/stats.ts
9313
+ var import_commander4 = require("commander");
9314
+ var statsCommand = new import_commander4.Command("stats").description("Show skill bank statistics").action(async (options, command) => {
9315
+ const globalOpts = command.optsWithGlobals();
9316
+ try {
9317
+ const skillBank = await createSkillBankFromOptions(globalOpts);
9318
+ const stats = await skillBank.getStats();
9319
+ if (globalOpts.json) {
9320
+ console.log(JSON.stringify(stats, null, 2));
9321
+ return;
8920
9322
  }
8921
- return relationships;
9323
+ console.log(formatStats(stats));
9324
+ } catch (error) {
9325
+ printError(error.message);
9326
+ process.exit(1);
8922
9327
  }
8923
- /**
8924
- * Get taxonomy tree
8925
- */
8926
- async getTaxonomyTree(rootPath) {
8927
- await this.initialize();
8928
- if (this.skillBank) {
8929
- const storage = this.skillBank.getStorage();
8930
- if (hasIndexerSupport(storage)) {
8931
- const nodes = await storage.getTaxonomyTree(rootPath);
8932
- return this.wrapTreeNodes(nodes, rootPath);
8933
- }
9328
+ });
9329
+
9330
+ // src/cli/commands/versions.ts
9331
+ var import_commander5 = require("commander");
9332
+ var versionsCommand = new import_commander5.Command("versions").description("Show version history for a skill").argument("<id>", "Skill ID").action(async (id, options, command) => {
9333
+ const globalOpts = command.optsWithGlobals();
9334
+ try {
9335
+ const skillBank = await createSkillBankFromOptions(globalOpts);
9336
+ const versions = await skillBank.getVersionHistory(id);
9337
+ if (versions.length === 0) {
9338
+ printInfo(`No version history found for: ${id}`);
9339
+ return;
8934
9340
  }
8935
- const skills = this.skillBank ? await this.skillBank.listSkills() : this.db?.getAllSkills?.() || [];
8936
- return this.buildTaxonomyTreeFromSkills(skills, rootPath);
9341
+ if (globalOpts.json) {
9342
+ console.log(JSON.stringify(versions, null, 2));
9343
+ return;
9344
+ }
9345
+ console.log(formatVersionHistory(versions));
9346
+ } catch (error) {
9347
+ printError(error.message);
9348
+ process.exit(1);
8937
9349
  }
8938
- /**
8939
- * Wrap tree nodes returned from SQLite storage into a root node
8940
- */
8941
- wrapTreeNodes(nodes, rootPath) {
8942
- const root = {
8943
- id: "root",
8944
- name: rootPath?.join(" > ") || "All Skills",
8945
- path: rootPath || [],
8946
- skillCount: 0,
8947
- children: []
8948
- };
8949
- for (const node of nodes) {
8950
- root.children.push(this.convertToTaxonomyNode(node));
8951
- root.skillCount += this.countNodeSkills(node);
9350
+ });
9351
+
9352
+ // src/cli/commands/diff.ts
9353
+ var import_commander6 = require("commander");
9354
+ var diffCommand = new import_commander6.Command("diff").description("Compare two versions of a skill").argument("<id>", "Skill ID").argument("<version-a>", "First version").argument("<version-b>", "Second version").action(async (id, versionA, versionB, options, command) => {
9355
+ const globalOpts = command.optsWithGlobals();
9356
+ try {
9357
+ const skillBank = await createSkillBankFromOptions(globalOpts);
9358
+ const diff = await skillBank.compareVersions(id, versionA, versionB);
9359
+ if (globalOpts.json) {
9360
+ console.log(JSON.stringify(diff, null, 2));
9361
+ return;
9362
+ }
9363
+ console.log(formatDiff(diff.versionA, diff.versionB, diff.changes));
9364
+ } catch (error) {
9365
+ printError(error.message);
9366
+ process.exit(1);
9367
+ }
9368
+ });
9369
+
9370
+ // src/cli/commands/rollback.ts
9371
+ var import_commander7 = require("commander");
9372
+ var rollbackCommand = new import_commander7.Command("rollback").description("Rollback a skill to a previous version").argument("<id>", "Skill ID").requiredOption("--to <version>", "Version to rollback to").action(async (id, options, command) => {
9373
+ const globalOpts = command.optsWithGlobals();
9374
+ try {
9375
+ const skillBank = await createSkillBankFromOptions(globalOpts);
9376
+ const skill = await skillBank.rollbackSkill(id, options.to);
9377
+ if (globalOpts.json) {
9378
+ console.log(JSON.stringify(skill, null, 2));
9379
+ return;
8952
9380
  }
8953
- return root;
9381
+ printSuccess(`Rolled back ${id} to v${options.to} (new version: v${skill.version})`);
9382
+ } catch (error) {
9383
+ printError(error.message);
9384
+ process.exit(1);
8954
9385
  }
8955
- /**
8956
- * Convert storage tree node to TaxonomyNode format
8957
- */
8958
- convertToTaxonomyNode(node) {
8959
- return {
8960
- id: node.id,
8961
- name: node.name,
8962
- path: Array.isArray(node.path) ? node.path : (node.path || "").split("/"),
8963
- skillCount: node.skillCount || 0,
8964
- children: (node.children || []).map((child) => this.convertToTaxonomyNode(child))
8965
- };
9386
+ });
9387
+
9388
+ // src/cli/commands/fork.ts
9389
+ var import_commander8 = require("commander");
9390
+ var forkCommand = new import_commander8.Command("fork").description("Fork a skill to create a variant").argument("<id>", "Skill ID to fork from").requiredOption("--new-id <id>", "ID for the new forked skill").requiredOption("--reason <reason>", "Reason for forking").option("--name <name>", "Name for the forked skill").option("--from-version <version>", "Version to fork from (defaults to latest)").action(async (id, options, command) => {
9391
+ const globalOpts = command.optsWithGlobals();
9392
+ try {
9393
+ const skillBank = await createSkillBankFromOptions(globalOpts);
9394
+ const forked = await skillBank.forkSkill(id, {
9395
+ newId: options.newId,
9396
+ newName: options.name,
9397
+ reason: options.reason,
9398
+ fromVersion: options.fromVersion
9399
+ });
9400
+ if (globalOpts.json) {
9401
+ console.log(JSON.stringify(forked, null, 2));
9402
+ return;
9403
+ }
9404
+ printSuccess(`Forked ${id} \u2192 ${forked.id} (v${forked.version})`);
9405
+ } catch (error) {
9406
+ printError(error.message);
9407
+ process.exit(1);
8966
9408
  }
8967
- /**
8968
- * Count total skills in a node tree
8969
- */
8970
- countNodeSkills(node) {
8971
- let count = node.skillCount || 0;
8972
- for (const child of node.children || []) {
8973
- count += this.countNodeSkills(child);
9409
+ });
9410
+
9411
+ // src/cli/commands/deprecate.ts
9412
+ var import_commander9 = require("commander");
9413
+ var deprecateCommand = new import_commander9.Command("deprecate").description("Mark a skill as deprecated").argument("<id>", "Skill ID").action(async (id, options, command) => {
9414
+ const globalOpts = command.optsWithGlobals();
9415
+ try {
9416
+ const skillBank = await createSkillBankFromOptions(globalOpts);
9417
+ const skill = await skillBank.deprecateSkill(id);
9418
+ if (globalOpts.json) {
9419
+ console.log(JSON.stringify(skill, null, 2));
9420
+ return;
8974
9421
  }
8975
- return count;
9422
+ printSuccess(`Deprecated: ${id} (v${skill.version})`);
9423
+ } catch (error) {
9424
+ printError(error.message);
9425
+ process.exit(1);
8976
9426
  }
8977
- /**
8978
- * Build taxonomy tree from flat nodes
8979
- */
8980
- buildTaxonomyTree(nodes, rootPath) {
8981
- const root = {
8982
- id: "root",
8983
- name: rootPath?.join(" > ") || "All Skills",
8984
- path: rootPath || [],
8985
- skillCount: 0,
8986
- children: []
8987
- };
8988
- const nodeMap = /* @__PURE__ */ new Map();
8989
- nodeMap.set("root", root);
8990
- for (const node of nodes) {
8991
- const taxNode = {
8992
- id: node.id,
8993
- name: node.name,
8994
- path: node.path,
8995
- skillCount: node.skillCount || 0,
8996
- children: []
8997
- };
8998
- nodeMap.set(node.id, taxNode);
9427
+ });
9428
+
9429
+ // src/cli/commands/activate.ts
9430
+ var import_commander10 = require("commander");
9431
+ var activateCommand = new import_commander10.Command("activate").description("Mark a skill as active").argument("<id>", "Skill ID").action(async (id, options, command) => {
9432
+ const globalOpts = command.optsWithGlobals();
9433
+ try {
9434
+ const skillBank = await createSkillBankFromOptions(globalOpts);
9435
+ const skill = await skillBank.getSkill(id);
9436
+ if (!skill) {
9437
+ printError(`Skill not found: ${id}`);
9438
+ process.exit(1);
8999
9439
  }
9000
- for (const node of nodes) {
9001
- const taxNode = nodeMap.get(node.id);
9002
- const parentId = node.parentId || "root";
9003
- const parent = nodeMap.get(parentId);
9004
- if (parent) {
9005
- parent.children.push(taxNode);
9006
- parent.skillCount += taxNode.skillCount;
9007
- }
9440
+ skill.status = "active";
9441
+ skill.updatedAt = /* @__PURE__ */ new Date();
9442
+ await skillBank.saveSkill(skill);
9443
+ if (globalOpts.json) {
9444
+ console.log(JSON.stringify(skill, null, 2));
9445
+ return;
9008
9446
  }
9009
- return root;
9447
+ printSuccess(`Activated: ${id} (v${skill.version})`);
9448
+ } catch (error) {
9449
+ printError(error.message);
9450
+ process.exit(1);
9010
9451
  }
9011
- /**
9012
- * Build taxonomy tree from skills
9013
- */
9014
- buildTaxonomyTreeFromSkills(skills, rootPath) {
9015
- const root = {
9016
- id: "root",
9017
- name: rootPath?.join(" > ") || "All Skills",
9018
- path: rootPath || [],
9019
- skillCount: 0,
9020
- children: []
9021
- };
9022
- const nodeMap = /* @__PURE__ */ new Map();
9023
- for (const skill of skills) {
9024
- const taxonomy = skill.taxonomy || (skill.primaryPath ? { primaryPath: skill.primaryPath } : null);
9025
- if (!taxonomy?.primaryPath) continue;
9026
- const skillPath = taxonomy.primaryPath;
9027
- if (rootPath && rootPath.length > 0) {
9028
- let matches = true;
9029
- for (let i = 0; i < rootPath.length; i++) {
9030
- if (skillPath[i] !== rootPath[i]) {
9031
- matches = false;
9032
- break;
9033
- }
9034
- }
9035
- if (!matches) continue;
9036
- }
9037
- let currentPath = [];
9038
- let parent = root;
9039
- for (const segment of skillPath) {
9040
- currentPath = [...currentPath, segment];
9041
- const pathKey = currentPath.join("/");
9042
- let node = nodeMap.get(pathKey);
9043
- if (!node) {
9044
- node = {
9045
- id: pathKey,
9046
- name: segment,
9047
- path: [...currentPath],
9048
- skillCount: 0,
9049
- children: []
9050
- };
9051
- nodeMap.set(pathKey, node);
9052
- parent.children.push(node);
9053
- }
9054
- parent = node;
9055
- }
9056
- parent.skillCount++;
9057
- root.skillCount++;
9452
+ });
9453
+
9454
+ // src/cli/commands/delete.ts
9455
+ var import_commander11 = require("commander");
9456
+ var deleteCommand = new import_commander11.Command("delete").description("Delete a skill").argument("<id>", "Skill ID").option("-f, --force", "Skip confirmation").option("-v, --version <version>", "Delete specific version only").action(async (id, options, command) => {
9457
+ const globalOpts = command.optsWithGlobals();
9458
+ try {
9459
+ const skillBank = await createSkillBankFromOptions(globalOpts);
9460
+ const skill = await skillBank.getSkill(id, options.version);
9461
+ if (!skill) {
9462
+ printError(`Skill not found: ${id}${options.version ? `@${options.version}` : ""}`);
9463
+ process.exit(1);
9058
9464
  }
9059
- return root;
9060
- }
9061
- /**
9062
- * Get indexer statistics
9063
- */
9064
- async getStats() {
9065
- await this.initialize();
9066
- if (this.skillBank) {
9067
- const skills = await this.skillBank.listSkills();
9068
- const storage = this.skillBank.getStorage();
9069
- let taxonomyNodes = 0;
9070
- let relationships = 0;
9071
- let sources = 0;
9072
- if (storage) {
9073
- if (hasIndexerSupport(storage)) {
9074
- const tree = await storage.getTaxonomyTree();
9075
- taxonomyNodes = this.countTaxonomyNodes(tree);
9076
- if (storage.getRelationships) {
9077
- for (const skill of skills) {
9078
- const rels = await storage.getRelationships(skill.id);
9079
- relationships += rels.length;
9080
- }
9081
- }
9082
- }
9083
- const sourceSet = /* @__PURE__ */ new Set();
9084
- for (const skill of skills) {
9085
- if (skill.externalSource?.repo) {
9086
- sourceSet.add(skill.externalSource.repo);
9087
- }
9088
- if (skill.source?.type === "imported" && skill.externalSource?.url) {
9089
- sourceSet.add(skill.externalSource.url);
9090
- }
9091
- }
9092
- sources = sourceSet.size;
9093
- }
9094
- const indexed = skills.filter(
9095
- (s) => s.status === "active" && (s.taxonomy || s.source?.type === "imported")
9096
- ).length;
9097
- return {
9098
- totalSkills: skills.length,
9099
- indexedSkills: indexed,
9100
- rawSkills: skills.filter((s) => s.status === "draft").length,
9101
- failedSkills: skills.filter((s) => s.status === "deprecated").length,
9102
- taxonomyNodes,
9103
- relationships,
9104
- sources
9105
- };
9465
+ if (!options.force) {
9466
+ printWarning(`This will permanently delete ${id}${options.version ? `@${options.version}` : " and all versions"}`);
9467
+ printWarning("Use --force to confirm");
9468
+ process.exit(1);
9106
9469
  }
9107
- if (this.db) {
9108
- const stats = this.db.getStats?.() || {};
9109
- return {
9110
- totalSkills: stats.totalSkills || 0,
9111
- indexedSkills: stats.indexedSkills || 0,
9112
- rawSkills: stats.rawSkills || 0,
9113
- failedSkills: stats.failedSkills || 0,
9114
- taxonomyNodes: stats.taxonomyNodes || 0,
9115
- relationships: stats.relationships || 0,
9116
- sources: stats.sources || 0
9117
- };
9470
+ const deleted = await skillBank.deleteSkill(id, options.version);
9471
+ if (!deleted) {
9472
+ printError(`Failed to delete: ${id}`);
9473
+ process.exit(1);
9118
9474
  }
9119
- return {
9120
- totalSkills: 0,
9121
- indexedSkills: 0,
9122
- rawSkills: 0,
9123
- failedSkills: 0,
9124
- taxonomyNodes: 0,
9125
- relationships: 0,
9126
- sources: 0
9127
- };
9475
+ if (globalOpts.json) {
9476
+ console.log(JSON.stringify({ deleted: true, id, version: options.version }));
9477
+ return;
9478
+ }
9479
+ printSuccess(`Deleted: ${id}${options.version ? `@${options.version}` : ""}`);
9480
+ } catch (error) {
9481
+ printError(error.message);
9482
+ process.exit(1);
9128
9483
  }
9129
- /**
9130
- * Count nodes in taxonomy tree
9131
- */
9132
- countTaxonomyNodes(nodes) {
9133
- let count = 0;
9134
- for (const node of nodes) {
9135
- count++;
9136
- if (node.children) {
9137
- count += this.countTaxonomyNodes(node.children);
9484
+ });
9485
+
9486
+ // src/cli/commands/export.ts
9487
+ var import_commander12 = require("commander");
9488
+ var fs14 = __toESM(require("fs"));
9489
+ var exportCommand = new import_commander12.Command("export").description("Export all skills to JSON").option("-o, --output <file>", "Output file path (defaults to stdout)").action(async (options, command) => {
9490
+ const globalOpts = command.optsWithGlobals();
9491
+ try {
9492
+ const skillBank = await createSkillBankFromOptions(globalOpts);
9493
+ const skills = await skillBank.exportAll();
9494
+ const json = JSON.stringify(skills, null, 2);
9495
+ if (options.output) {
9496
+ fs14.writeFileSync(options.output, json, "utf-8");
9497
+ if (!globalOpts.quiet) {
9498
+ printSuccess(`Exported ${skills.length} skill(s) to ${options.output}`);
9138
9499
  }
9500
+ } else {
9501
+ console.log(json);
9139
9502
  }
9140
- return count;
9503
+ } catch (error) {
9504
+ printError(error.message);
9505
+ process.exit(1);
9141
9506
  }
9142
- /**
9143
- * Scrape and index skills directly into SkillBank
9144
- * This is the streamlined workflow for integrated mode
9145
- */
9146
- async scrapeAndIndex(sources, options) {
9147
- if (!this.skillBank) {
9148
- throw new Error("SkillBank required for scrapeAndIndex. Use integrated mode.");
9149
- }
9150
- await this.initialize();
9151
- const skillsAdded = [];
9152
- const scraped = await this.scrape(sources, { force: options?.force });
9153
- let indexed = { indexed: 0, skipped: 0, failed: 0, errors: [] };
9154
- if (options?.autoClassify !== false) {
9155
- indexed = await this.classify({ all: options?.force });
9507
+ });
9508
+
9509
+ // src/cli/commands/import.ts
9510
+ var import_commander13 = require("commander");
9511
+ var fs15 = __toESM(require("fs"));
9512
+
9513
+ // src/import/detect.ts
9514
+ function isSkillTreeSkill(obj) {
9515
+ if (typeof obj !== "object" || obj === null) return false;
9516
+ const skill = obj;
9517
+ return typeof skill.id === "string" && typeof skill.name === "string" && typeof skill.version === "string" && typeof skill.instructions === "string" && typeof skill.metrics === "object";
9518
+ }
9519
+ function isIndexerSkill(obj) {
9520
+ if (typeof obj !== "object" || obj === null) return false;
9521
+ const skill = obj;
9522
+ return typeof skill.slug === "string" && typeof skill.sourceRepo === "string" && typeof skill.sourcePath === "string" && typeof skill.sourceUrl === "string" && typeof skill.content === "string" && typeof skill.scrapedAt === "string" && (skill.status === "raw" || skill.status === "indexed" || skill.status === "failed");
9523
+ }
9524
+ function isIndexerExport(obj) {
9525
+ if (typeof obj !== "object" || obj === null) return false;
9526
+ const exp = obj;
9527
+ return typeof exp.exportedAt === "string" && Array.isArray(exp.skills);
9528
+ }
9529
+ function detectFormat(data) {
9530
+ if (Array.isArray(data)) {
9531
+ if (data.length === 0) {
9532
+ return { format: "unknown", confidence: 0, details: "Empty array" };
9156
9533
  }
9157
- if (this.db) {
9158
- const skills = this.db.getAllSkills?.() || [];
9159
- for (const skill of skills) {
9160
- if (skill.status !== "indexed" && !options?.force) continue;
9161
- try {
9162
- const converted = convertIndexerSkill(skill);
9163
- await this.skillBank.saveSkill(converted.skill);
9164
- skillsAdded.push(converted.skill.id);
9165
- } catch (err) {
9166
- indexed.errors.push(`Failed to import ${skill.slug}: ${err.message}`);
9167
- }
9168
- }
9534
+ const first = data[0];
9535
+ if (isIndexerSkill(first)) {
9536
+ return { format: "indexer", confidence: 0.95, details: "Array of indexer skills" };
9169
9537
  }
9170
- let relationships = { detected: 0, skipped: 0, errors: [] };
9171
- if (options?.detectRelationships !== false) {
9172
- relationships = await this.detectRelationships();
9538
+ if (isSkillTreeSkill(first)) {
9539
+ return { format: "skill-tree", confidence: 0.95, details: "Array of skill-tree skills" };
9173
9540
  }
9174
- return {
9175
- scraped,
9176
- indexed,
9177
- relationships,
9178
- skillsAdded
9179
- };
9541
+ return { format: "unknown", confidence: 0.3, details: "Array of unknown objects" };
9180
9542
  }
9181
- /**
9182
- * Import skills from indexer database into SkillBank
9183
- */
9184
- async importFromIndexerDb(options) {
9185
- if (!this.skillBank) {
9186
- throw new Error("SkillBank not configured. Use integrated mode.");
9543
+ if (typeof data === "object" && data !== null) {
9544
+ if (isIndexerExport(data)) {
9545
+ return { format: "indexer-export", confidence: 0.98, details: "Indexer export with metadata" };
9187
9546
  }
9188
- await this.initialize();
9189
- if (!this.db) {
9190
- throw new Error(
9191
- "Scraper database not available. Build the scraper module first."
9192
- );
9547
+ if (isIndexerSkill(data)) {
9548
+ return { format: "indexer", confidence: 0.9, details: "Single indexer skill" };
9193
9549
  }
9194
- const result = { imported: 0, failed: 0, skills: [] };
9195
- try {
9196
- let skills;
9197
- if (options?.status) {
9198
- skills = this.db.getSkillsByStatus?.(options.status) || [];
9199
- } else {
9200
- skills = this.db.getAllSkills?.() || [];
9550
+ if (isSkillTreeSkill(data)) {
9551
+ return { format: "skill-tree", confidence: 0.9, details: "Single skill-tree skill" };
9552
+ }
9553
+ }
9554
+ return { format: "unknown", confidence: 0, details: "Unrecognized format" };
9555
+ }
9556
+ function detectFormatFromString(content) {
9557
+ try {
9558
+ const data = JSON.parse(content);
9559
+ return detectFormat(data);
9560
+ } catch {
9561
+ return { format: "unknown", confidence: 0, details: "Invalid JSON" };
9562
+ }
9563
+ }
9564
+ function isLikelyIndexerFormat(content) {
9565
+ const result = detectFormatFromString(content);
9566
+ return result.format === "indexer" || result.format === "indexer-export";
9567
+ }
9568
+
9569
+ // src/cli/commands/import.ts
9570
+ var importCommand = new import_commander13.Command("import").description("Import skills from JSON file").argument("<file>", "JSON file to import").option("--from-indexer", "Import from skill-indexer export format").option("--auto-detect", "Auto-detect format (default: true)", true).action(async (file, options, command) => {
9571
+ const globalOpts = command.optsWithGlobals();
9572
+ try {
9573
+ if (!fs15.existsSync(file)) {
9574
+ printError(`File not found: ${file}`);
9575
+ process.exit(1);
9576
+ }
9577
+ const content = fs15.readFileSync(file, "utf-8");
9578
+ let skills;
9579
+ const isIndexerFormat = options.fromIndexer || options.autoDetect !== false && isLikelyIndexerFormat(content);
9580
+ if (isIndexerFormat) {
9581
+ if (!globalOpts.quiet) {
9582
+ printInfo("Detected skill-indexer format, converting...");
9201
9583
  }
9202
- if (options?.limit) {
9203
- skills = skills.slice(0, options.limit);
9584
+ try {
9585
+ const { skills: converted, stats, exportMetadata } = parseIndexerExport(content);
9586
+ skills = converted;
9587
+ if (!globalOpts.quiet) {
9588
+ printInfo(`Converted ${stats.total} skills (${stats.withStructuredContent} with structured content)`);
9589
+ if (exportMetadata) {
9590
+ printInfo(`Original export from: ${exportMetadata.exportedAt}`);
9591
+ }
9592
+ }
9593
+ } catch (err) {
9594
+ printError(`Failed to parse indexer export: ${err.message}`);
9595
+ process.exit(1);
9596
+ }
9597
+ } else {
9598
+ try {
9599
+ const parsed = JSON.parse(content);
9600
+ skills = Array.isArray(parsed) ? parsed : [parsed];
9601
+ } catch {
9602
+ printError("Invalid JSON file");
9603
+ process.exit(1);
9204
9604
  }
9205
9605
  for (const skill of skills) {
9206
- try {
9207
- const converted = convertIndexerSkill(skill);
9208
- await this.skillBank.saveSkill(converted.skill);
9209
- result.imported++;
9210
- result.skills.push(converted.skill);
9211
- } catch (err) {
9212
- result.failed++;
9606
+ skill.createdAt = new Date(skill.createdAt);
9607
+ skill.updatedAt = new Date(skill.updatedAt);
9608
+ if (skill.metrics.lastUsed) {
9609
+ skill.metrics.lastUsed = new Date(skill.metrics.lastUsed);
9610
+ }
9611
+ if (skill.source?.importedAt) {
9612
+ skill.source.importedAt = new Date(skill.source.importedAt);
9613
+ }
9614
+ if (skill.externalSource?.scrapedAt) {
9615
+ skill.externalSource.scrapedAt = new Date(skill.externalSource.scrapedAt);
9213
9616
  }
9214
9617
  }
9215
- } catch (err) {
9216
- throw new Error(`Import failed: ${err.message}`);
9217
9618
  }
9218
- return result;
9219
- }
9220
- /**
9221
- * Get default skill sources from config
9222
- */
9223
- getDefaultSources() {
9224
- const configSources = this.globalConfig.indexer.sources;
9225
- if (configSources.length > 0) {
9226
- return configSources.map((url) => ({
9227
- type: "awesome-list",
9228
- url
9229
- }));
9619
+ const skillBank = await createSkillBankFromOptions(globalOpts);
9620
+ const result = await skillBank.importSkills(skills);
9621
+ if (globalOpts.json) {
9622
+ console.log(JSON.stringify({
9623
+ ...result,
9624
+ format: isIndexerFormat ? "indexer" : "skill-tree"
9625
+ }, null, 2));
9626
+ return;
9230
9627
  }
9231
- return [
9232
- { type: "awesome-list", url: "https://github.com/VoltAgent/awesome-agent-skills" }
9233
- ];
9234
- }
9235
- /**
9236
- * Close database connections
9237
- */
9238
- async close() {
9239
- if (this.db && typeof this.db.close === "function") {
9240
- this.db.close();
9628
+ printSuccess(`Imported ${result.imported} skill(s)`);
9629
+ if (result.failed > 0) {
9630
+ printWarning(`Failed to import ${result.failed} skill(s)`);
9241
9631
  }
9242
- this.db = void 0;
9243
- this.initialized = false;
9632
+ } catch (error) {
9633
+ printError(error.message);
9634
+ process.exit(1);
9244
9635
  }
9245
- };
9246
- function createIntegratedIndexer(skillBank, config2 = {}) {
9247
- return new IndexerService(config2, skillBank);
9248
- }
9636
+ });
9637
+
9638
+ // src/cli/commands/indexer/index.ts
9639
+ var import_commander20 = require("commander");
9249
9640
 
9250
9641
  // src/cli/commands/indexer/scrape.ts
9642
+ var import_commander14 = require("commander");
9643
+ var import_child_process2 = require("child_process");
9251
9644
  var scrapeCommand = new import_commander14.Command("scrape").description("Scrape skills from GitHub sources").argument("[url]", "Repository or awesome-list URL").option("-d, --discover", "Discover from default sources").option("-f, --force", "Force scrape even if no changes detected").option("--standalone", "Use standalone skillindexer CLI (fallback)").option("--import", "Auto-import scraped skills into skill-tree", true).action(async (url, options, command) => {
9252
9645
  const globalOpts = command.optsWithGlobals();
9253
9646
  if (options.standalone) {
@@ -10314,11 +10707,11 @@ configCommand.command("edit").description("Open configuration file in editor").a
10314
10707
  configCommand.command("defaults").description("Show default configuration values").action(() => {
10315
10708
  const globalOpts = configCommand.parent?.opts() || {};
10316
10709
  if (globalOpts.json) {
10317
- console.log(JSON.stringify(DEFAULT_CONFIG5, null, 2));
10710
+ console.log(JSON.stringify(DEFAULT_CONFIG6, null, 2));
10318
10711
  } else {
10319
10712
  console.log("Default Configuration:");
10320
10713
  console.log("=".repeat(40));
10321
- printConfig(DEFAULT_CONFIG5, 0);
10714
+ printConfig(DEFAULT_CONFIG6, 0);
10322
10715
  }
10323
10716
  });
10324
10717
  configCommand.command("validate").description("Validate configuration file").action(() => {
@@ -10838,7 +11231,7 @@ var materializeCommand = new import_commander24.Command("materialize").descripti
10838
11231
  });
10839
11232
 
10840
11233
  // src/cli/commands/loadout/index.ts
10841
- var import_commander37 = require("commander");
11234
+ var import_commander38 = require("commander");
10842
11235
 
10843
11236
  // src/cli/commands/loadout/list.ts
10844
11237
  var import_commander25 = require("commander");
@@ -11191,7 +11584,7 @@ var renderSubcommand = new import_commander35.Command("render").description("Ren
11191
11584
  printInfo("Loadout is empty. Nothing to render.");
11192
11585
  return;
11193
11586
  }
11194
- const output = server.renderSystemPrompt();
11587
+ const output = await server.renderSystemPrompt();
11195
11588
  console.log(output);
11196
11589
  } catch (error) {
11197
11590
  printError(error.message);
@@ -11221,11 +11614,34 @@ var clearSubcommand = new import_commander36.Command("clear").description("Clear
11221
11614
  }
11222
11615
  });
11223
11616
 
11617
+ // src/cli/commands/loadout/browse.ts
11618
+ var import_commander37 = require("commander");
11619
+ var browseSubcommand = new import_commander37.Command("browse").description("Browse the skill catalog by category").argument("[path]", "Category path to browse (e.g., Development/Python)").action(async (pathArg, _options, command) => {
11620
+ const globalOpts = command.optsWithGlobals();
11621
+ try {
11622
+ const { server } = await createLoadoutServer(globalOpts);
11623
+ const path18 = pathArg ? pathArg.split("/").filter(Boolean) : void 0;
11624
+ const output = await server.agentBrowseCatalog(path18);
11625
+ if (globalOpts.json) {
11626
+ console.log(JSON.stringify({ path: path18 ?? [], output }, null, 2));
11627
+ return;
11628
+ }
11629
+ if (!output) {
11630
+ printInfo("No catalog data available.");
11631
+ return;
11632
+ }
11633
+ console.log(output);
11634
+ } catch (error) {
11635
+ printError(error.message);
11636
+ process.exit(1);
11637
+ }
11638
+ });
11639
+
11224
11640
  // src/cli/commands/loadout/index.ts
11225
- var loadoutCommand = new import_commander37.Command("loadout").description("Manage skill loadouts for agent sessions").addCommand(listSubcommand).addCommand(searchSubcommand).addCommand(addSubcommand).addCommand(removeSubcommand).addCommand(profileSubcommand).addCommand(setSubcommand).addCommand(expandSubcommand).addCommand(collapseSubcommand).addCommand(useSubcommand).addCommand(getSubcommand).addCommand(renderSubcommand).addCommand(clearSubcommand);
11641
+ var loadoutCommand = new import_commander38.Command("loadout").description("Manage skill loadouts for agent sessions").addCommand(listSubcommand).addCommand(searchSubcommand).addCommand(addSubcommand).addCommand(removeSubcommand).addCommand(profileSubcommand).addCommand(setSubcommand).addCommand(expandSubcommand).addCommand(collapseSubcommand).addCommand(useSubcommand).addCommand(getSubcommand).addCommand(renderSubcommand).addCommand(clearSubcommand).addCommand(browseSubcommand);
11226
11642
 
11227
11643
  // src/cli/index.ts
11228
- var program = new import_commander38.Command();
11644
+ var program = new import_commander39.Command();
11229
11645
  var config = loadConfig();
11230
11646
  program.name("skill-tree").description("Management CLI for agent skills").version(VERSION).option("-p, --path <dir>", "Skills directory path", config.storage.path).option("-c, --config <file>", "Config file path", getConfigPath()).option("--json", "Output as JSON", config.cli.output_format === "json").option("-q, --quiet", "Suppress non-essential output", config.cli.quiet).option("--no-color", "Disable colored output", !config.cli.color);
11231
11647
  program.addCommand(listCommand);
@@ -11248,3 +11664,4 @@ program.addCommand(readCommand);
11248
11664
  program.addCommand(materializeCommand);
11249
11665
  program.addCommand(loadoutCommand);
11250
11666
  program.parse();
11667
+ //# sourceMappingURL=index.js.map