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.
- package/dist/cli/index.js +1827 -1410
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/index.mjs +9538 -1657
- package/dist/cli/index.mjs.map +1 -0
- package/dist/index.d.mts +318 -7
- package/dist/index.d.ts +318 -7
- package/dist/index.js +1539 -92
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +9614 -70
- package/dist/index.mjs.map +1 -0
- package/package.json +3 -2
- package/dist/chunk-3SRB47JW.mjs +0 -8344
- package/dist/chunk-43YOKLZP.mjs +0 -6081
- package/dist/chunk-4AGZU52D.mjs +0 -7918
- package/dist/chunk-4OC5QFIF.mjs +0 -11267
- package/dist/chunk-55SMGVTP.mjs +0 -7126
- package/dist/chunk-6FX4IK4Z.mjs +0 -5368
- package/dist/chunk-7EGDKOHV.mjs +0 -9439
- package/dist/chunk-7LMOQW5H.mjs +0 -4893
- package/dist/chunk-7QIQJVNP.mjs +0 -14206
- package/dist/chunk-7VB4ZRZO.mjs +0 -7127
- package/dist/chunk-BPVRW25O.mjs +0 -6089
- package/dist/chunk-CI4476KM.mjs +0 -6607
- package/dist/chunk-DDXYQ74I.mjs +0 -13969
- package/dist/chunk-DQOFJXBX.mjs +0 -6595
- package/dist/chunk-E2CVK23F.mjs +0 -8751
- package/dist/chunk-F3YEUQAP.mjs +0 -654
- package/dist/chunk-FKJJ4RJG.mjs +0 -13874
- package/dist/chunk-II7DECZQ.mjs +0 -9111
- package/dist/chunk-INKVOZXK.mjs +0 -15898
- package/dist/chunk-K6NRCSAZ.mjs +0 -4355
- package/dist/chunk-OYHYXKXO.mjs +0 -7297
- package/dist/chunk-PDPN7FW7.mjs +0 -1045
- package/dist/chunk-TEUB6DZR.mjs +0 -6453
- package/dist/chunk-TWPEHDW4.mjs +0 -1067
- package/dist/chunk-Y54UK2J3.mjs +0 -13071
- package/dist/chunk-ZQVS7MQK.mjs +0 -6081
- package/dist/sqlite-OLU72GHB.mjs +0 -6
- package/dist/sqlite-XJRPMNAJ.mjs +0 -6
- package/dist/sync-BSWMMDA6.mjs +0 -14
package/dist/index.js
CHANGED
|
@@ -853,15 +853,15 @@ var init_sqlite = __esm({
|
|
|
853
853
|
/**
|
|
854
854
|
* Get or create a taxonomy node
|
|
855
855
|
*/
|
|
856
|
-
async ensureTaxonomyNode(
|
|
856
|
+
async ensureTaxonomyNode(path13) {
|
|
857
857
|
this.ensureInitialized();
|
|
858
858
|
const db = this.getDb();
|
|
859
|
-
const pathStr =
|
|
859
|
+
const pathStr = path13.join("/");
|
|
860
860
|
const existing = db.prepare("SELECT id FROM taxonomy_nodes WHERE path = ?").get(pathStr);
|
|
861
861
|
if (existing) return existing.id;
|
|
862
862
|
const id = `node-${pathStr.replace(/\//g, "-").toLowerCase()}`;
|
|
863
|
-
const name =
|
|
864
|
-
const parentPath =
|
|
863
|
+
const name = path13[path13.length - 1] || "Root";
|
|
864
|
+
const parentPath = path13.slice(0, -1);
|
|
865
865
|
let parentId = null;
|
|
866
866
|
if (parentPath.length > 0) {
|
|
867
867
|
parentId = await this.ensureTaxonomyNode(parentPath);
|
|
@@ -1789,11 +1789,13 @@ __export(index_exports, {
|
|
|
1789
1789
|
AgentsParser: () => AgentsParser,
|
|
1790
1790
|
AgentsSync: () => AgentsSync,
|
|
1791
1791
|
CachedStorageAdapter: () => CachedStorageAdapter,
|
|
1792
|
+
CatalogRenderer: () => CatalogRenderer,
|
|
1792
1793
|
ConflictStore: () => ConflictStore,
|
|
1793
1794
|
DEFAULT_AGENTS_CONFIG: () => DEFAULT_AGENTS_CONFIG,
|
|
1794
1795
|
FederationManager: () => FederationManager,
|
|
1795
1796
|
GitSyncAdapter: () => GitSyncAdapter,
|
|
1796
1797
|
HookRegistry: () => HookRegistry,
|
|
1798
|
+
IndexerService: () => IndexerService,
|
|
1797
1799
|
LineageTracker: () => LineageTracker,
|
|
1798
1800
|
LoadoutCompiler: () => LoadoutCompiler,
|
|
1799
1801
|
Materializer: () => Materializer,
|
|
@@ -2934,8 +2936,214 @@ function createSyncManager(options) {
|
|
|
2934
2936
|
return new SyncManager(options);
|
|
2935
2937
|
}
|
|
2936
2938
|
|
|
2937
|
-
// src/serving/
|
|
2939
|
+
// src/serving/xml-utils.ts
|
|
2940
|
+
function escapeXml(text) {
|
|
2941
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2942
|
+
}
|
|
2943
|
+
function getSkillSummary(skill, maxLength = 150) {
|
|
2944
|
+
if (skill.serving?.summary) {
|
|
2945
|
+
return skill.serving.summary;
|
|
2946
|
+
}
|
|
2947
|
+
const firstSentence = skill.description.split(/[.!?]/)[0];
|
|
2948
|
+
if (firstSentence.length <= maxLength) {
|
|
2949
|
+
return firstSentence;
|
|
2950
|
+
}
|
|
2951
|
+
return skill.description.substring(0, maxLength - 3) + "...";
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
// src/serving/catalog-renderer.ts
|
|
2955
|
+
function hasCatalogSupport(storage) {
|
|
2956
|
+
const s = storage;
|
|
2957
|
+
return typeof s.getTaxonomyTree === "function" && typeof s.getSkillsInTaxonomyNode === "function";
|
|
2958
|
+
}
|
|
2959
|
+
var UNTAGGED_CATEGORY = "other";
|
|
2960
|
+
var CATALOG_USAGE_LINES = [
|
|
2961
|
+
" Use skill_browse to explore categories.",
|
|
2962
|
+
" Use skill_search to find by keyword.",
|
|
2963
|
+
" Use skill_expand to load a skill into your context."
|
|
2964
|
+
];
|
|
2938
2965
|
var DEFAULT_CONFIG = {
|
|
2966
|
+
maxCategoriesPerLevel: 12,
|
|
2967
|
+
maxSkillsAtLeaf: 8,
|
|
2968
|
+
maxSummaryLength: 80,
|
|
2969
|
+
format: "xml"
|
|
2970
|
+
};
|
|
2971
|
+
var CatalogRenderer = class _CatalogRenderer {
|
|
2972
|
+
constructor(storage, config) {
|
|
2973
|
+
this.storage = storage;
|
|
2974
|
+
this.overviewCache = null;
|
|
2975
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
2976
|
+
}
|
|
2977
|
+
static {
|
|
2978
|
+
this.CACHE_TTL_MS = 6e4;
|
|
2979
|
+
}
|
|
2980
|
+
/**
|
|
2981
|
+
* Render level-0 catalog overview for system prompt injection.
|
|
2982
|
+
* Shows top-level categories with counts. ~200 tokens.
|
|
2983
|
+
* Returns empty string if no skills exist.
|
|
2984
|
+
* Results are cached for 60 seconds to avoid repeated storage queries.
|
|
2985
|
+
*/
|
|
2986
|
+
async renderOverview() {
|
|
2987
|
+
if (this.overviewCache && Date.now() - this.overviewCache.timestamp < _CatalogRenderer.CACHE_TTL_MS) {
|
|
2988
|
+
return this.overviewCache.value;
|
|
2989
|
+
}
|
|
2990
|
+
let result;
|
|
2991
|
+
if (hasCatalogSupport(this.storage)) {
|
|
2992
|
+
const tree = await this.storage.getTaxonomyTree();
|
|
2993
|
+
if (tree.length > 0) {
|
|
2994
|
+
result = this.renderOverviewFromNodes(tree);
|
|
2995
|
+
this.overviewCache = { value: result, timestamp: Date.now() };
|
|
2996
|
+
return result;
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
result = await this.renderOverviewFromTags();
|
|
3000
|
+
this.overviewCache = { value: result, timestamp: Date.now() };
|
|
3001
|
+
return result;
|
|
3002
|
+
}
|
|
3003
|
+
/**
|
|
3004
|
+
* Render a specific category path for browse drill-down.
|
|
3005
|
+
* Shows subcategories at intermediate nodes, or skill summaries at leaf nodes.
|
|
3006
|
+
*/
|
|
3007
|
+
async renderCategory(path13) {
|
|
3008
|
+
if (hasCatalogSupport(this.storage)) {
|
|
3009
|
+
return this.renderCategoryFromTaxonomy(this.storage, path13);
|
|
3010
|
+
}
|
|
3011
|
+
return this.renderCategoryFromTags(path13);
|
|
3012
|
+
}
|
|
3013
|
+
/**
|
|
3014
|
+
* Invalidate the overview cache (e.g., after skill changes).
|
|
3015
|
+
*/
|
|
3016
|
+
invalidateCache() {
|
|
3017
|
+
this.overviewCache = null;
|
|
3018
|
+
}
|
|
3019
|
+
// ===========================================================================
|
|
3020
|
+
// OVERVIEW RENDERING (shared structure)
|
|
3021
|
+
// ===========================================================================
|
|
3022
|
+
/**
|
|
3023
|
+
* Render the overview XML shell with a list of categories.
|
|
3024
|
+
* Used by both taxonomy-based and tag-based paths.
|
|
3025
|
+
*/
|
|
3026
|
+
renderOverviewXml(totalSkills, categories) {
|
|
3027
|
+
const lines = [];
|
|
3028
|
+
lines.push(`<skill_catalog total="${totalSkills}">`);
|
|
3029
|
+
lines.push(" <usage>");
|
|
3030
|
+
lines.push(...CATALOG_USAGE_LINES);
|
|
3031
|
+
lines.push(" </usage>");
|
|
3032
|
+
lines.push(" <categories>");
|
|
3033
|
+
for (const cat of categories) {
|
|
3034
|
+
lines.push(` <category path="${escapeXml(cat.name)}" count="${cat.count}" />`);
|
|
3035
|
+
}
|
|
3036
|
+
lines.push(" </categories>");
|
|
3037
|
+
lines.push("</skill_catalog>");
|
|
3038
|
+
return lines.join("\n");
|
|
3039
|
+
}
|
|
3040
|
+
// ===========================================================================
|
|
3041
|
+
// TAXONOMY-BASED RENDERING
|
|
3042
|
+
// ===========================================================================
|
|
3043
|
+
renderOverviewFromNodes(roots) {
|
|
3044
|
+
const counted = this.withCounts(roots);
|
|
3045
|
+
const totalSkills = counted.reduce((sum, c) => sum + c.count, 0);
|
|
3046
|
+
const categories = counted.sort((a, b) => b.count - a.count).slice(0, this.config.maxCategoriesPerLevel).map((c) => ({ name: c.node.name, count: c.count }));
|
|
3047
|
+
return this.renderOverviewXml(totalSkills, categories);
|
|
3048
|
+
}
|
|
3049
|
+
async renderCategoryFromTaxonomy(storage, path13) {
|
|
3050
|
+
const tree = await storage.getTaxonomyTree(path13);
|
|
3051
|
+
const pathStr = path13.join("/");
|
|
3052
|
+
if (tree.length > 0 && tree.some((n) => n.children.length > 0)) {
|
|
3053
|
+
const root = tree[0];
|
|
3054
|
+
const rootCount = this.countNodeSkills(root);
|
|
3055
|
+
const counted = this.withCounts(root.children);
|
|
3056
|
+
const children = counted.sort((a, b) => b.count - a.count).slice(0, this.config.maxCategoriesPerLevel);
|
|
3057
|
+
const lines = [];
|
|
3058
|
+
lines.push(`<catalog_browse path="${escapeXml(pathStr)}" count="${rootCount}">`);
|
|
3059
|
+
lines.push(" <subcategories>");
|
|
3060
|
+
for (const { node: child, count } of children) {
|
|
3061
|
+
const childPath = [...path13, child.name].join("/");
|
|
3062
|
+
lines.push(` <category path="${escapeXml(childPath)}" count="${count}" />`);
|
|
3063
|
+
}
|
|
3064
|
+
lines.push(" </subcategories>");
|
|
3065
|
+
lines.push("</catalog_browse>");
|
|
3066
|
+
return lines.join("\n");
|
|
3067
|
+
}
|
|
3068
|
+
if (tree.length > 0) {
|
|
3069
|
+
const nodeId = tree[0].id;
|
|
3070
|
+
const skills = await storage.getSkillsInTaxonomyNode(nodeId);
|
|
3071
|
+
return this.renderLeafSkills(skills, pathStr, tree[0].skillCount);
|
|
3072
|
+
}
|
|
3073
|
+
return `<catalog_browse path="${escapeXml(pathStr)}" count="0" />`;
|
|
3074
|
+
}
|
|
3075
|
+
// ===========================================================================
|
|
3076
|
+
// TAG-BASED FALLBACK
|
|
3077
|
+
// ===========================================================================
|
|
3078
|
+
async renderOverviewFromTags() {
|
|
3079
|
+
const skills = await this.storage.listSkills({ status: ["active"] });
|
|
3080
|
+
if (skills.length === 0) return "";
|
|
3081
|
+
const tagCounts = this.groupByPrimaryTag(skills);
|
|
3082
|
+
const categories = Array.from(tagCounts.entries()).sort((a, b) => b[1] - a[1]).slice(0, this.config.maxCategoriesPerLevel).map(([name, count]) => ({ name, count }));
|
|
3083
|
+
return this.renderOverviewXml(skills.length, categories);
|
|
3084
|
+
}
|
|
3085
|
+
async renderCategoryFromTags(path13) {
|
|
3086
|
+
if (path13.length === 0) {
|
|
3087
|
+
return this.renderOverviewFromTags();
|
|
3088
|
+
}
|
|
3089
|
+
const tag = path13[0];
|
|
3090
|
+
const matching = await this.storage.listSkills({ status: ["active"], tags: [tag] });
|
|
3091
|
+
const pathStr = path13.join("/");
|
|
3092
|
+
return this.renderLeafSkills(matching, pathStr, matching.length);
|
|
3093
|
+
}
|
|
3094
|
+
// ===========================================================================
|
|
3095
|
+
// SHARED RENDERING HELPERS
|
|
3096
|
+
// ===========================================================================
|
|
3097
|
+
renderLeafSkills(skills, pathStr, totalCount) {
|
|
3098
|
+
const displayed = skills.slice(0, this.config.maxSkillsAtLeaf);
|
|
3099
|
+
const remaining = totalCount - displayed.length;
|
|
3100
|
+
const lines = [];
|
|
3101
|
+
lines.push(`<catalog_browse path="${escapeXml(pathStr)}" count="${totalCount}">`);
|
|
3102
|
+
lines.push(" <skills>");
|
|
3103
|
+
for (const skill of displayed) {
|
|
3104
|
+
const tags = skill.tags.length > 0 ? ` tags="${escapeXml(skill.tags.join(", "))}"` : "";
|
|
3105
|
+
const desc = getSkillSummary(skill, this.config.maxSummaryLength);
|
|
3106
|
+
lines.push(` <skill id="${escapeXml(skill.id)}"${tags}>`);
|
|
3107
|
+
lines.push(` ${escapeXml(desc)}`);
|
|
3108
|
+
lines.push(" </skill>");
|
|
3109
|
+
}
|
|
3110
|
+
lines.push(" </skills>");
|
|
3111
|
+
if (remaining > 0) {
|
|
3112
|
+
lines.push(` <more remaining="${remaining}">`);
|
|
3113
|
+
lines.push(" Use skill_search for more, or skill_expand {id} to load.");
|
|
3114
|
+
lines.push(" </more>");
|
|
3115
|
+
}
|
|
3116
|
+
lines.push("</catalog_browse>");
|
|
3117
|
+
return lines.join("\n");
|
|
3118
|
+
}
|
|
3119
|
+
// ===========================================================================
|
|
3120
|
+
// UTILITIES
|
|
3121
|
+
// ===========================================================================
|
|
3122
|
+
groupByPrimaryTag(skills) {
|
|
3123
|
+
const tagCounts = /* @__PURE__ */ new Map();
|
|
3124
|
+
for (const skill of skills) {
|
|
3125
|
+
const tag = skill.tags[0] || UNTAGGED_CATEGORY;
|
|
3126
|
+
tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
|
|
3127
|
+
}
|
|
3128
|
+
return tagCounts;
|
|
3129
|
+
}
|
|
3130
|
+
/**
|
|
3131
|
+
* Pre-compute counts for a list of nodes, avoiding redundant recursive walks.
|
|
3132
|
+
*/
|
|
3133
|
+
withCounts(nodes) {
|
|
3134
|
+
return nodes.map((node) => ({ node, count: this.countNodeSkills(node) }));
|
|
3135
|
+
}
|
|
3136
|
+
countNodeSkills(node) {
|
|
3137
|
+
let count = node.skillCount;
|
|
3138
|
+
for (const child of node.children) {
|
|
3139
|
+
count += this.countNodeSkills(child);
|
|
3140
|
+
}
|
|
3141
|
+
return count;
|
|
3142
|
+
}
|
|
3143
|
+
};
|
|
3144
|
+
|
|
3145
|
+
// src/serving/loadout-compiler.ts
|
|
3146
|
+
var DEFAULT_CONFIG2 = {
|
|
2939
3147
|
defaultMaxSkills: 15,
|
|
2940
3148
|
defaultStatus: ["active"],
|
|
2941
3149
|
semanticThreshold: 0.6
|
|
@@ -2944,7 +3152,7 @@ var LoadoutCompiler = class {
|
|
|
2944
3152
|
constructor(storage, config) {
|
|
2945
3153
|
this.storage = storage;
|
|
2946
3154
|
this.config = {
|
|
2947
|
-
...
|
|
3155
|
+
...DEFAULT_CONFIG2,
|
|
2948
3156
|
...config
|
|
2949
3157
|
};
|
|
2950
3158
|
}
|
|
@@ -3165,11 +3373,76 @@ var LoadoutCompiler = class {
|
|
|
3165
3373
|
// src/serving/project-detector.ts
|
|
3166
3374
|
var import_fs = require("fs");
|
|
3167
3375
|
var import_path = require("path");
|
|
3168
|
-
var
|
|
3376
|
+
var ProjectDetector = class _ProjectDetector {
|
|
3169
3377
|
constructor() {
|
|
3170
3378
|
/** Cache for project context */
|
|
3171
3379
|
this.cache = /* @__PURE__ */ new Map();
|
|
3172
3380
|
}
|
|
3381
|
+
static {
|
|
3382
|
+
/** Project type patterns */
|
|
3383
|
+
this.PROJECT_TYPES = [
|
|
3384
|
+
{ manifestFile: "package.json", type: "nodejs", tags: ["nodejs", "javascript"], packageManager: "npm" },
|
|
3385
|
+
{ manifestFile: "pyproject.toml", type: "python", tags: ["python"], packageManager: "pip" },
|
|
3386
|
+
{ manifestFile: "requirements.txt", type: "python", tags: ["python"], packageManager: "pip" },
|
|
3387
|
+
{ manifestFile: "Cargo.toml", type: "rust", tags: ["rust"], packageManager: "cargo" },
|
|
3388
|
+
{ manifestFile: "go.mod", type: "go", tags: ["go", "golang"] },
|
|
3389
|
+
{ manifestFile: "pom.xml", type: "java", tags: ["java", "maven"], packageManager: "maven" },
|
|
3390
|
+
{ manifestFile: "build.gradle", type: "java", tags: ["java", "gradle"], packageManager: "gradle" },
|
|
3391
|
+
{ manifestFile: "build.gradle.kts", type: "kotlin", tags: ["kotlin", "gradle"], packageManager: "gradle" }
|
|
3392
|
+
];
|
|
3393
|
+
}
|
|
3394
|
+
static {
|
|
3395
|
+
/** TypeScript detection */
|
|
3396
|
+
this.TYPESCRIPT_FILES = ["tsconfig.json", "tsconfig.base.json"];
|
|
3397
|
+
}
|
|
3398
|
+
static {
|
|
3399
|
+
/** Node.js framework patterns */
|
|
3400
|
+
this.NODE_FRAMEWORKS = [
|
|
3401
|
+
{ name: "react", packageName: "react", tags: ["react", "frontend"] },
|
|
3402
|
+
{ name: "next", packageName: "next", tags: ["nextjs", "react", "fullstack"] },
|
|
3403
|
+
{ name: "vue", packageName: "vue", tags: ["vue", "frontend"] },
|
|
3404
|
+
{ name: "nuxt", packageName: "nuxt", tags: ["nuxt", "vue", "fullstack"] },
|
|
3405
|
+
{ name: "angular", packageName: "@angular/core", tags: ["angular", "frontend"] },
|
|
3406
|
+
{ name: "svelte", packageName: "svelte", tags: ["svelte", "frontend"] },
|
|
3407
|
+
{ name: "express", packageName: "express", tags: ["express", "backend", "api"] },
|
|
3408
|
+
{ name: "fastify", packageName: "fastify", tags: ["fastify", "backend", "api"] },
|
|
3409
|
+
{ name: "nestjs", packageName: "@nestjs/core", tags: ["nestjs", "backend", "api"] },
|
|
3410
|
+
{ name: "hono", packageName: "hono", tags: ["hono", "backend", "api"] },
|
|
3411
|
+
{ name: "prisma", packageName: "@prisma/client", tags: ["prisma", "database", "orm"] },
|
|
3412
|
+
{ name: "drizzle", packageName: "drizzle-orm", tags: ["drizzle", "database", "orm"] },
|
|
3413
|
+
{ name: "typeorm", packageName: "typeorm", tags: ["typeorm", "database", "orm"] },
|
|
3414
|
+
{ name: "jest", packageName: "jest", tags: ["testing", "jest"] },
|
|
3415
|
+
{ name: "vitest", packageName: "vitest", tags: ["testing", "vitest"] },
|
|
3416
|
+
{ name: "playwright", packageName: "@playwright/test", tags: ["testing", "e2e", "playwright"] },
|
|
3417
|
+
{ name: "cypress", packageName: "cypress", tags: ["testing", "e2e", "cypress"] }
|
|
3418
|
+
];
|
|
3419
|
+
}
|
|
3420
|
+
static {
|
|
3421
|
+
/** Python framework patterns (from pyproject.toml or requirements.txt) */
|
|
3422
|
+
this.PYTHON_FRAMEWORKS = [
|
|
3423
|
+
{ name: "fastapi", packageName: "fastapi", tags: ["fastapi", "backend", "api"] },
|
|
3424
|
+
{ name: "django", packageName: "django", tags: ["django", "backend", "fullstack"] },
|
|
3425
|
+
{ name: "flask", packageName: "flask", tags: ["flask", "backend", "api"] },
|
|
3426
|
+
{ name: "sqlalchemy", packageName: "sqlalchemy", tags: ["sqlalchemy", "database", "orm"] },
|
|
3427
|
+
{ name: "pytest", packageName: "pytest", tags: ["testing", "pytest"] },
|
|
3428
|
+
{ name: "pydantic", packageName: "pydantic", tags: ["pydantic", "validation"] }
|
|
3429
|
+
];
|
|
3430
|
+
}
|
|
3431
|
+
static {
|
|
3432
|
+
/** Directory patterns */
|
|
3433
|
+
this.DIRECTORY_PATTERNS = [
|
|
3434
|
+
{ pattern: ".github/workflows", feature: "github-actions", tags: ["ci", "github-actions"] },
|
|
3435
|
+
{ pattern: ".gitlab-ci.yml", feature: "gitlab-ci", tags: ["ci", "gitlab"] },
|
|
3436
|
+
{ pattern: "Dockerfile", feature: "docker", tags: ["docker", "containers"] },
|
|
3437
|
+
{ pattern: "docker-compose.yml", feature: "docker-compose", tags: ["docker", "containers"] },
|
|
3438
|
+
{ pattern: "docker-compose.yaml", feature: "docker-compose", tags: ["docker", "containers"] },
|
|
3439
|
+
{ pattern: "terraform", feature: "terraform", tags: ["terraform", "infrastructure"] },
|
|
3440
|
+
{ pattern: "kubernetes", feature: "kubernetes", tags: ["kubernetes", "infrastructure"] },
|
|
3441
|
+
{ pattern: "k8s", feature: "kubernetes", tags: ["kubernetes", "infrastructure"] },
|
|
3442
|
+
{ pattern: ".env.example", feature: "env-config", tags: ["configuration"] },
|
|
3443
|
+
{ pattern: "prisma/schema.prisma", feature: "prisma", tags: ["prisma", "database"] }
|
|
3444
|
+
];
|
|
3445
|
+
}
|
|
3173
3446
|
/**
|
|
3174
3447
|
* Detect project context from a directory
|
|
3175
3448
|
*/
|
|
@@ -3331,71 +3604,15 @@ var _ProjectDetector = class _ProjectDetector {
|
|
|
3331
3604
|
}
|
|
3332
3605
|
}
|
|
3333
3606
|
};
|
|
3334
|
-
/** Project type patterns */
|
|
3335
|
-
_ProjectDetector.PROJECT_TYPES = [
|
|
3336
|
-
{ manifestFile: "package.json", type: "nodejs", tags: ["nodejs", "javascript"], packageManager: "npm" },
|
|
3337
|
-
{ manifestFile: "pyproject.toml", type: "python", tags: ["python"], packageManager: "pip" },
|
|
3338
|
-
{ manifestFile: "requirements.txt", type: "python", tags: ["python"], packageManager: "pip" },
|
|
3339
|
-
{ manifestFile: "Cargo.toml", type: "rust", tags: ["rust"], packageManager: "cargo" },
|
|
3340
|
-
{ manifestFile: "go.mod", type: "go", tags: ["go", "golang"] },
|
|
3341
|
-
{ manifestFile: "pom.xml", type: "java", tags: ["java", "maven"], packageManager: "maven" },
|
|
3342
|
-
{ manifestFile: "build.gradle", type: "java", tags: ["java", "gradle"], packageManager: "gradle" },
|
|
3343
|
-
{ manifestFile: "build.gradle.kts", type: "kotlin", tags: ["kotlin", "gradle"], packageManager: "gradle" }
|
|
3344
|
-
];
|
|
3345
|
-
/** TypeScript detection */
|
|
3346
|
-
_ProjectDetector.TYPESCRIPT_FILES = ["tsconfig.json", "tsconfig.base.json"];
|
|
3347
|
-
/** Node.js framework patterns */
|
|
3348
|
-
_ProjectDetector.NODE_FRAMEWORKS = [
|
|
3349
|
-
{ name: "react", packageName: "react", tags: ["react", "frontend"] },
|
|
3350
|
-
{ name: "next", packageName: "next", tags: ["nextjs", "react", "fullstack"] },
|
|
3351
|
-
{ name: "vue", packageName: "vue", tags: ["vue", "frontend"] },
|
|
3352
|
-
{ name: "nuxt", packageName: "nuxt", tags: ["nuxt", "vue", "fullstack"] },
|
|
3353
|
-
{ name: "angular", packageName: "@angular/core", tags: ["angular", "frontend"] },
|
|
3354
|
-
{ name: "svelte", packageName: "svelte", tags: ["svelte", "frontend"] },
|
|
3355
|
-
{ name: "express", packageName: "express", tags: ["express", "backend", "api"] },
|
|
3356
|
-
{ name: "fastify", packageName: "fastify", tags: ["fastify", "backend", "api"] },
|
|
3357
|
-
{ name: "nestjs", packageName: "@nestjs/core", tags: ["nestjs", "backend", "api"] },
|
|
3358
|
-
{ name: "hono", packageName: "hono", tags: ["hono", "backend", "api"] },
|
|
3359
|
-
{ name: "prisma", packageName: "@prisma/client", tags: ["prisma", "database", "orm"] },
|
|
3360
|
-
{ name: "drizzle", packageName: "drizzle-orm", tags: ["drizzle", "database", "orm"] },
|
|
3361
|
-
{ name: "typeorm", packageName: "typeorm", tags: ["typeorm", "database", "orm"] },
|
|
3362
|
-
{ name: "jest", packageName: "jest", tags: ["testing", "jest"] },
|
|
3363
|
-
{ name: "vitest", packageName: "vitest", tags: ["testing", "vitest"] },
|
|
3364
|
-
{ name: "playwright", packageName: "@playwright/test", tags: ["testing", "e2e", "playwright"] },
|
|
3365
|
-
{ name: "cypress", packageName: "cypress", tags: ["testing", "e2e", "cypress"] }
|
|
3366
|
-
];
|
|
3367
|
-
/** Python framework patterns (from pyproject.toml or requirements.txt) */
|
|
3368
|
-
_ProjectDetector.PYTHON_FRAMEWORKS = [
|
|
3369
|
-
{ name: "fastapi", packageName: "fastapi", tags: ["fastapi", "backend", "api"] },
|
|
3370
|
-
{ name: "django", packageName: "django", tags: ["django", "backend", "fullstack"] },
|
|
3371
|
-
{ name: "flask", packageName: "flask", tags: ["flask", "backend", "api"] },
|
|
3372
|
-
{ name: "sqlalchemy", packageName: "sqlalchemy", tags: ["sqlalchemy", "database", "orm"] },
|
|
3373
|
-
{ name: "pytest", packageName: "pytest", tags: ["testing", "pytest"] },
|
|
3374
|
-
{ name: "pydantic", packageName: "pydantic", tags: ["pydantic", "validation"] }
|
|
3375
|
-
];
|
|
3376
|
-
/** Directory patterns */
|
|
3377
|
-
_ProjectDetector.DIRECTORY_PATTERNS = [
|
|
3378
|
-
{ pattern: ".github/workflows", feature: "github-actions", tags: ["ci", "github-actions"] },
|
|
3379
|
-
{ pattern: ".gitlab-ci.yml", feature: "gitlab-ci", tags: ["ci", "gitlab"] },
|
|
3380
|
-
{ pattern: "Dockerfile", feature: "docker", tags: ["docker", "containers"] },
|
|
3381
|
-
{ pattern: "docker-compose.yml", feature: "docker-compose", tags: ["docker", "containers"] },
|
|
3382
|
-
{ pattern: "docker-compose.yaml", feature: "docker-compose", tags: ["docker", "containers"] },
|
|
3383
|
-
{ pattern: "terraform", feature: "terraform", tags: ["terraform", "infrastructure"] },
|
|
3384
|
-
{ pattern: "kubernetes", feature: "kubernetes", tags: ["kubernetes", "infrastructure"] },
|
|
3385
|
-
{ pattern: "k8s", feature: "kubernetes", tags: ["kubernetes", "infrastructure"] },
|
|
3386
|
-
{ pattern: ".env.example", feature: "env-config", tags: ["configuration"] },
|
|
3387
|
-
{ pattern: "prisma/schema.prisma", feature: "prisma", tags: ["prisma", "database"] }
|
|
3388
|
-
];
|
|
3389
|
-
var ProjectDetector = _ProjectDetector;
|
|
3390
3607
|
|
|
3391
3608
|
// src/serving/view-renderer.ts
|
|
3392
|
-
var
|
|
3609
|
+
var DEFAULT_CONFIG3 = {
|
|
3393
3610
|
includeTokenEstimates: false,
|
|
3394
3611
|
maxSummaryLength: 150
|
|
3395
3612
|
};
|
|
3396
3613
|
var ViewRenderer = class {
|
|
3397
3614
|
constructor(config) {
|
|
3398
|
-
this.config = { ...
|
|
3615
|
+
this.config = { ...DEFAULT_CONFIG3, ...config };
|
|
3399
3616
|
}
|
|
3400
3617
|
/**
|
|
3401
3618
|
* Render loadout state as OpenSkills-compatible XML
|
|
@@ -3422,6 +3639,18 @@ var ViewRenderer = class {
|
|
|
3422
3639
|
lines.push(" <content>");
|
|
3423
3640
|
lines.push(skill.instructions.split("\n").map((line) => " " + line).join("\n"));
|
|
3424
3641
|
lines.push(" </content>");
|
|
3642
|
+
if (skill.relationships && skill.relationships.length > 0) {
|
|
3643
|
+
const hints = skill.relationships.filter((r) => r.confidence >= 0.5).sort((a, b) => b.confidence - a.confidence).slice(0, 5);
|
|
3644
|
+
if (hints.length > 0) {
|
|
3645
|
+
lines.push(" <related>");
|
|
3646
|
+
for (const rel of hints) {
|
|
3647
|
+
const relatedSkill = state.available.get(rel.targetSkillId);
|
|
3648
|
+
const desc = relatedSkill ? this.getSummary(relatedSkill) : rel.targetSkillId;
|
|
3649
|
+
lines.push(` <see_also id="${this.escapeXml(rel.targetSkillId)}" type="${rel.type}">${this.escapeXml(desc)}</see_also>`);
|
|
3650
|
+
}
|
|
3651
|
+
lines.push(" </related>");
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3425
3654
|
lines.push("</skill>");
|
|
3426
3655
|
} else {
|
|
3427
3656
|
const summary = this.getSummary(skill);
|
|
@@ -3534,23 +3763,18 @@ var ViewRenderer = class {
|
|
|
3534
3763
|
// Private helpers
|
|
3535
3764
|
// ===========================================================================
|
|
3536
3765
|
/**
|
|
3537
|
-
* Get summary for a skill (short description)
|
|
3766
|
+
* Get summary for a skill (short description).
|
|
3767
|
+
* Delegates to shared getSkillSummary utility.
|
|
3538
3768
|
*/
|
|
3539
3769
|
getSummary(skill) {
|
|
3540
|
-
|
|
3541
|
-
return skill.serving.summary;
|
|
3542
|
-
}
|
|
3543
|
-
const firstSentence = skill.description.split(/[.!?]/)[0];
|
|
3544
|
-
if (firstSentence.length <= this.config.maxSummaryLength) {
|
|
3545
|
-
return firstSentence;
|
|
3546
|
-
}
|
|
3547
|
-
return skill.description.substring(0, this.config.maxSummaryLength - 3) + "...";
|
|
3770
|
+
return getSkillSummary(skill, this.config.maxSummaryLength);
|
|
3548
3771
|
}
|
|
3549
3772
|
/**
|
|
3550
|
-
* Escape special XML characters
|
|
3773
|
+
* Escape special XML characters.
|
|
3774
|
+
* Delegates to shared escapeXml utility.
|
|
3551
3775
|
*/
|
|
3552
3776
|
escapeXml(str) {
|
|
3553
|
-
return str
|
|
3777
|
+
return escapeXml(str);
|
|
3554
3778
|
}
|
|
3555
3779
|
/**
|
|
3556
3780
|
* Estimate tokens for a full skill
|
|
@@ -3646,7 +3870,7 @@ function listBuiltInProfiles() {
|
|
|
3646
3870
|
}
|
|
3647
3871
|
|
|
3648
3872
|
// src/serving/graph-server.ts
|
|
3649
|
-
var
|
|
3873
|
+
var DEFAULT_CONFIG4 = {
|
|
3650
3874
|
agentCanModify: true,
|
|
3651
3875
|
agentCanSetLoadout: false,
|
|
3652
3876
|
agentCanSwitchProfile: true,
|
|
@@ -3658,24 +3882,29 @@ var DEFAULT_CONFIG3 = {
|
|
|
3658
3882
|
persistState: false,
|
|
3659
3883
|
outputFormat: "xml",
|
|
3660
3884
|
includeTokenEstimates: false,
|
|
3885
|
+
enableCatalog: true,
|
|
3661
3886
|
profiles: {}
|
|
3662
3887
|
};
|
|
3663
3888
|
var SkillGraphServer = class {
|
|
3664
3889
|
// Track LRU for eviction
|
|
3665
3890
|
constructor(storage, config) {
|
|
3666
3891
|
this.storage = storage;
|
|
3892
|
+
this.catalogRenderer = null;
|
|
3667
3893
|
this.handlers = /* @__PURE__ */ new Set();
|
|
3668
3894
|
this.lruOrder = [];
|
|
3669
3895
|
this.config = {
|
|
3670
|
-
...
|
|
3896
|
+
...DEFAULT_CONFIG4,
|
|
3671
3897
|
...config,
|
|
3672
|
-
profiles: { ...builtInProfiles, ...
|
|
3898
|
+
profiles: { ...builtInProfiles, ...DEFAULT_CONFIG4.profiles, ...config?.profiles }
|
|
3673
3899
|
};
|
|
3674
3900
|
this.compiler = new LoadoutCompiler(storage);
|
|
3675
3901
|
this.projectDetector = new ProjectDetector();
|
|
3676
3902
|
this.viewRenderer = new ViewRenderer({
|
|
3677
3903
|
includeTokenEstimates: this.config.includeTokenEstimates
|
|
3678
3904
|
});
|
|
3905
|
+
if (this.config.enableCatalog !== false) {
|
|
3906
|
+
this.catalogRenderer = new CatalogRenderer(storage, config?.catalogConfig);
|
|
3907
|
+
}
|
|
3679
3908
|
this.state = {
|
|
3680
3909
|
available: /* @__PURE__ */ new Map(),
|
|
3681
3910
|
expanded: /* @__PURE__ */ new Set(),
|
|
@@ -3964,17 +4193,70 @@ var SkillGraphServer = class {
|
|
|
3964
4193
|
profiles: this.getProfiles()
|
|
3965
4194
|
};
|
|
3966
4195
|
}
|
|
4196
|
+
/**
|
|
4197
|
+
* Agent browses the skill catalog at a given path.
|
|
4198
|
+
* Returns rendered category view (subcategories or skill summaries at leaf).
|
|
4199
|
+
* Pass no path for the top-level overview.
|
|
4200
|
+
*/
|
|
4201
|
+
async agentBrowseCatalog(path13) {
|
|
4202
|
+
if (!this.catalogRenderer) {
|
|
4203
|
+
return "<error>Catalog browsing is not enabled</error>";
|
|
4204
|
+
}
|
|
4205
|
+
if (!path13 || path13.length === 0) {
|
|
4206
|
+
const result2 = await this.catalogRenderer.renderOverview();
|
|
4207
|
+
if (result2) {
|
|
4208
|
+
this.emit({ type: "catalog:browsed", path: [] });
|
|
4209
|
+
}
|
|
4210
|
+
return result2;
|
|
4211
|
+
}
|
|
4212
|
+
const result = await this.catalogRenderer.renderCategory(path13);
|
|
4213
|
+
this.emit({ type: "catalog:browsed", path: path13 });
|
|
4214
|
+
return result;
|
|
4215
|
+
}
|
|
4216
|
+
/**
|
|
4217
|
+
* Agent adds a skill discovered via browsing directly to loadout and expands it.
|
|
4218
|
+
* Bridges browse → loadout: found it in catalog, now load it.
|
|
4219
|
+
*/
|
|
4220
|
+
async agentAddFromCatalog(skillId) {
|
|
4221
|
+
if (!this.config.agentCanModify) {
|
|
4222
|
+
return { added: false, pending: false, expanded: false };
|
|
4223
|
+
}
|
|
4224
|
+
if (this.state.available.has(skillId)) {
|
|
4225
|
+
const expanded = this.expandSkill(skillId);
|
|
4226
|
+
return { added: false, pending: false, expanded };
|
|
4227
|
+
}
|
|
4228
|
+
const result = await this.agentRequestSkills([skillId]);
|
|
4229
|
+
if (result.added.length > 0) {
|
|
4230
|
+
const expanded = this.expandSkill(skillId);
|
|
4231
|
+
this.emit({ type: "catalog:added", skillId });
|
|
4232
|
+
return { added: true, pending: false, expanded };
|
|
4233
|
+
}
|
|
4234
|
+
if (result.pending.length > 0) {
|
|
4235
|
+
return { added: false, pending: true, expanded: false };
|
|
4236
|
+
}
|
|
4237
|
+
return { added: false, pending: false, expanded: false };
|
|
4238
|
+
}
|
|
3967
4239
|
// ===========================================================================
|
|
3968
4240
|
// RENDERING
|
|
3969
4241
|
// ===========================================================================
|
|
3970
4242
|
/**
|
|
3971
|
-
* Render current state as system prompt content
|
|
4243
|
+
* Render current state as system prompt content.
|
|
4244
|
+
* Includes catalog overview when catalog is enabled.
|
|
3972
4245
|
*/
|
|
3973
|
-
renderSystemPrompt() {
|
|
4246
|
+
async renderSystemPrompt() {
|
|
4247
|
+
let prompt;
|
|
3974
4248
|
if (this.config.outputFormat === "markdown") {
|
|
3975
|
-
|
|
4249
|
+
prompt = this.viewRenderer.renderMarkdown(this.state);
|
|
4250
|
+
} else {
|
|
4251
|
+
prompt = this.viewRenderer.renderXml(this.state);
|
|
3976
4252
|
}
|
|
3977
|
-
|
|
4253
|
+
if (this.catalogRenderer) {
|
|
4254
|
+
const overview = await this.catalogRenderer.renderOverview();
|
|
4255
|
+
if (overview) {
|
|
4256
|
+
prompt += "\n" + overview;
|
|
4257
|
+
}
|
|
4258
|
+
}
|
|
4259
|
+
return prompt;
|
|
3978
4260
|
}
|
|
3979
4261
|
/**
|
|
3980
4262
|
* Estimate total tokens for current loadout
|
|
@@ -4345,7 +4627,7 @@ function resolveSkilltreeDir(repoRoot) {
|
|
|
4345
4627
|
if (fs4.existsSync(swarmDir)) return swarmDir;
|
|
4346
4628
|
return path4.join(repoRoot, ".skilltree");
|
|
4347
4629
|
}
|
|
4348
|
-
var
|
|
4630
|
+
var DEFAULT_CONFIG5 = {
|
|
4349
4631
|
version: 1,
|
|
4350
4632
|
discovery: "default",
|
|
4351
4633
|
paths: ["skills"],
|
|
@@ -4373,7 +4655,7 @@ function getSkilltreeDir(repoRoot) {
|
|
|
4373
4655
|
}
|
|
4374
4656
|
function getSkillsDir(repoRoot, config) {
|
|
4375
4657
|
const skilltreeDir = getSkilltreeDir(repoRoot);
|
|
4376
|
-
const paths = config?.paths ||
|
|
4658
|
+
const paths = config?.paths || DEFAULT_CONFIG5.paths;
|
|
4377
4659
|
return path4.join(skilltreeDir, paths[0]);
|
|
4378
4660
|
}
|
|
4379
4661
|
function parseSkilltreeConfig(content) {
|
|
@@ -4386,10 +4668,10 @@ function validateConfig(config) {
|
|
|
4386
4668
|
}
|
|
4387
4669
|
return {
|
|
4388
4670
|
version: 1,
|
|
4389
|
-
discovery: config.discovery ||
|
|
4390
|
-
paths: config.paths ||
|
|
4391
|
-
exclude: [...
|
|
4392
|
-
skillFilePatterns: config.skillFilePatterns ||
|
|
4671
|
+
discovery: config.discovery || DEFAULT_CONFIG5.discovery,
|
|
4672
|
+
paths: config.paths || DEFAULT_CONFIG5.paths,
|
|
4673
|
+
exclude: [...DEFAULT_CONFIG5.exclude || [], ...config.exclude || []],
|
|
4674
|
+
skillFilePatterns: config.skillFilePatterns || DEFAULT_CONFIG5.skillFilePatterns,
|
|
4393
4675
|
namespace: config.namespace
|
|
4394
4676
|
};
|
|
4395
4677
|
}
|
|
@@ -4400,7 +4682,7 @@ async function loadSkilltreeConfig(repoRoot) {
|
|
|
4400
4682
|
return parseSkilltreeConfig(content);
|
|
4401
4683
|
} catch (error) {
|
|
4402
4684
|
if (error.code === "ENOENT") {
|
|
4403
|
-
return { ...
|
|
4685
|
+
return { ...DEFAULT_CONFIG5 };
|
|
4404
4686
|
}
|
|
4405
4687
|
throw error;
|
|
4406
4688
|
}
|
|
@@ -4435,7 +4717,7 @@ async function initSkilltreeDir(repoRoot, config) {
|
|
|
4435
4717
|
);
|
|
4436
4718
|
}
|
|
4437
4719
|
}
|
|
4438
|
-
function isSkillFile(filename, patterns =
|
|
4720
|
+
function isSkillFile(filename, patterns = DEFAULT_CONFIG5.skillFilePatterns) {
|
|
4439
4721
|
const lower = filename.toLowerCase();
|
|
4440
4722
|
for (const pattern of patterns) {
|
|
4441
4723
|
if (pattern.includes("*")) {
|
|
@@ -4468,7 +4750,7 @@ async function discoverSkills(repoRoot) {
|
|
|
4468
4750
|
if (config.discovery === "scan") {
|
|
4469
4751
|
await scanForSkills(skilltreeDir, skilltreeDir, config, discovered);
|
|
4470
4752
|
} else {
|
|
4471
|
-
const paths = config.paths ||
|
|
4753
|
+
const paths = config.paths || DEFAULT_CONFIG5.paths;
|
|
4472
4754
|
for (const searchPath of paths) {
|
|
4473
4755
|
const fullPath = path4.join(skilltreeDir, searchPath);
|
|
4474
4756
|
await scanForSkills(skilltreeDir, fullPath, config, discovered);
|
|
@@ -8257,6 +8539,1168 @@ function createDefaultSyncConfig(remoteUrl, agentId, options) {
|
|
|
8257
8539
|
};
|
|
8258
8540
|
}
|
|
8259
8541
|
|
|
8542
|
+
// src/services/indexer.ts
|
|
8543
|
+
var path12 = __toESM(require("path"));
|
|
8544
|
+
var fs12 = __toESM(require("fs"));
|
|
8545
|
+
|
|
8546
|
+
// src/config/types.ts
|
|
8547
|
+
var DEFAULT_CONFIG6 = {
|
|
8548
|
+
storage: {
|
|
8549
|
+
path: "~/.skill-tree"
|
|
8550
|
+
},
|
|
8551
|
+
indexer: {
|
|
8552
|
+
sources: [],
|
|
8553
|
+
batch_size: 10,
|
|
8554
|
+
min_confidence: 0.7
|
|
8555
|
+
},
|
|
8556
|
+
matching: {
|
|
8557
|
+
embedding_model: "text-embedding-3-small",
|
|
8558
|
+
similarity_threshold: 0.8
|
|
8559
|
+
},
|
|
8560
|
+
sync: {
|
|
8561
|
+
auto_check: false,
|
|
8562
|
+
check_interval_hours: 24,
|
|
8563
|
+
conflict_resolution: "prompt"
|
|
8564
|
+
},
|
|
8565
|
+
cli: {
|
|
8566
|
+
output_format: "table",
|
|
8567
|
+
color: true,
|
|
8568
|
+
quiet: false
|
|
8569
|
+
},
|
|
8570
|
+
materialization: {
|
|
8571
|
+
enabled: false,
|
|
8572
|
+
mode: "symlink",
|
|
8573
|
+
symlink_paths: [],
|
|
8574
|
+
agents_md_path: "",
|
|
8575
|
+
agents_md_format: "xml",
|
|
8576
|
+
debounce_ms: 500
|
|
8577
|
+
}
|
|
8578
|
+
};
|
|
8579
|
+
|
|
8580
|
+
// src/config/loader.ts
|
|
8581
|
+
var fs11 = __toESM(require("fs"));
|
|
8582
|
+
var path11 = __toESM(require("path"));
|
|
8583
|
+
var os = __toESM(require("os"));
|
|
8584
|
+
var ENV_MAPPINGS = {
|
|
8585
|
+
GITHUB_TOKEN: "indexer.github_token",
|
|
8586
|
+
ANTHROPIC_API_KEY: "indexer.anthropic_key",
|
|
8587
|
+
SKILL_TREE_STORAGE_PATH: "storage.path",
|
|
8588
|
+
SKILL_TREE_OUTPUT_FORMAT: "cli.output_format",
|
|
8589
|
+
SKILL_TREE_QUIET: "cli.quiet",
|
|
8590
|
+
SKILL_TREE_NO_COLOR: "cli.color"
|
|
8591
|
+
};
|
|
8592
|
+
function getConfigDir() {
|
|
8593
|
+
return path11.join(os.homedir(), ".skill-tree");
|
|
8594
|
+
}
|
|
8595
|
+
function getConfigPath() {
|
|
8596
|
+
return path11.join(getConfigDir(), "config.yaml");
|
|
8597
|
+
}
|
|
8598
|
+
function expandPath(filePath) {
|
|
8599
|
+
if (filePath.startsWith("~/")) {
|
|
8600
|
+
return path11.join(os.homedir(), filePath.slice(2));
|
|
8601
|
+
}
|
|
8602
|
+
if (filePath.startsWith("~")) {
|
|
8603
|
+
return path11.join(os.homedir(), filePath.slice(1));
|
|
8604
|
+
}
|
|
8605
|
+
return filePath;
|
|
8606
|
+
}
|
|
8607
|
+
function substituteEnvVars(value) {
|
|
8608
|
+
let result = value.replace(/\$\{([^}]+)\}/g, (_, varName) => {
|
|
8609
|
+
return process.env[varName] || "";
|
|
8610
|
+
});
|
|
8611
|
+
result = result.replace(/\$([A-Z_][A-Z0-9_]*)/gi, (_, varName) => {
|
|
8612
|
+
return process.env[varName] || "";
|
|
8613
|
+
});
|
|
8614
|
+
return result;
|
|
8615
|
+
}
|
|
8616
|
+
function substituteEnvVarsInObject(obj) {
|
|
8617
|
+
if (typeof obj === "string") {
|
|
8618
|
+
return substituteEnvVars(obj);
|
|
8619
|
+
}
|
|
8620
|
+
if (Array.isArray(obj)) {
|
|
8621
|
+
return obj.map((item) => substituteEnvVarsInObject(item));
|
|
8622
|
+
}
|
|
8623
|
+
if (obj !== null && typeof obj === "object") {
|
|
8624
|
+
const result = {};
|
|
8625
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
8626
|
+
result[key] = substituteEnvVarsInObject(value);
|
|
8627
|
+
}
|
|
8628
|
+
return result;
|
|
8629
|
+
}
|
|
8630
|
+
return obj;
|
|
8631
|
+
}
|
|
8632
|
+
function setNestedProperty(obj, path13, value) {
|
|
8633
|
+
const parts = path13.split(".");
|
|
8634
|
+
let current = obj;
|
|
8635
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
8636
|
+
const part = parts[i];
|
|
8637
|
+
if (!(part in current)) {
|
|
8638
|
+
current[part] = {};
|
|
8639
|
+
}
|
|
8640
|
+
current = current[part];
|
|
8641
|
+
}
|
|
8642
|
+
current[parts[parts.length - 1]] = value;
|
|
8643
|
+
}
|
|
8644
|
+
function deepMerge(target, source) {
|
|
8645
|
+
const result = { ...target };
|
|
8646
|
+
for (const key of Object.keys(source)) {
|
|
8647
|
+
const sourceValue = source[key];
|
|
8648
|
+
const targetValue = result[key];
|
|
8649
|
+
if (sourceValue !== void 0 && typeof sourceValue === "object" && sourceValue !== null && !Array.isArray(sourceValue) && typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue)) {
|
|
8650
|
+
result[key] = deepMerge(
|
|
8651
|
+
targetValue,
|
|
8652
|
+
sourceValue
|
|
8653
|
+
);
|
|
8654
|
+
} else if (sourceValue !== void 0) {
|
|
8655
|
+
result[key] = sourceValue;
|
|
8656
|
+
}
|
|
8657
|
+
}
|
|
8658
|
+
return result;
|
|
8659
|
+
}
|
|
8660
|
+
function parseSimpleYaml(content) {
|
|
8661
|
+
const result = {};
|
|
8662
|
+
const lines = content.split("\n");
|
|
8663
|
+
const stack = [{ indent: -1, obj: result }];
|
|
8664
|
+
for (const line of lines) {
|
|
8665
|
+
const trimmed = line.trim();
|
|
8666
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
8667
|
+
continue;
|
|
8668
|
+
}
|
|
8669
|
+
const indent = line.search(/\S/);
|
|
8670
|
+
while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
|
|
8671
|
+
stack.pop();
|
|
8672
|
+
}
|
|
8673
|
+
const parent = stack[stack.length - 1].obj;
|
|
8674
|
+
const colonIndex = trimmed.indexOf(":");
|
|
8675
|
+
if (colonIndex === -1) continue;
|
|
8676
|
+
const key = trimmed.slice(0, colonIndex).trim();
|
|
8677
|
+
let value = trimmed.slice(colonIndex + 1).trim();
|
|
8678
|
+
if (value === "") {
|
|
8679
|
+
const newObj = {};
|
|
8680
|
+
parent[key] = newObj;
|
|
8681
|
+
stack.push({ indent, obj: newObj });
|
|
8682
|
+
} else if (value.startsWith("[") && value.endsWith("]")) {
|
|
8683
|
+
const items = value.slice(1, -1).split(",").map((s) => s.trim()).filter(Boolean);
|
|
8684
|
+
parent[key] = items.map((item) => {
|
|
8685
|
+
if (item.startsWith('"') && item.endsWith('"') || item.startsWith("'") && item.endsWith("'")) {
|
|
8686
|
+
return item.slice(1, -1);
|
|
8687
|
+
}
|
|
8688
|
+
if (item === "true") return true;
|
|
8689
|
+
if (item === "false") return false;
|
|
8690
|
+
const num = Number(item);
|
|
8691
|
+
if (!isNaN(num)) return num;
|
|
8692
|
+
return item;
|
|
8693
|
+
});
|
|
8694
|
+
} else {
|
|
8695
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
8696
|
+
value = value.slice(1, -1);
|
|
8697
|
+
}
|
|
8698
|
+
if (value === "true") {
|
|
8699
|
+
parent[key] = true;
|
|
8700
|
+
} else if (value === "false") {
|
|
8701
|
+
parent[key] = false;
|
|
8702
|
+
} else {
|
|
8703
|
+
const num = Number(value);
|
|
8704
|
+
if (!isNaN(num) && value !== "") {
|
|
8705
|
+
parent[key] = num;
|
|
8706
|
+
} else {
|
|
8707
|
+
parent[key] = value;
|
|
8708
|
+
}
|
|
8709
|
+
}
|
|
8710
|
+
}
|
|
8711
|
+
}
|
|
8712
|
+
return result;
|
|
8713
|
+
}
|
|
8714
|
+
function loadConfigFromFile(configPath) {
|
|
8715
|
+
const expandedPath = expandPath(configPath);
|
|
8716
|
+
if (!fs11.existsSync(expandedPath)) {
|
|
8717
|
+
return null;
|
|
8718
|
+
}
|
|
8719
|
+
try {
|
|
8720
|
+
const content = fs11.readFileSync(expandedPath, "utf-8");
|
|
8721
|
+
const parsed = parseSimpleYaml(content);
|
|
8722
|
+
return substituteEnvVarsInObject(parsed);
|
|
8723
|
+
} catch (error) {
|
|
8724
|
+
console.error(`Warning: Failed to parse config file ${configPath}:`, error);
|
|
8725
|
+
return null;
|
|
8726
|
+
}
|
|
8727
|
+
}
|
|
8728
|
+
function loadConfigFromEnv() {
|
|
8729
|
+
const config = {};
|
|
8730
|
+
for (const [envVar, configPath] of Object.entries(ENV_MAPPINGS)) {
|
|
8731
|
+
const value = process.env[envVar];
|
|
8732
|
+
if (value !== void 0) {
|
|
8733
|
+
if (envVar === "SKILL_TREE_NO_COLOR") {
|
|
8734
|
+
setNestedProperty(config, configPath, value !== "true" && value !== "1");
|
|
8735
|
+
} else if (envVar === "SKILL_TREE_QUIET") {
|
|
8736
|
+
setNestedProperty(config, configPath, value === "true" || value === "1");
|
|
8737
|
+
} else {
|
|
8738
|
+
setNestedProperty(config, configPath, value);
|
|
8739
|
+
}
|
|
8740
|
+
}
|
|
8741
|
+
}
|
|
8742
|
+
return config;
|
|
8743
|
+
}
|
|
8744
|
+
var ConfigLoader = class {
|
|
8745
|
+
constructor(configPath) {
|
|
8746
|
+
this.loaded = false;
|
|
8747
|
+
this.configPath = configPath || getConfigPath();
|
|
8748
|
+
this.config = { ...DEFAULT_CONFIG6 };
|
|
8749
|
+
}
|
|
8750
|
+
/**
|
|
8751
|
+
* Load configuration from all sources
|
|
8752
|
+
* Priority (highest to lowest):
|
|
8753
|
+
* 1. Environment variables
|
|
8754
|
+
* 2. Config file
|
|
8755
|
+
* 3. Default values
|
|
8756
|
+
*/
|
|
8757
|
+
load() {
|
|
8758
|
+
if (this.loaded) {
|
|
8759
|
+
return this.config;
|
|
8760
|
+
}
|
|
8761
|
+
let config = deepMerge({}, DEFAULT_CONFIG6);
|
|
8762
|
+
const fileConfig = loadConfigFromFile(this.configPath);
|
|
8763
|
+
if (fileConfig) {
|
|
8764
|
+
config = deepMerge(config, fileConfig);
|
|
8765
|
+
}
|
|
8766
|
+
const envConfig = loadConfigFromEnv();
|
|
8767
|
+
config = deepMerge(config, envConfig);
|
|
8768
|
+
config.storage.path = expandPath(config.storage.path);
|
|
8769
|
+
this.config = config;
|
|
8770
|
+
this.loaded = true;
|
|
8771
|
+
return this.config;
|
|
8772
|
+
}
|
|
8773
|
+
/**
|
|
8774
|
+
* Get current configuration
|
|
8775
|
+
*/
|
|
8776
|
+
getConfig() {
|
|
8777
|
+
if (!this.loaded) {
|
|
8778
|
+
return this.load();
|
|
8779
|
+
}
|
|
8780
|
+
return this.config;
|
|
8781
|
+
}
|
|
8782
|
+
/**
|
|
8783
|
+
* Override configuration with partial values
|
|
8784
|
+
*/
|
|
8785
|
+
override(overrides) {
|
|
8786
|
+
this.config = deepMerge(this.getConfig(), overrides);
|
|
8787
|
+
return this.config;
|
|
8788
|
+
}
|
|
8789
|
+
/**
|
|
8790
|
+
* Get a specific config value by path
|
|
8791
|
+
*/
|
|
8792
|
+
get(path13) {
|
|
8793
|
+
const parts = path13.split(".");
|
|
8794
|
+
let current = this.getConfig();
|
|
8795
|
+
for (const part of parts) {
|
|
8796
|
+
if (current === null || typeof current !== "object") {
|
|
8797
|
+
return void 0;
|
|
8798
|
+
}
|
|
8799
|
+
current = current[part];
|
|
8800
|
+
}
|
|
8801
|
+
return current;
|
|
8802
|
+
}
|
|
8803
|
+
/**
|
|
8804
|
+
* Check if config file exists
|
|
8805
|
+
*/
|
|
8806
|
+
configFileExists() {
|
|
8807
|
+
return fs11.existsSync(expandPath(this.configPath));
|
|
8808
|
+
}
|
|
8809
|
+
/**
|
|
8810
|
+
* Create default config file
|
|
8811
|
+
*/
|
|
8812
|
+
createDefaultConfigFile() {
|
|
8813
|
+
const configDir = path11.dirname(expandPath(this.configPath));
|
|
8814
|
+
if (!fs11.existsSync(configDir)) {
|
|
8815
|
+
fs11.mkdirSync(configDir, { recursive: true });
|
|
8816
|
+
}
|
|
8817
|
+
const configContent = `# Skill Tree Configuration
|
|
8818
|
+
# Generated by skill-tree
|
|
8819
|
+
|
|
8820
|
+
storage:
|
|
8821
|
+
# Path to .skilltree directory (skills stored as files, SQLite cache auto-generated)
|
|
8822
|
+
path: ~/.skill-tree
|
|
8823
|
+
|
|
8824
|
+
indexer:
|
|
8825
|
+
# GitHub token for API access (or use GITHUB_TOKEN env var)
|
|
8826
|
+
github_token: \${GITHUB_TOKEN}
|
|
8827
|
+
# Anthropic API key for classification (or use ANTHROPIC_API_KEY env var)
|
|
8828
|
+
anthropic_key: \${ANTHROPIC_API_KEY}
|
|
8829
|
+
sources: []
|
|
8830
|
+
batch_size: 10
|
|
8831
|
+
min_confidence: 0.7
|
|
8832
|
+
|
|
8833
|
+
matching:
|
|
8834
|
+
embedding_model: text-embedding-3-small
|
|
8835
|
+
similarity_threshold: 0.8
|
|
8836
|
+
|
|
8837
|
+
sync:
|
|
8838
|
+
auto_check: false
|
|
8839
|
+
check_interval_hours: 24
|
|
8840
|
+
conflict_resolution: prompt
|
|
8841
|
+
|
|
8842
|
+
cli:
|
|
8843
|
+
output_format: table
|
|
8844
|
+
color: true
|
|
8845
|
+
quiet: false
|
|
8846
|
+
|
|
8847
|
+
materialization:
|
|
8848
|
+
# Enable automatic materialization to agent-discoverable paths
|
|
8849
|
+
enabled: false
|
|
8850
|
+
# Mode: 'symlink' (default) or 'copy'
|
|
8851
|
+
mode: symlink
|
|
8852
|
+
# Directories to expose skills in (e.g. .claude/skills, .agent/skills)
|
|
8853
|
+
symlink_paths: []
|
|
8854
|
+
# Path to auto-generate AGENTS.md (empty = disabled)
|
|
8855
|
+
agents_md_path: ""
|
|
8856
|
+
# Format for AGENTS.md: xml, markdown, json
|
|
8857
|
+
agents_md_format: xml
|
|
8858
|
+
# Debounce interval in ms for batch updates
|
|
8859
|
+
debounce_ms: 500
|
|
8860
|
+
`;
|
|
8861
|
+
fs11.writeFileSync(expandPath(this.configPath), configContent);
|
|
8862
|
+
}
|
|
8863
|
+
};
|
|
8864
|
+
var globalConfig = null;
|
|
8865
|
+
function getConfigLoader(configPath) {
|
|
8866
|
+
if (!globalConfig) {
|
|
8867
|
+
globalConfig = new ConfigLoader(configPath);
|
|
8868
|
+
}
|
|
8869
|
+
return globalConfig;
|
|
8870
|
+
}
|
|
8871
|
+
function loadConfig(configPath) {
|
|
8872
|
+
return getConfigLoader(configPath).load();
|
|
8873
|
+
}
|
|
8874
|
+
|
|
8875
|
+
// src/import/converter.ts
|
|
8876
|
+
function convertIndexerSkill(indexerSkill) {
|
|
8877
|
+
const warnings = [];
|
|
8878
|
+
const instructions = indexerSkill.content || "";
|
|
8879
|
+
const hasStructuredContent = instructions.trim().length > 0;
|
|
8880
|
+
if (!hasStructuredContent) {
|
|
8881
|
+
warnings.push("No content found, instructions will be empty");
|
|
8882
|
+
}
|
|
8883
|
+
const tags = [...indexerSkill.tags || []];
|
|
8884
|
+
if (indexerSkill.sourceRepo) {
|
|
8885
|
+
const repoName = indexerSkill.sourceRepo.split("/").pop();
|
|
8886
|
+
if (repoName && !tags.includes(repoName)) {
|
|
8887
|
+
tags.push(repoName);
|
|
8888
|
+
}
|
|
8889
|
+
}
|
|
8890
|
+
const status = indexerSkill.status === "indexed" ? "active" : indexerSkill.status === "raw" ? "draft" : "draft";
|
|
8891
|
+
const skill = {
|
|
8892
|
+
id: indexerSkill.slug,
|
|
8893
|
+
name: indexerSkill.displayName || indexerSkill.name,
|
|
8894
|
+
version: indexerSkill.version,
|
|
8895
|
+
description: indexerSkill.description,
|
|
8896
|
+
instructions,
|
|
8897
|
+
author: indexerSkill.author,
|
|
8898
|
+
tags,
|
|
8899
|
+
createdAt: new Date(indexerSkill.scrapedAt),
|
|
8900
|
+
updatedAt: new Date(indexerSkill.updatedAt),
|
|
8901
|
+
status,
|
|
8902
|
+
metrics: {
|
|
8903
|
+
usageCount: 0,
|
|
8904
|
+
successRate: 0,
|
|
8905
|
+
feedbackScores: []
|
|
8906
|
+
},
|
|
8907
|
+
source: {
|
|
8908
|
+
type: "imported",
|
|
8909
|
+
location: indexerSkill.sourceUrl,
|
|
8910
|
+
importedAt: /* @__PURE__ */ new Date()
|
|
8911
|
+
},
|
|
8912
|
+
// Extended fields for indexed skills
|
|
8913
|
+
taxonomy: indexerSkill.primaryPath ? {
|
|
8914
|
+
primaryPath: indexerSkill.primaryPath,
|
|
8915
|
+
secondaryPaths: indexerSkill.secondaryPaths,
|
|
8916
|
+
confidence: indexerSkill.confidence
|
|
8917
|
+
} : void 0,
|
|
8918
|
+
externalSource: {
|
|
8919
|
+
url: indexerSkill.sourceUrl,
|
|
8920
|
+
repo: indexerSkill.sourceRepo,
|
|
8921
|
+
scrapedAt: new Date(indexerSkill.scrapedAt)
|
|
8922
|
+
}
|
|
8923
|
+
};
|
|
8924
|
+
return { skill, warnings, hasStructuredContent };
|
|
8925
|
+
}
|
|
8926
|
+
|
|
8927
|
+
// src/services/indexer.ts
|
|
8928
|
+
function hasIndexerSupport(storage) {
|
|
8929
|
+
const s = storage;
|
|
8930
|
+
return typeof s.addRelationship === "function" && typeof s.getTaxonomyTree === "function";
|
|
8931
|
+
}
|
|
8932
|
+
var IndexerService = class {
|
|
8933
|
+
constructor(config = {}, skillBank) {
|
|
8934
|
+
this.initialized = false;
|
|
8935
|
+
this.globalConfig = loadConfig();
|
|
8936
|
+
this.serviceConfig = {
|
|
8937
|
+
githubToken: config.githubToken || this.globalConfig.indexer.github_token,
|
|
8938
|
+
anthropicApiKey: config.anthropicApiKey || this.globalConfig.indexer.anthropic_key,
|
|
8939
|
+
batchSize: config.batchSize || this.globalConfig.indexer.batch_size,
|
|
8940
|
+
minConfidence: config.minConfidence || this.globalConfig.indexer.min_confidence,
|
|
8941
|
+
concurrency: config.concurrency || 3,
|
|
8942
|
+
cacheTtlSeconds: config.cacheTtlSeconds || 3600,
|
|
8943
|
+
databasePath: config.databasePath,
|
|
8944
|
+
cacheDir: config.cacheDir,
|
|
8945
|
+
claudeModel: config.claudeModel,
|
|
8946
|
+
scraperModules: config.scraperModules
|
|
8947
|
+
};
|
|
8948
|
+
this.skillBank = skillBank;
|
|
8949
|
+
}
|
|
8950
|
+
/**
|
|
8951
|
+
* Get effective configuration
|
|
8952
|
+
*/
|
|
8953
|
+
getConfig() {
|
|
8954
|
+
return { ...this.serviceConfig };
|
|
8955
|
+
}
|
|
8956
|
+
/**
|
|
8957
|
+
* Initialize the service (lazy load scraper modules)
|
|
8958
|
+
*/
|
|
8959
|
+
async initialize() {
|
|
8960
|
+
if (this.initialized) return;
|
|
8961
|
+
try {
|
|
8962
|
+
if (this.serviceConfig.scraperModules) {
|
|
8963
|
+
const mod = this.serviceConfig.scraperModules;
|
|
8964
|
+
this.databaseModule = mod;
|
|
8965
|
+
if (mod.createDatabase) {
|
|
8966
|
+
const dbPath = this.serviceConfig.databasePath || path12.join(process.cwd(), "scraper/data/skills.db");
|
|
8967
|
+
const dbDir = path12.dirname(dbPath);
|
|
8968
|
+
if (!fs12.existsSync(dbDir)) fs12.mkdirSync(dbDir, { recursive: true });
|
|
8969
|
+
this.db = mod.createDatabase({ type: "sqlite", path: dbPath });
|
|
8970
|
+
if (this.db.connect) await this.db.connect();
|
|
8971
|
+
if (this.db.migrate) await this.db.migrate();
|
|
8972
|
+
}
|
|
8973
|
+
const localDb = this.db;
|
|
8974
|
+
if (mod.ScraperService) {
|
|
8975
|
+
this.scraperModule = {
|
|
8976
|
+
createScraper: (config) => {
|
|
8977
|
+
const scraperConfig = {
|
|
8978
|
+
githubToken: config.githubToken || "",
|
|
8979
|
+
cacheEnabled: true,
|
|
8980
|
+
cacheDir: config.cacheDir || path12.join(process.cwd(), ".cache/scraper"),
|
|
8981
|
+
cacheTtlSeconds: config.cacheTtlSeconds || 3600,
|
|
8982
|
+
requestDelayMs: 100,
|
|
8983
|
+
maxRetries: 3
|
|
8984
|
+
};
|
|
8985
|
+
const svc = new mod.ScraperService(localDb, scraperConfig);
|
|
8986
|
+
let initialized = false;
|
|
8987
|
+
const ensureInit = async () => {
|
|
8988
|
+
if (!initialized) {
|
|
8989
|
+
await svc.init();
|
|
8990
|
+
initialized = true;
|
|
8991
|
+
}
|
|
8992
|
+
};
|
|
8993
|
+
return {
|
|
8994
|
+
scrapeAwesomeList: async (url, opts) => {
|
|
8995
|
+
await ensureInit();
|
|
8996
|
+
await svc.scrape({ type: "awesome-list", url }, opts);
|
|
8997
|
+
return await localDb.listSkills() || [];
|
|
8998
|
+
},
|
|
8999
|
+
scrapeRepository: async (url, opts) => {
|
|
9000
|
+
await ensureInit();
|
|
9001
|
+
await svc.scrape({ type: "repository", url }, opts);
|
|
9002
|
+
return await localDb.listSkills() || [];
|
|
9003
|
+
}
|
|
9004
|
+
};
|
|
9005
|
+
}
|
|
9006
|
+
};
|
|
9007
|
+
}
|
|
9008
|
+
if (mod.SkillClassifier) {
|
|
9009
|
+
this.indexerModule = {
|
|
9010
|
+
createIndexer: (config) => {
|
|
9011
|
+
const classifier = new mod.SkillClassifier(config);
|
|
9012
|
+
return {
|
|
9013
|
+
classifySkill: async (skill) => {
|
|
9014
|
+
let tree = null;
|
|
9015
|
+
if (mod.TaxonomyManager && localDb) {
|
|
9016
|
+
try {
|
|
9017
|
+
const tm = new mod.TaxonomyManager(localDb);
|
|
9018
|
+
tree = await tm.getTree();
|
|
9019
|
+
} catch {
|
|
9020
|
+
}
|
|
9021
|
+
}
|
|
9022
|
+
return classifier.classify(skill, tree);
|
|
9023
|
+
}
|
|
9024
|
+
};
|
|
9025
|
+
}
|
|
9026
|
+
};
|
|
9027
|
+
}
|
|
9028
|
+
this.initialized = true;
|
|
9029
|
+
return;
|
|
9030
|
+
}
|
|
9031
|
+
const possiblePaths = [
|
|
9032
|
+
// Relative to this file in dist
|
|
9033
|
+
path12.resolve(__dirname, "../../scraper/dist"),
|
|
9034
|
+
// Relative to project root
|
|
9035
|
+
path12.resolve(process.cwd(), "scraper/dist"),
|
|
9036
|
+
// Absolute paths from config
|
|
9037
|
+
this.serviceConfig.cacheDir ? path12.resolve(this.serviceConfig.cacheDir, "../scraper/dist") : null
|
|
9038
|
+
].filter(Boolean);
|
|
9039
|
+
let scraperBasePath = null;
|
|
9040
|
+
for (const basePath of possiblePaths) {
|
|
9041
|
+
const scraperIndex = path12.join(basePath, "scraper/index.js");
|
|
9042
|
+
if (fs12.existsSync(scraperIndex)) {
|
|
9043
|
+
scraperBasePath = basePath;
|
|
9044
|
+
break;
|
|
9045
|
+
}
|
|
9046
|
+
}
|
|
9047
|
+
if (!scraperBasePath) {
|
|
9048
|
+
throw new Error(
|
|
9049
|
+
"Scraper modules not found. Run `cd scraper && npm run build` first."
|
|
9050
|
+
);
|
|
9051
|
+
}
|
|
9052
|
+
const scraperPath = path12.join(scraperBasePath, "scraper/index.js");
|
|
9053
|
+
const indexerPath = path12.join(scraperBasePath, "indexer/index.js");
|
|
9054
|
+
const databasePath = path12.join(scraperBasePath, "database/index.js");
|
|
9055
|
+
this.scraperModule = await import(
|
|
9056
|
+
/* webpackIgnore: true */
|
|
9057
|
+
scraperPath
|
|
9058
|
+
);
|
|
9059
|
+
this.indexerModule = await import(
|
|
9060
|
+
/* webpackIgnore: true */
|
|
9061
|
+
indexerPath
|
|
9062
|
+
);
|
|
9063
|
+
this.databaseModule = await import(
|
|
9064
|
+
/* webpackIgnore: true */
|
|
9065
|
+
databasePath
|
|
9066
|
+
);
|
|
9067
|
+
if (this.databaseModule.createDatabase) {
|
|
9068
|
+
const dbPath = this.serviceConfig.databasePath || path12.join(process.cwd(), "scraper/data/skills.db");
|
|
9069
|
+
this.db = this.databaseModule.createDatabase(dbPath);
|
|
9070
|
+
}
|
|
9071
|
+
this.initialized = true;
|
|
9072
|
+
} catch (err) {
|
|
9073
|
+
console.warn(`Scraper modules not available: ${err.message}`);
|
|
9074
|
+
console.warn("Some indexer features will be limited.");
|
|
9075
|
+
this.initialized = true;
|
|
9076
|
+
}
|
|
9077
|
+
}
|
|
9078
|
+
/**
|
|
9079
|
+
* Check if the indexer is available
|
|
9080
|
+
*/
|
|
9081
|
+
isAvailable() {
|
|
9082
|
+
return this.initialized && !!this.scraperModule;
|
|
9083
|
+
}
|
|
9084
|
+
/**
|
|
9085
|
+
* Check if running in degraded mode (no scraper modules)
|
|
9086
|
+
*/
|
|
9087
|
+
isDegradedMode() {
|
|
9088
|
+
return this.initialized && !this.scraperModule;
|
|
9089
|
+
}
|
|
9090
|
+
/**
|
|
9091
|
+
* Scrape skills from GitHub sources
|
|
9092
|
+
*/
|
|
9093
|
+
async scrape(sources, options) {
|
|
9094
|
+
await this.initialize();
|
|
9095
|
+
if (!this.scraperModule || !this.db) {
|
|
9096
|
+
throw new Error(
|
|
9097
|
+
"Scraper not available. Build the scraper module first: cd scraper && npm run build"
|
|
9098
|
+
);
|
|
9099
|
+
}
|
|
9100
|
+
const result = {
|
|
9101
|
+
discovered: 0,
|
|
9102
|
+
scraped: 0,
|
|
9103
|
+
skipped: 0,
|
|
9104
|
+
failed: 0,
|
|
9105
|
+
unchanged: 0,
|
|
9106
|
+
errors: []
|
|
9107
|
+
};
|
|
9108
|
+
try {
|
|
9109
|
+
const scraper = this.scraperModule.createScraper({
|
|
9110
|
+
githubToken: this.serviceConfig.githubToken,
|
|
9111
|
+
cacheDir: this.serviceConfig.cacheDir,
|
|
9112
|
+
cacheTtlSeconds: this.serviceConfig.cacheTtlSeconds
|
|
9113
|
+
});
|
|
9114
|
+
for (const source of sources) {
|
|
9115
|
+
try {
|
|
9116
|
+
let skills;
|
|
9117
|
+
if (source.type === "awesome-list") {
|
|
9118
|
+
skills = await scraper.scrapeAwesomeList(source.url, {
|
|
9119
|
+
force: options?.force
|
|
9120
|
+
});
|
|
9121
|
+
} else {
|
|
9122
|
+
skills = await scraper.scrapeRepository(source.url, {
|
|
9123
|
+
force: options?.force
|
|
9124
|
+
});
|
|
9125
|
+
}
|
|
9126
|
+
result.discovered += skills.length;
|
|
9127
|
+
for (const skill of skills) {
|
|
9128
|
+
try {
|
|
9129
|
+
const existing = this.db.getSkillBySlug?.(skill.slug);
|
|
9130
|
+
if (existing && !options?.force) {
|
|
9131
|
+
result.unchanged++;
|
|
9132
|
+
} else {
|
|
9133
|
+
this.db.saveSkill?.(skill);
|
|
9134
|
+
result.scraped++;
|
|
9135
|
+
}
|
|
9136
|
+
} catch (err) {
|
|
9137
|
+
result.failed++;
|
|
9138
|
+
result.errors.push(
|
|
9139
|
+
`Failed to save skill ${skill.slug}: ${err.message}`
|
|
9140
|
+
);
|
|
9141
|
+
}
|
|
9142
|
+
}
|
|
9143
|
+
} catch (err) {
|
|
9144
|
+
result.failed++;
|
|
9145
|
+
result.errors.push(
|
|
9146
|
+
`Failed to scrape ${source.url}: ${err.message}`
|
|
9147
|
+
);
|
|
9148
|
+
}
|
|
9149
|
+
}
|
|
9150
|
+
} catch (err) {
|
|
9151
|
+
result.errors.push(`Scrape failed: ${err.message}`);
|
|
9152
|
+
}
|
|
9153
|
+
return result;
|
|
9154
|
+
}
|
|
9155
|
+
/**
|
|
9156
|
+
* Classify unindexed skills using AI
|
|
9157
|
+
*/
|
|
9158
|
+
async classify(options) {
|
|
9159
|
+
await this.initialize();
|
|
9160
|
+
if (!this.indexerModule || !this.db) {
|
|
9161
|
+
throw new Error(
|
|
9162
|
+
"Indexer not available. Build the scraper module first: cd scraper && npm run build"
|
|
9163
|
+
);
|
|
9164
|
+
}
|
|
9165
|
+
const result = {
|
|
9166
|
+
indexed: 0,
|
|
9167
|
+
skipped: 0,
|
|
9168
|
+
failed: 0,
|
|
9169
|
+
errors: []
|
|
9170
|
+
};
|
|
9171
|
+
try {
|
|
9172
|
+
const indexer = this.indexerModule.createIndexer({
|
|
9173
|
+
anthropicApiKey: this.serviceConfig.anthropicApiKey,
|
|
9174
|
+
model: this.serviceConfig.claudeModel || "claude-sonnet-4-20250514",
|
|
9175
|
+
minConfidence: this.serviceConfig.minConfidence
|
|
9176
|
+
});
|
|
9177
|
+
let skills;
|
|
9178
|
+
if (options?.skillId) {
|
|
9179
|
+
const skill = this.db.getSkillBySlug?.(options.skillId);
|
|
9180
|
+
skills = skill ? [skill] : [];
|
|
9181
|
+
} else if (options?.all) {
|
|
9182
|
+
skills = this.db.getAllSkills?.() || [];
|
|
9183
|
+
} else {
|
|
9184
|
+
skills = this.db.getSkillsByStatus?.("raw") || [];
|
|
9185
|
+
}
|
|
9186
|
+
const batchSize = this.serviceConfig.batchSize || 10;
|
|
9187
|
+
for (let i = 0; i < skills.length; i += batchSize) {
|
|
9188
|
+
const batch = skills.slice(i, i + batchSize);
|
|
9189
|
+
for (const skill of batch) {
|
|
9190
|
+
if (skill.status === "indexed" && !options?.all) {
|
|
9191
|
+
result.skipped++;
|
|
9192
|
+
continue;
|
|
9193
|
+
}
|
|
9194
|
+
try {
|
|
9195
|
+
const classification = await indexer.classifySkill(skill);
|
|
9196
|
+
skill.status = "indexed";
|
|
9197
|
+
skill.primaryPath = classification.primaryPath;
|
|
9198
|
+
skill.secondaryPaths = classification.secondaryPaths;
|
|
9199
|
+
skill.confidence = classification.confidence;
|
|
9200
|
+
skill.classificationReasoning = classification.reasoning;
|
|
9201
|
+
skill.indexedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9202
|
+
this.db.saveSkill?.(skill);
|
|
9203
|
+
result.indexed++;
|
|
9204
|
+
} catch (err) {
|
|
9205
|
+
result.failed++;
|
|
9206
|
+
result.errors.push(
|
|
9207
|
+
`Failed to classify ${skill.slug}: ${err.message}`
|
|
9208
|
+
);
|
|
9209
|
+
}
|
|
9210
|
+
}
|
|
9211
|
+
}
|
|
9212
|
+
} catch (err) {
|
|
9213
|
+
result.errors.push(`Classification failed: ${err.message}`);
|
|
9214
|
+
}
|
|
9215
|
+
return result;
|
|
9216
|
+
}
|
|
9217
|
+
/**
|
|
9218
|
+
* Detect relationships between skills
|
|
9219
|
+
*/
|
|
9220
|
+
async detectRelationships(options) {
|
|
9221
|
+
await this.initialize();
|
|
9222
|
+
const result = {
|
|
9223
|
+
detected: 0,
|
|
9224
|
+
skipped: 0,
|
|
9225
|
+
errors: []
|
|
9226
|
+
};
|
|
9227
|
+
if (this.skillBank) {
|
|
9228
|
+
try {
|
|
9229
|
+
const storage = this.skillBank.getStorage();
|
|
9230
|
+
if (hasIndexerSupport(storage)) {
|
|
9231
|
+
const skills = await this.skillBank.listSkills();
|
|
9232
|
+
const targetSkills = options?.skillId ? skills.filter((s) => s.id === options.skillId) : skills;
|
|
9233
|
+
for (const skill of targetSkills) {
|
|
9234
|
+
const relationships = this.detectSkillRelationships(skill, skills);
|
|
9235
|
+
for (const rel of relationships) {
|
|
9236
|
+
try {
|
|
9237
|
+
await storage.addRelationship(
|
|
9238
|
+
skill.id,
|
|
9239
|
+
rel.targetSkillId,
|
|
9240
|
+
rel.type,
|
|
9241
|
+
rel.confidence,
|
|
9242
|
+
rel.reasoning
|
|
9243
|
+
);
|
|
9244
|
+
result.detected++;
|
|
9245
|
+
} catch (err) {
|
|
9246
|
+
result.skipped++;
|
|
9247
|
+
}
|
|
9248
|
+
}
|
|
9249
|
+
}
|
|
9250
|
+
}
|
|
9251
|
+
} catch (err) {
|
|
9252
|
+
result.errors.push(
|
|
9253
|
+
`Relationship detection failed: ${err.message}`
|
|
9254
|
+
);
|
|
9255
|
+
}
|
|
9256
|
+
return result;
|
|
9257
|
+
}
|
|
9258
|
+
if (!this.db) {
|
|
9259
|
+
throw new Error("Neither SkillBank nor scraper database available.");
|
|
9260
|
+
}
|
|
9261
|
+
try {
|
|
9262
|
+
const skills = this.db.getAllSkills?.() || [];
|
|
9263
|
+
const targetSkills = options?.skillId ? skills.filter((s) => s.slug === options.skillId) : skills.filter((s) => s.status === "indexed");
|
|
9264
|
+
for (const skill of targetSkills) {
|
|
9265
|
+
const relationships = this.detectSkillRelationships(skill, skills);
|
|
9266
|
+
for (const rel of relationships) {
|
|
9267
|
+
try {
|
|
9268
|
+
this.db.saveRelationship?.({
|
|
9269
|
+
sourceSkillId: skill.id,
|
|
9270
|
+
...rel
|
|
9271
|
+
});
|
|
9272
|
+
result.detected++;
|
|
9273
|
+
} catch (err) {
|
|
9274
|
+
result.skipped++;
|
|
9275
|
+
}
|
|
9276
|
+
}
|
|
9277
|
+
}
|
|
9278
|
+
} catch (err) {
|
|
9279
|
+
result.errors.push(
|
|
9280
|
+
`Relationship detection failed: ${err.message}`
|
|
9281
|
+
);
|
|
9282
|
+
}
|
|
9283
|
+
return result;
|
|
9284
|
+
}
|
|
9285
|
+
/**
|
|
9286
|
+
* Detect relationships for a single skill
|
|
9287
|
+
*/
|
|
9288
|
+
detectSkillRelationships(skill, allSkills) {
|
|
9289
|
+
const relationships = [];
|
|
9290
|
+
const skillId = skill.id || skill.slug;
|
|
9291
|
+
const skillContent = `${skill.name} ${skill.description} ${skill.instructions || ""} ${skill.content || ""}`.toLowerCase();
|
|
9292
|
+
for (const other of allSkills) {
|
|
9293
|
+
const otherId = other.id || other.slug;
|
|
9294
|
+
if (otherId === skillId) continue;
|
|
9295
|
+
const otherName = (other.name || "").toLowerCase();
|
|
9296
|
+
const dependencyPatterns = [
|
|
9297
|
+
"requires",
|
|
9298
|
+
"depends on",
|
|
9299
|
+
"uses",
|
|
9300
|
+
"needs",
|
|
9301
|
+
"builds on"
|
|
9302
|
+
];
|
|
9303
|
+
for (const pattern of dependencyPatterns) {
|
|
9304
|
+
if (skillContent.includes(`${pattern} ${otherName}`)) {
|
|
9305
|
+
relationships.push({
|
|
9306
|
+
targetSkillId: otherId,
|
|
9307
|
+
type: "depends_on",
|
|
9308
|
+
confidence: 0.7,
|
|
9309
|
+
reasoning: `Content mentions "${pattern} ${other.name}"`
|
|
9310
|
+
});
|
|
9311
|
+
break;
|
|
9312
|
+
}
|
|
9313
|
+
}
|
|
9314
|
+
const extensionPatterns = [
|
|
9315
|
+
"extends",
|
|
9316
|
+
"improves",
|
|
9317
|
+
"enhances",
|
|
9318
|
+
"builds upon"
|
|
9319
|
+
];
|
|
9320
|
+
for (const pattern of extensionPatterns) {
|
|
9321
|
+
if (skillContent.includes(`${pattern} ${otherName}`)) {
|
|
9322
|
+
relationships.push({
|
|
9323
|
+
targetSkillId: otherId,
|
|
9324
|
+
type: "extends",
|
|
9325
|
+
confidence: 0.7,
|
|
9326
|
+
reasoning: `Content mentions "${pattern} ${other.name}"`
|
|
9327
|
+
});
|
|
9328
|
+
break;
|
|
9329
|
+
}
|
|
9330
|
+
}
|
|
9331
|
+
const alternativePatterns = [
|
|
9332
|
+
"alternative to",
|
|
9333
|
+
"instead of",
|
|
9334
|
+
"replacement for"
|
|
9335
|
+
];
|
|
9336
|
+
for (const pattern of alternativePatterns) {
|
|
9337
|
+
if (skillContent.includes(`${pattern} ${otherName}`)) {
|
|
9338
|
+
relationships.push({
|
|
9339
|
+
targetSkillId: otherId,
|
|
9340
|
+
type: "alternative",
|
|
9341
|
+
confidence: 0.6,
|
|
9342
|
+
reasoning: `Content mentions "${pattern} ${other.name}"`
|
|
9343
|
+
});
|
|
9344
|
+
break;
|
|
9345
|
+
}
|
|
9346
|
+
}
|
|
9347
|
+
const skillPath = skill.taxonomy?.primaryPath || skill.primaryPath || [];
|
|
9348
|
+
const otherPath = other.taxonomy?.primaryPath || other.primaryPath || [];
|
|
9349
|
+
if (skillPath.length >= 2 && otherPath.length >= 2) {
|
|
9350
|
+
if (skillPath[0] === otherPath[0] && skillPath[1] === otherPath[1]) {
|
|
9351
|
+
relationships.push({
|
|
9352
|
+
targetSkillId: otherId,
|
|
9353
|
+
type: "related",
|
|
9354
|
+
confidence: 0.5,
|
|
9355
|
+
reasoning: `Same taxonomy category: ${skillPath.slice(0, 2).join(" > ")}`
|
|
9356
|
+
});
|
|
9357
|
+
}
|
|
9358
|
+
}
|
|
9359
|
+
}
|
|
9360
|
+
return relationships;
|
|
9361
|
+
}
|
|
9362
|
+
/**
|
|
9363
|
+
* Get taxonomy tree
|
|
9364
|
+
*/
|
|
9365
|
+
async getTaxonomyTree(rootPath) {
|
|
9366
|
+
await this.initialize();
|
|
9367
|
+
if (this.skillBank) {
|
|
9368
|
+
const storage = this.skillBank.getStorage();
|
|
9369
|
+
if (hasIndexerSupport(storage)) {
|
|
9370
|
+
const nodes = await storage.getTaxonomyTree(rootPath);
|
|
9371
|
+
return this.wrapTreeNodes(nodes, rootPath);
|
|
9372
|
+
}
|
|
9373
|
+
}
|
|
9374
|
+
const skills = this.skillBank ? await this.skillBank.listSkills() : this.db?.getAllSkills?.() || [];
|
|
9375
|
+
return this.buildTaxonomyTreeFromSkills(skills, rootPath);
|
|
9376
|
+
}
|
|
9377
|
+
/**
|
|
9378
|
+
* Wrap tree nodes returned from SQLite storage into a root node
|
|
9379
|
+
*/
|
|
9380
|
+
wrapTreeNodes(nodes, rootPath) {
|
|
9381
|
+
const root = {
|
|
9382
|
+
id: "root",
|
|
9383
|
+
name: rootPath?.join(" > ") || "All Skills",
|
|
9384
|
+
path: rootPath || [],
|
|
9385
|
+
skillCount: 0,
|
|
9386
|
+
children: []
|
|
9387
|
+
};
|
|
9388
|
+
for (const node of nodes) {
|
|
9389
|
+
root.children.push(this.convertToTaxonomyNode(node));
|
|
9390
|
+
root.skillCount += this.countNodeSkills(node);
|
|
9391
|
+
}
|
|
9392
|
+
return root;
|
|
9393
|
+
}
|
|
9394
|
+
/**
|
|
9395
|
+
* Convert storage tree node to TaxonomyNode format
|
|
9396
|
+
*/
|
|
9397
|
+
convertToTaxonomyNode(node) {
|
|
9398
|
+
return {
|
|
9399
|
+
id: node.id,
|
|
9400
|
+
name: node.name,
|
|
9401
|
+
path: Array.isArray(node.path) ? node.path : (node.path || "").split("/"),
|
|
9402
|
+
skillCount: node.skillCount || 0,
|
|
9403
|
+
children: (node.children || []).map(
|
|
9404
|
+
(child) => this.convertToTaxonomyNode(child)
|
|
9405
|
+
)
|
|
9406
|
+
};
|
|
9407
|
+
}
|
|
9408
|
+
/**
|
|
9409
|
+
* Count total skills in a node tree
|
|
9410
|
+
*/
|
|
9411
|
+
countNodeSkills(node) {
|
|
9412
|
+
let count = node.skillCount || 0;
|
|
9413
|
+
for (const child of node.children || []) {
|
|
9414
|
+
count += this.countNodeSkills(child);
|
|
9415
|
+
}
|
|
9416
|
+
return count;
|
|
9417
|
+
}
|
|
9418
|
+
/**
|
|
9419
|
+
* Build taxonomy tree from flat nodes
|
|
9420
|
+
*/
|
|
9421
|
+
buildTaxonomyTree(nodes, rootPath) {
|
|
9422
|
+
const root = {
|
|
9423
|
+
id: "root",
|
|
9424
|
+
name: rootPath?.join(" > ") || "All Skills",
|
|
9425
|
+
path: rootPath || [],
|
|
9426
|
+
skillCount: 0,
|
|
9427
|
+
children: []
|
|
9428
|
+
};
|
|
9429
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
9430
|
+
nodeMap.set("root", root);
|
|
9431
|
+
for (const node of nodes) {
|
|
9432
|
+
const taxNode = {
|
|
9433
|
+
id: node.id,
|
|
9434
|
+
name: node.name,
|
|
9435
|
+
path: node.path,
|
|
9436
|
+
skillCount: node.skillCount || 0,
|
|
9437
|
+
children: []
|
|
9438
|
+
};
|
|
9439
|
+
nodeMap.set(node.id, taxNode);
|
|
9440
|
+
}
|
|
9441
|
+
for (const node of nodes) {
|
|
9442
|
+
const taxNode = nodeMap.get(node.id);
|
|
9443
|
+
const parentId = node.parentId || "root";
|
|
9444
|
+
const parent = nodeMap.get(parentId);
|
|
9445
|
+
if (parent) {
|
|
9446
|
+
parent.children.push(taxNode);
|
|
9447
|
+
parent.skillCount += taxNode.skillCount;
|
|
9448
|
+
}
|
|
9449
|
+
}
|
|
9450
|
+
return root;
|
|
9451
|
+
}
|
|
9452
|
+
/**
|
|
9453
|
+
* Build taxonomy tree from skills
|
|
9454
|
+
*/
|
|
9455
|
+
buildTaxonomyTreeFromSkills(skills, rootPath) {
|
|
9456
|
+
const root = {
|
|
9457
|
+
id: "root",
|
|
9458
|
+
name: rootPath?.join(" > ") || "All Skills",
|
|
9459
|
+
path: rootPath || [],
|
|
9460
|
+
skillCount: 0,
|
|
9461
|
+
children: []
|
|
9462
|
+
};
|
|
9463
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
9464
|
+
for (const skill of skills) {
|
|
9465
|
+
const taxonomy = skill.taxonomy || (skill.primaryPath ? { primaryPath: skill.primaryPath } : null);
|
|
9466
|
+
if (!taxonomy?.primaryPath) continue;
|
|
9467
|
+
const skillPath = taxonomy.primaryPath;
|
|
9468
|
+
if (rootPath && rootPath.length > 0) {
|
|
9469
|
+
let matches = true;
|
|
9470
|
+
for (let i = 0; i < rootPath.length; i++) {
|
|
9471
|
+
if (skillPath[i] !== rootPath[i]) {
|
|
9472
|
+
matches = false;
|
|
9473
|
+
break;
|
|
9474
|
+
}
|
|
9475
|
+
}
|
|
9476
|
+
if (!matches) continue;
|
|
9477
|
+
}
|
|
9478
|
+
let currentPath = [];
|
|
9479
|
+
let parent = root;
|
|
9480
|
+
for (const segment of skillPath) {
|
|
9481
|
+
currentPath = [...currentPath, segment];
|
|
9482
|
+
const pathKey = currentPath.join("/");
|
|
9483
|
+
let node = nodeMap.get(pathKey);
|
|
9484
|
+
if (!node) {
|
|
9485
|
+
node = {
|
|
9486
|
+
id: pathKey,
|
|
9487
|
+
name: segment,
|
|
9488
|
+
path: [...currentPath],
|
|
9489
|
+
skillCount: 0,
|
|
9490
|
+
children: []
|
|
9491
|
+
};
|
|
9492
|
+
nodeMap.set(pathKey, node);
|
|
9493
|
+
parent.children.push(node);
|
|
9494
|
+
}
|
|
9495
|
+
parent = node;
|
|
9496
|
+
}
|
|
9497
|
+
parent.skillCount++;
|
|
9498
|
+
root.skillCount++;
|
|
9499
|
+
}
|
|
9500
|
+
return root;
|
|
9501
|
+
}
|
|
9502
|
+
/**
|
|
9503
|
+
* Get indexer statistics
|
|
9504
|
+
*/
|
|
9505
|
+
async getStats() {
|
|
9506
|
+
await this.initialize();
|
|
9507
|
+
if (this.skillBank) {
|
|
9508
|
+
const skills = await this.skillBank.listSkills();
|
|
9509
|
+
const storage = this.skillBank.getStorage();
|
|
9510
|
+
let taxonomyNodes = 0;
|
|
9511
|
+
let relationships = 0;
|
|
9512
|
+
let sources = 0;
|
|
9513
|
+
if (storage) {
|
|
9514
|
+
if (hasIndexerSupport(storage)) {
|
|
9515
|
+
const tree = await storage.getTaxonomyTree();
|
|
9516
|
+
taxonomyNodes = this.countTaxonomyNodes(tree);
|
|
9517
|
+
if (storage.getRelationships) {
|
|
9518
|
+
for (const skill of skills) {
|
|
9519
|
+
const rels = await storage.getRelationships(skill.id);
|
|
9520
|
+
relationships += rels.length;
|
|
9521
|
+
}
|
|
9522
|
+
}
|
|
9523
|
+
}
|
|
9524
|
+
const sourceSet = /* @__PURE__ */ new Set();
|
|
9525
|
+
for (const skill of skills) {
|
|
9526
|
+
if (skill.externalSource?.repo) {
|
|
9527
|
+
sourceSet.add(skill.externalSource.repo);
|
|
9528
|
+
}
|
|
9529
|
+
if (skill.source?.type === "imported" && skill.externalSource?.url) {
|
|
9530
|
+
sourceSet.add(skill.externalSource.url);
|
|
9531
|
+
}
|
|
9532
|
+
}
|
|
9533
|
+
sources = sourceSet.size;
|
|
9534
|
+
}
|
|
9535
|
+
const indexed = skills.filter(
|
|
9536
|
+
(s) => s.status === "active" && (s.taxonomy || s.source?.type === "imported")
|
|
9537
|
+
).length;
|
|
9538
|
+
return {
|
|
9539
|
+
totalSkills: skills.length,
|
|
9540
|
+
indexedSkills: indexed,
|
|
9541
|
+
rawSkills: skills.filter((s) => s.status === "draft").length,
|
|
9542
|
+
failedSkills: skills.filter((s) => s.status === "deprecated").length,
|
|
9543
|
+
taxonomyNodes,
|
|
9544
|
+
relationships,
|
|
9545
|
+
sources
|
|
9546
|
+
};
|
|
9547
|
+
}
|
|
9548
|
+
if (this.db) {
|
|
9549
|
+
const stats = this.db.getStats?.() || {};
|
|
9550
|
+
return {
|
|
9551
|
+
totalSkills: stats.totalSkills || 0,
|
|
9552
|
+
indexedSkills: stats.indexedSkills || 0,
|
|
9553
|
+
rawSkills: stats.rawSkills || 0,
|
|
9554
|
+
failedSkills: stats.failedSkills || 0,
|
|
9555
|
+
taxonomyNodes: stats.taxonomyNodes || 0,
|
|
9556
|
+
relationships: stats.relationships || 0,
|
|
9557
|
+
sources: stats.sources || 0
|
|
9558
|
+
};
|
|
9559
|
+
}
|
|
9560
|
+
return {
|
|
9561
|
+
totalSkills: 0,
|
|
9562
|
+
indexedSkills: 0,
|
|
9563
|
+
rawSkills: 0,
|
|
9564
|
+
failedSkills: 0,
|
|
9565
|
+
taxonomyNodes: 0,
|
|
9566
|
+
relationships: 0,
|
|
9567
|
+
sources: 0
|
|
9568
|
+
};
|
|
9569
|
+
}
|
|
9570
|
+
/**
|
|
9571
|
+
* Count nodes in taxonomy tree
|
|
9572
|
+
*/
|
|
9573
|
+
countTaxonomyNodes(nodes) {
|
|
9574
|
+
let count = 0;
|
|
9575
|
+
for (const node of nodes) {
|
|
9576
|
+
count++;
|
|
9577
|
+
if (node.children) {
|
|
9578
|
+
count += this.countTaxonomyNodes(node.children);
|
|
9579
|
+
}
|
|
9580
|
+
}
|
|
9581
|
+
return count;
|
|
9582
|
+
}
|
|
9583
|
+
/**
|
|
9584
|
+
* Scrape and index skills directly into SkillBank
|
|
9585
|
+
* This is the streamlined workflow for integrated mode
|
|
9586
|
+
*/
|
|
9587
|
+
async scrapeAndIndex(sources, options) {
|
|
9588
|
+
if (!this.skillBank) {
|
|
9589
|
+
throw new Error(
|
|
9590
|
+
"SkillBank required for scrapeAndIndex. Use integrated mode."
|
|
9591
|
+
);
|
|
9592
|
+
}
|
|
9593
|
+
await this.initialize();
|
|
9594
|
+
const skillsAdded = [];
|
|
9595
|
+
const scraped = await this.scrape(sources, { force: options?.force });
|
|
9596
|
+
let indexed = {
|
|
9597
|
+
indexed: 0,
|
|
9598
|
+
skipped: 0,
|
|
9599
|
+
failed: 0,
|
|
9600
|
+
errors: []
|
|
9601
|
+
};
|
|
9602
|
+
if (options?.autoClassify !== false) {
|
|
9603
|
+
indexed = await this.classify({ all: options?.force });
|
|
9604
|
+
}
|
|
9605
|
+
if (this.db) {
|
|
9606
|
+
const skills = await (this.db.getAllSkills?.() || this.db.listSkills?.()) || [];
|
|
9607
|
+
for (const skill of skills) {
|
|
9608
|
+
if (skill.status !== "indexed" && !options?.force && !options?.importAll) continue;
|
|
9609
|
+
try {
|
|
9610
|
+
const converted = convertIndexerSkill(skill);
|
|
9611
|
+
await this.skillBank.saveSkill(converted.skill);
|
|
9612
|
+
skillsAdded.push(converted.skill.id);
|
|
9613
|
+
} catch (err) {
|
|
9614
|
+
indexed.errors.push(
|
|
9615
|
+
`Failed to import ${skill.slug}: ${err.message}`
|
|
9616
|
+
);
|
|
9617
|
+
}
|
|
9618
|
+
}
|
|
9619
|
+
}
|
|
9620
|
+
let relationships = {
|
|
9621
|
+
detected: 0,
|
|
9622
|
+
skipped: 0,
|
|
9623
|
+
errors: []
|
|
9624
|
+
};
|
|
9625
|
+
if (options?.detectRelationships !== false) {
|
|
9626
|
+
relationships = await this.detectRelationships();
|
|
9627
|
+
}
|
|
9628
|
+
return {
|
|
9629
|
+
scraped,
|
|
9630
|
+
indexed,
|
|
9631
|
+
relationships,
|
|
9632
|
+
skillsAdded
|
|
9633
|
+
};
|
|
9634
|
+
}
|
|
9635
|
+
/**
|
|
9636
|
+
* Import skills from indexer database into SkillBank
|
|
9637
|
+
*/
|
|
9638
|
+
async importFromIndexerDb(options) {
|
|
9639
|
+
if (!this.skillBank) {
|
|
9640
|
+
throw new Error("SkillBank not configured. Use integrated mode.");
|
|
9641
|
+
}
|
|
9642
|
+
await this.initialize();
|
|
9643
|
+
if (!this.db) {
|
|
9644
|
+
throw new Error(
|
|
9645
|
+
"Scraper database not available. Build the scraper module first."
|
|
9646
|
+
);
|
|
9647
|
+
}
|
|
9648
|
+
const result = { imported: 0, failed: 0, skills: [] };
|
|
9649
|
+
try {
|
|
9650
|
+
let skills;
|
|
9651
|
+
if (options?.status) {
|
|
9652
|
+
skills = this.db.getSkillsByStatus?.(options.status) || [];
|
|
9653
|
+
} else {
|
|
9654
|
+
skills = this.db.getAllSkills?.() || [];
|
|
9655
|
+
}
|
|
9656
|
+
if (options?.limit) {
|
|
9657
|
+
skills = skills.slice(0, options.limit);
|
|
9658
|
+
}
|
|
9659
|
+
for (const skill of skills) {
|
|
9660
|
+
try {
|
|
9661
|
+
const converted = convertIndexerSkill(skill);
|
|
9662
|
+
await this.skillBank.saveSkill(converted.skill);
|
|
9663
|
+
result.imported++;
|
|
9664
|
+
result.skills.push(converted.skill);
|
|
9665
|
+
} catch (err) {
|
|
9666
|
+
result.failed++;
|
|
9667
|
+
}
|
|
9668
|
+
}
|
|
9669
|
+
} catch (err) {
|
|
9670
|
+
throw new Error(`Import failed: ${err.message}`);
|
|
9671
|
+
}
|
|
9672
|
+
return result;
|
|
9673
|
+
}
|
|
9674
|
+
/**
|
|
9675
|
+
* Get default skill sources from config
|
|
9676
|
+
*/
|
|
9677
|
+
getDefaultSources() {
|
|
9678
|
+
const configSources = this.globalConfig.indexer.sources;
|
|
9679
|
+
if (configSources.length > 0) {
|
|
9680
|
+
return configSources.map((url) => ({
|
|
9681
|
+
type: "awesome-list",
|
|
9682
|
+
url
|
|
9683
|
+
}));
|
|
9684
|
+
}
|
|
9685
|
+
return [
|
|
9686
|
+
{
|
|
9687
|
+
type: "awesome-list",
|
|
9688
|
+
url: "https://github.com/VoltAgent/awesome-agent-skills"
|
|
9689
|
+
}
|
|
9690
|
+
];
|
|
9691
|
+
}
|
|
9692
|
+
/**
|
|
9693
|
+
* Close database connections
|
|
9694
|
+
*/
|
|
9695
|
+
async close() {
|
|
9696
|
+
if (this.db && typeof this.db.close === "function") {
|
|
9697
|
+
this.db.close();
|
|
9698
|
+
}
|
|
9699
|
+
this.db = void 0;
|
|
9700
|
+
this.initialized = false;
|
|
9701
|
+
}
|
|
9702
|
+
};
|
|
9703
|
+
|
|
8260
9704
|
// src/index.ts
|
|
8261
9705
|
var VERSION = "0.1.0";
|
|
8262
9706
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -8265,11 +9709,13 @@ var VERSION = "0.1.0";
|
|
|
8265
9709
|
AgentsParser,
|
|
8266
9710
|
AgentsSync,
|
|
8267
9711
|
CachedStorageAdapter,
|
|
9712
|
+
CatalogRenderer,
|
|
8268
9713
|
ConflictStore,
|
|
8269
9714
|
DEFAULT_AGENTS_CONFIG,
|
|
8270
9715
|
FederationManager,
|
|
8271
9716
|
GitSyncAdapter,
|
|
8272
9717
|
HookRegistry,
|
|
9718
|
+
IndexerService,
|
|
8273
9719
|
LineageTracker,
|
|
8274
9720
|
LoadoutCompiler,
|
|
8275
9721
|
Materializer,
|
|
@@ -8326,3 +9772,4 @@ var VERSION = "0.1.0";
|
|
|
8326
9772
|
testingProfile,
|
|
8327
9773
|
writeAgentsMd
|
|
8328
9774
|
});
|
|
9775
|
+
//# sourceMappingURL=index.js.map
|