skill-tree 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -25
- package/dist/chunk-F3YEUQAP.mjs +654 -0
- package/dist/chunk-TEUB6DZR.mjs +6453 -0
- package/dist/chunk-TWPEHDW4.mjs +1067 -0
- package/dist/cli/index.js +1455 -810
- package/dist/cli/index.mjs +213 -298
- package/dist/index.d.mts +129 -200
- package/dist/index.d.ts +129 -200
- package/dist/index.js +6137 -6268
- package/dist/index.mjs +17 -15
- package/dist/sqlite-XJRPMNAJ.mjs +6 -0
- package/dist/sync-BSWMMDA6.mjs +14 -0
- package/package.json +1 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
import {
|
|
3
3
|
CachedStorageAdapter,
|
|
4
4
|
GitSyncAdapter,
|
|
5
|
+
Materializer,
|
|
5
6
|
SkillBank,
|
|
6
7
|
SkillGraphServer,
|
|
7
8
|
VERSION,
|
|
8
9
|
createDefaultSyncConfig
|
|
9
|
-
} from "../chunk-
|
|
10
|
-
import "../chunk-
|
|
10
|
+
} from "../chunk-TEUB6DZR.mjs";
|
|
11
|
+
import "../chunk-TWPEHDW4.mjs";
|
|
12
|
+
import "../chunk-F3YEUQAP.mjs";
|
|
11
13
|
|
|
12
14
|
// src/cli/index.ts
|
|
13
|
-
import { Command as
|
|
15
|
+
import { Command as Command38 } from "commander";
|
|
14
16
|
|
|
15
17
|
// src/config/types.ts
|
|
16
18
|
var DEFAULT_CONFIG = {
|
|
@@ -35,6 +37,14 @@ var DEFAULT_CONFIG = {
|
|
|
35
37
|
output_format: "table",
|
|
36
38
|
color: true,
|
|
37
39
|
quiet: false
|
|
40
|
+
},
|
|
41
|
+
materialization: {
|
|
42
|
+
enabled: false,
|
|
43
|
+
mode: "symlink",
|
|
44
|
+
symlink_paths: [],
|
|
45
|
+
agents_md_path: "",
|
|
46
|
+
agents_md_format: "xml",
|
|
47
|
+
debounce_ms: 500
|
|
38
48
|
}
|
|
39
49
|
};
|
|
40
50
|
|
|
@@ -90,8 +100,8 @@ function substituteEnvVarsInObject(obj) {
|
|
|
90
100
|
}
|
|
91
101
|
return obj;
|
|
92
102
|
}
|
|
93
|
-
function setNestedProperty(obj,
|
|
94
|
-
const parts =
|
|
103
|
+
function setNestedProperty(obj, path8, value) {
|
|
104
|
+
const parts = path8.split(".");
|
|
95
105
|
let current = obj;
|
|
96
106
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
97
107
|
const part = parts[i];
|
|
@@ -250,8 +260,8 @@ var ConfigLoader = class {
|
|
|
250
260
|
/**
|
|
251
261
|
* Get a specific config value by path
|
|
252
262
|
*/
|
|
253
|
-
get(
|
|
254
|
-
const parts =
|
|
263
|
+
get(path8) {
|
|
264
|
+
const parts = path8.split(".");
|
|
255
265
|
let current = this.getConfig();
|
|
256
266
|
for (const part of parts) {
|
|
257
267
|
if (current === null || typeof current !== "object") {
|
|
@@ -304,6 +314,20 @@ cli:
|
|
|
304
314
|
output_format: table
|
|
305
315
|
color: true
|
|
306
316
|
quiet: false
|
|
317
|
+
|
|
318
|
+
materialization:
|
|
319
|
+
# Enable automatic materialization to agent-discoverable paths
|
|
320
|
+
enabled: false
|
|
321
|
+
# Mode: 'symlink' (default) or 'copy'
|
|
322
|
+
mode: symlink
|
|
323
|
+
# Directories to expose skills in (e.g. .claude/skills, .agent/skills)
|
|
324
|
+
symlink_paths: []
|
|
325
|
+
# Path to auto-generate AGENTS.md (empty = disabled)
|
|
326
|
+
agents_md_path: ""
|
|
327
|
+
# Format for AGENTS.md: xml, markdown, json
|
|
328
|
+
agents_md_format: xml
|
|
329
|
+
# Debounce interval in ms for batch updates
|
|
330
|
+
debounce_ms: 500
|
|
307
331
|
`;
|
|
308
332
|
fs.writeFileSync(expandPath(this.configPath), configContent);
|
|
309
333
|
}
|
|
@@ -373,6 +397,19 @@ function ensureDir(dir) {
|
|
|
373
397
|
}
|
|
374
398
|
|
|
375
399
|
// src/cli/utils/skillbank.ts
|
|
400
|
+
function getMaterializationConfig() {
|
|
401
|
+
const config2 = loadConfig();
|
|
402
|
+
const mat = config2.materialization;
|
|
403
|
+
if (!mat?.enabled) return void 0;
|
|
404
|
+
return {
|
|
405
|
+
enabled: true,
|
|
406
|
+
mode: mat.mode ?? "symlink",
|
|
407
|
+
symlinkPaths: mat.symlink_paths?.length ? mat.symlink_paths : void 0,
|
|
408
|
+
agentsMdPath: mat.agents_md_path || void 0,
|
|
409
|
+
agentsMdFormat: mat.agents_md_format ?? "xml",
|
|
410
|
+
debounceMs: mat.debounce_ms ?? 500
|
|
411
|
+
};
|
|
412
|
+
}
|
|
376
413
|
async function createSkillBankFromOptions(options) {
|
|
377
414
|
const skillPath = resolveSkillPath(options.path);
|
|
378
415
|
ensureDir(skillPath);
|
|
@@ -380,7 +417,8 @@ async function createSkillBankFromOptions(options) {
|
|
|
380
417
|
storage: {
|
|
381
418
|
basePath: skillPath,
|
|
382
419
|
openSkillsCompatible: true
|
|
383
|
-
}
|
|
420
|
+
},
|
|
421
|
+
materialization: getMaterializationConfig()
|
|
384
422
|
});
|
|
385
423
|
await skillBank.initialize();
|
|
386
424
|
return skillBank;
|
|
@@ -438,33 +476,10 @@ function formatSkillDetail(skill) {
|
|
|
438
476
|
lines.push(chalk.dim("\u2500".repeat(50)));
|
|
439
477
|
lines.push(skill.description);
|
|
440
478
|
lines.push("");
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
lines.push(skill.problem);
|
|
444
|
-
lines.push("");
|
|
445
|
-
if (skill.triggerConditions.length > 0) {
|
|
446
|
-
lines.push(chalk.dim("Trigger Conditions"));
|
|
447
|
-
lines.push(chalk.dim("\u2500".repeat(50)));
|
|
448
|
-
for (const trigger of skill.triggerConditions) {
|
|
449
|
-
const desc = trigger.description ? ` - ${trigger.description}` : "";
|
|
450
|
-
lines.push(` ${chalk.cyan(trigger.type)}: ${trigger.value}${chalk.dim(desc)}`);
|
|
451
|
-
}
|
|
452
|
-
lines.push("");
|
|
453
|
-
}
|
|
454
|
-
lines.push(chalk.dim("Solution"));
|
|
455
|
-
lines.push(chalk.dim("\u2500".repeat(50)));
|
|
456
|
-
lines.push(skill.solution);
|
|
457
|
-
lines.push("");
|
|
458
|
-
if (skill.verification) {
|
|
459
|
-
lines.push(chalk.dim("Verification"));
|
|
479
|
+
if (skill.instructions) {
|
|
480
|
+
lines.push(chalk.dim("Instructions"));
|
|
460
481
|
lines.push(chalk.dim("\u2500".repeat(50)));
|
|
461
|
-
lines.push(skill.
|
|
462
|
-
lines.push("");
|
|
463
|
-
}
|
|
464
|
-
if (skill.notes) {
|
|
465
|
-
lines.push(chalk.dim("Notes"));
|
|
466
|
-
lines.push(chalk.dim("\u2500".repeat(50)));
|
|
467
|
-
lines.push(skill.notes);
|
|
482
|
+
lines.push(skill.instructions);
|
|
468
483
|
}
|
|
469
484
|
return lines.join("\n");
|
|
470
485
|
}
|
|
@@ -859,225 +874,13 @@ import { Command as Command13 } from "commander";
|
|
|
859
874
|
import * as fs4 from "fs";
|
|
860
875
|
|
|
861
876
|
// src/import/converter.ts
|
|
862
|
-
var DEFAULT_SECTION_PATTERNS = {
|
|
863
|
-
problem: [
|
|
864
|
-
/^#+\s*(?:problem|issue|what\s+problem|the\s+problem)/i,
|
|
865
|
-
/^#+\s*(?:why|motivation|background)/i
|
|
866
|
-
],
|
|
867
|
-
solution: [
|
|
868
|
-
/^#+\s*(?:solution|how\s+to|implementation|approach)/i,
|
|
869
|
-
/^#+\s*(?:usage|instructions|steps)/i
|
|
870
|
-
],
|
|
871
|
-
verification: [
|
|
872
|
-
/^#+\s*(?:verification|verify|testing|test|validate)/i,
|
|
873
|
-
/^#+\s*(?:how\s+to\s+verify|checking)/i
|
|
874
|
-
],
|
|
875
|
-
examples: [
|
|
876
|
-
/^#+\s*(?:examples?|sample|demo)/i,
|
|
877
|
-
/^#+\s*(?:use\s+cases?|scenarios?)/i
|
|
878
|
-
],
|
|
879
|
-
notes: [
|
|
880
|
-
/^#+\s*(?:notes?|caveats?|warnings?|tips?)/i,
|
|
881
|
-
/^#+\s*(?:additional|edge\s+cases?|considerations?)/i
|
|
882
|
-
],
|
|
883
|
-
triggers: [
|
|
884
|
-
/^#+\s*(?:triggers?|when\s+to\s+use|conditions?)/i,
|
|
885
|
-
/^#+\s*(?:activation|applies\s+when)/i
|
|
886
|
-
]
|
|
887
|
-
};
|
|
888
|
-
function parseMarkdownSections(content) {
|
|
889
|
-
const lines = content.split("\n");
|
|
890
|
-
const sections = {
|
|
891
|
-
remainingContent: ""
|
|
892
|
-
};
|
|
893
|
-
let currentSection = "remaining";
|
|
894
|
-
let currentContent = [];
|
|
895
|
-
const flushSection = () => {
|
|
896
|
-
const text = currentContent.join("\n").trim();
|
|
897
|
-
if (!text) return;
|
|
898
|
-
switch (currentSection) {
|
|
899
|
-
case "problem":
|
|
900
|
-
sections.problem = text;
|
|
901
|
-
break;
|
|
902
|
-
case "solution":
|
|
903
|
-
sections.solution = text;
|
|
904
|
-
break;
|
|
905
|
-
case "verification":
|
|
906
|
-
sections.verification = text;
|
|
907
|
-
break;
|
|
908
|
-
case "examples":
|
|
909
|
-
sections.examples = parseExamples(text);
|
|
910
|
-
break;
|
|
911
|
-
case "notes":
|
|
912
|
-
sections.notes = text;
|
|
913
|
-
break;
|
|
914
|
-
case "triggers":
|
|
915
|
-
sections.triggers = parseTriggerList(text);
|
|
916
|
-
break;
|
|
917
|
-
case "remaining":
|
|
918
|
-
sections.remainingContent += (sections.remainingContent ? "\n\n" : "") + text;
|
|
919
|
-
break;
|
|
920
|
-
}
|
|
921
|
-
currentContent = [];
|
|
922
|
-
};
|
|
923
|
-
for (const line of lines) {
|
|
924
|
-
let matchedSection = null;
|
|
925
|
-
for (const [section, sectionPatterns] of Object.entries(DEFAULT_SECTION_PATTERNS)) {
|
|
926
|
-
for (const pattern of sectionPatterns) {
|
|
927
|
-
if (pattern.test(line)) {
|
|
928
|
-
matchedSection = section;
|
|
929
|
-
break;
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
if (matchedSection) break;
|
|
933
|
-
}
|
|
934
|
-
if (matchedSection) {
|
|
935
|
-
flushSection();
|
|
936
|
-
currentSection = matchedSection;
|
|
937
|
-
} else {
|
|
938
|
-
currentContent.push(line);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
flushSection();
|
|
942
|
-
return sections;
|
|
943
|
-
}
|
|
944
|
-
function parseExamples(text) {
|
|
945
|
-
const examples = [];
|
|
946
|
-
const exampleBlocks = text.split(/^###\s+/m).filter(Boolean);
|
|
947
|
-
for (const block of exampleBlocks) {
|
|
948
|
-
const lines = block.split("\n");
|
|
949
|
-
const scenario = lines[0]?.trim() || "Example";
|
|
950
|
-
const content = lines.slice(1).join("\n");
|
|
951
|
-
const beforeMatch = content.match(/(?:before|input|given)[:\s]*\n?([\s\S]*?)(?=(?:after|output|then|result)|$)/i);
|
|
952
|
-
const afterMatch = content.match(/(?:after|output|then|result)[:\s]*\n?([\s\S]*?)$/i);
|
|
953
|
-
if (beforeMatch || afterMatch) {
|
|
954
|
-
examples.push({
|
|
955
|
-
scenario,
|
|
956
|
-
before: beforeMatch?.[1]?.trim() || "",
|
|
957
|
-
after: afterMatch?.[1]?.trim() || content.trim()
|
|
958
|
-
});
|
|
959
|
-
} else {
|
|
960
|
-
examples.push({
|
|
961
|
-
scenario,
|
|
962
|
-
before: "",
|
|
963
|
-
after: content.trim()
|
|
964
|
-
});
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
if (examples.length === 0 && text.trim()) {
|
|
968
|
-
examples.push({
|
|
969
|
-
scenario: "Example usage",
|
|
970
|
-
before: "",
|
|
971
|
-
after: text.trim()
|
|
972
|
-
});
|
|
973
|
-
}
|
|
974
|
-
return examples;
|
|
975
|
-
}
|
|
976
|
-
function parseTriggerList(text) {
|
|
977
|
-
const triggers = [];
|
|
978
|
-
const listItems = text.match(/^[\s]*[-*•]\s*(.+)$/gm) || text.match(/^[\s]*\d+\.\s*(.+)$/gm);
|
|
979
|
-
if (listItems) {
|
|
980
|
-
for (const item of listItems) {
|
|
981
|
-
const cleaned = item.replace(/^[\s]*[-*•\d.]+\s*/, "").trim();
|
|
982
|
-
if (cleaned) triggers.push(cleaned);
|
|
983
|
-
}
|
|
984
|
-
} else {
|
|
985
|
-
triggers.push(...text.split("\n").map((t) => t.trim()).filter(Boolean));
|
|
986
|
-
}
|
|
987
|
-
return triggers;
|
|
988
|
-
}
|
|
989
|
-
function inferTriggerType(text) {
|
|
990
|
-
const lower = text.toLowerCase();
|
|
991
|
-
if (lower.includes("error") || lower.includes("exception") || lower.includes("fail")) {
|
|
992
|
-
return { type: "error", value: text, description: "Triggered by error conditions" };
|
|
993
|
-
}
|
|
994
|
-
if (lower.includes("pattern") || lower.includes("regex") || lower.includes("match")) {
|
|
995
|
-
return { type: "pattern", value: text };
|
|
996
|
-
}
|
|
997
|
-
if (lower.includes("when") || lower.includes("context") || lower.includes("if ")) {
|
|
998
|
-
return { type: "context", value: text };
|
|
999
|
-
}
|
|
1000
|
-
return { type: "keyword", value: text };
|
|
1001
|
-
}
|
|
1002
|
-
function extractKeywordsFromDescription(description) {
|
|
1003
|
-
const keywords = [];
|
|
1004
|
-
const techPatterns = [
|
|
1005
|
-
/\b(react|vue|angular|svelte|next\.?js|nuxt)\b/gi,
|
|
1006
|
-
/\b(node\.?js|deno|bun|python|rust|go|java|typescript|javascript)\b/gi,
|
|
1007
|
-
/\b(docker|kubernetes|k8s|aws|gcp|azure)\b/gi,
|
|
1008
|
-
/\b(git|github|gitlab|npm|yarn|pnpm)\b/gi,
|
|
1009
|
-
/\b(sql|postgres|mysql|mongodb|redis)\b/gi,
|
|
1010
|
-
/\b(api|rest|graphql|grpc)\b/gi,
|
|
1011
|
-
/\b(test|testing|jest|vitest|pytest)\b/gi
|
|
1012
|
-
];
|
|
1013
|
-
for (const pattern of techPatterns) {
|
|
1014
|
-
const matches = description.match(pattern);
|
|
1015
|
-
if (matches) {
|
|
1016
|
-
keywords.push(...matches.map((m) => m.toLowerCase()));
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
return [...new Set(keywords)];
|
|
1020
|
-
}
|
|
1021
|
-
function extractTriggerConditions(skill, sections) {
|
|
1022
|
-
const conditions = [];
|
|
1023
|
-
if (sections?.triggers) {
|
|
1024
|
-
for (const trigger of sections.triggers) {
|
|
1025
|
-
conditions.push(inferTriggerType(trigger));
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
const keywords = extractKeywordsFromDescription(skill.description);
|
|
1029
|
-
for (const keyword of keywords) {
|
|
1030
|
-
if (!conditions.some((c) => c.value.toLowerCase() === keyword.toLowerCase())) {
|
|
1031
|
-
conditions.push({
|
|
1032
|
-
type: "keyword",
|
|
1033
|
-
value: keyword,
|
|
1034
|
-
description: "Keyword from description"
|
|
1035
|
-
});
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
if (skill.tags) {
|
|
1039
|
-
for (const tag of skill.tags.slice(0, 5)) {
|
|
1040
|
-
if (!conditions.some((c) => c.value.toLowerCase() === tag.toLowerCase())) {
|
|
1041
|
-
conditions.push({
|
|
1042
|
-
type: "keyword",
|
|
1043
|
-
value: tag,
|
|
1044
|
-
description: "Tag from classification"
|
|
1045
|
-
});
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
if (conditions.length === 0) {
|
|
1050
|
-
conditions.push({
|
|
1051
|
-
type: "context",
|
|
1052
|
-
value: skill.description.substring(0, 100),
|
|
1053
|
-
description: "Generated from skill description"
|
|
1054
|
-
});
|
|
1055
|
-
}
|
|
1056
|
-
return conditions;
|
|
1057
|
-
}
|
|
1058
877
|
function convertIndexerSkill(indexerSkill) {
|
|
1059
878
|
const warnings = [];
|
|
1060
|
-
const
|
|
1061
|
-
const hasStructuredContent =
|
|
879
|
+
const instructions = indexerSkill.content || "";
|
|
880
|
+
const hasStructuredContent = instructions.trim().length > 0;
|
|
1062
881
|
if (!hasStructuredContent) {
|
|
1063
|
-
warnings.push("No
|
|
1064
|
-
}
|
|
1065
|
-
const triggerConditions = extractTriggerConditions(indexerSkill, sections);
|
|
1066
|
-
let examples = sections.examples || [];
|
|
1067
|
-
if (examples.length === 0) {
|
|
1068
|
-
examples = [{
|
|
1069
|
-
scenario: "Usage example",
|
|
1070
|
-
before: "",
|
|
1071
|
-
after: indexerSkill.content.substring(0, 500)
|
|
1072
|
-
}];
|
|
1073
|
-
warnings.push("No examples found, created placeholder");
|
|
1074
|
-
}
|
|
1075
|
-
const problem = sections.problem || indexerSkill.description;
|
|
1076
|
-
const solution = sections.solution || indexerSkill.content;
|
|
1077
|
-
const verification = sections.verification || "Verify the skill output meets expectations";
|
|
1078
|
-
if (!sections.problem) warnings.push("No problem section found, using description");
|
|
1079
|
-
if (!sections.solution) warnings.push("No solution section found, using full content");
|
|
1080
|
-
if (!sections.verification) warnings.push("No verification section found, using default");
|
|
882
|
+
warnings.push("No content found, instructions will be empty");
|
|
883
|
+
}
|
|
1081
884
|
const tags = [...indexerSkill.tags || []];
|
|
1082
885
|
if (indexerSkill.sourceRepo) {
|
|
1083
886
|
const repoName = indexerSkill.sourceRepo.split("/").pop();
|
|
@@ -1086,26 +889,12 @@ function convertIndexerSkill(indexerSkill) {
|
|
|
1086
889
|
}
|
|
1087
890
|
}
|
|
1088
891
|
const status = indexerSkill.status === "indexed" ? "active" : indexerSkill.status === "raw" ? "draft" : "draft";
|
|
1089
|
-
let notes = sections.notes || void 0;
|
|
1090
|
-
if (indexerSkill.classificationReasoning) {
|
|
1091
|
-
const classificationNote = `
|
|
1092
|
-
|
|
1093
|
-
---
|
|
1094
|
-
Classification: ${indexerSkill.primaryPath?.join(" > ") || "uncategorized"}
|
|
1095
|
-
Reasoning: ${indexerSkill.classificationReasoning}`;
|
|
1096
|
-
notes = (notes || "") + classificationNote;
|
|
1097
|
-
}
|
|
1098
892
|
const skill = {
|
|
1099
893
|
id: indexerSkill.slug,
|
|
1100
894
|
name: indexerSkill.displayName || indexerSkill.name,
|
|
1101
895
|
version: indexerSkill.version,
|
|
1102
896
|
description: indexerSkill.description,
|
|
1103
|
-
|
|
1104
|
-
triggerConditions,
|
|
1105
|
-
solution,
|
|
1106
|
-
verification,
|
|
1107
|
-
examples,
|
|
1108
|
-
notes,
|
|
897
|
+
instructions,
|
|
1109
898
|
author: indexerSkill.author,
|
|
1110
899
|
tags,
|
|
1111
900
|
createdAt: new Date(indexerSkill.scrapedAt),
|
|
@@ -1176,7 +965,7 @@ function parseIndexerExport(content) {
|
|
|
1176
965
|
function isSkillTreeSkill(obj) {
|
|
1177
966
|
if (typeof obj !== "object" || obj === null) return false;
|
|
1178
967
|
const skill = obj;
|
|
1179
|
-
return typeof skill.id === "string" && typeof skill.name === "string" && typeof skill.version === "string" && typeof skill.
|
|
968
|
+
return typeof skill.id === "string" && typeof skill.name === "string" && typeof skill.version === "string" && typeof skill.instructions === "string" && typeof skill.metrics === "object";
|
|
1180
969
|
}
|
|
1181
970
|
function isIndexerSkill(obj) {
|
|
1182
971
|
if (typeof obj !== "object" || obj === null) return false;
|
|
@@ -1590,7 +1379,7 @@ var IndexerService = class {
|
|
|
1590
1379
|
detectSkillRelationships(skill, allSkills) {
|
|
1591
1380
|
const relationships = [];
|
|
1592
1381
|
const skillId = skill.id || skill.slug;
|
|
1593
|
-
const skillContent = `${skill.name} ${skill.description} ${skill.
|
|
1382
|
+
const skillContent = `${skill.name} ${skill.description} ${skill.instructions || ""} ${skill.content || ""}`.toLowerCase();
|
|
1594
1383
|
for (const other of allSkills) {
|
|
1595
1384
|
const otherId = other.id || other.slug;
|
|
1596
1385
|
if (otherId === skillId) continue;
|
|
@@ -3439,18 +3228,142 @@ function resolveCommand() {
|
|
|
3439
3228
|
});
|
|
3440
3229
|
}
|
|
3441
3230
|
|
|
3231
|
+
// src/cli/commands/read.ts
|
|
3232
|
+
import { Command as Command23 } from "commander";
|
|
3233
|
+
import * as path6 from "path";
|
|
3234
|
+
function serializeSkillMd(skill) {
|
|
3235
|
+
const lines = [];
|
|
3236
|
+
lines.push("---");
|
|
3237
|
+
lines.push(`name: ${skill.name}`);
|
|
3238
|
+
lines.push(`description: |`);
|
|
3239
|
+
lines.push(` ${skill.description}`);
|
|
3240
|
+
lines.push(`version: ${skill.version}`);
|
|
3241
|
+
if (skill.tags.length > 0) {
|
|
3242
|
+
lines.push(`tags:`);
|
|
3243
|
+
for (const tag of skill.tags) {
|
|
3244
|
+
lines.push(` - ${tag}`);
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
lines.push("---");
|
|
3248
|
+
lines.push("");
|
|
3249
|
+
lines.push(skill.instructions);
|
|
3250
|
+
return lines.join("\n");
|
|
3251
|
+
}
|
|
3252
|
+
async function resolveSkill(bank, query) {
|
|
3253
|
+
const exact = await bank.getSkill(query);
|
|
3254
|
+
if (exact) return { skill: exact, fuzzy: false };
|
|
3255
|
+
const results = await bank.searchSkills(query);
|
|
3256
|
+
if (results.length > 0) {
|
|
3257
|
+
return { skill: results[0], fuzzy: true };
|
|
3258
|
+
}
|
|
3259
|
+
return null;
|
|
3260
|
+
}
|
|
3261
|
+
var readCommand = new Command23("read").description("Read skill(s) to stdout (for agent activation)").argument("<names>", "Skill name(s), comma-separated").action(async (names, _options, command) => {
|
|
3262
|
+
const globalOpts = command.optsWithGlobals();
|
|
3263
|
+
try {
|
|
3264
|
+
const skillBank = await createSkillBankFromOptions(globalOpts);
|
|
3265
|
+
const basePath = getSkillPath(globalOpts);
|
|
3266
|
+
const nameList = names.split(",").map((n) => n.trim()).filter(Boolean);
|
|
3267
|
+
for (const name of nameList) {
|
|
3268
|
+
const result = await resolveSkill(skillBank, name);
|
|
3269
|
+
if (!result) {
|
|
3270
|
+
printError(`Skill not found: ${name}`);
|
|
3271
|
+
process.exit(1);
|
|
3272
|
+
}
|
|
3273
|
+
const { skill, fuzzy } = result;
|
|
3274
|
+
if (fuzzy) {
|
|
3275
|
+
console.log(`Reading: ${skill.name} (matched: ${skill.id})`);
|
|
3276
|
+
} else {
|
|
3277
|
+
console.log(`Reading: ${skill.name}`);
|
|
3278
|
+
}
|
|
3279
|
+
const skillDir = path6.join(basePath, ".skilltree", "skills", skill.id);
|
|
3280
|
+
console.log(`Base directory: ${skillDir}`);
|
|
3281
|
+
console.log("");
|
|
3282
|
+
console.log(serializeSkillMd(skill));
|
|
3283
|
+
console.log("");
|
|
3284
|
+
console.log(`Skill read: ${skill.name}`);
|
|
3285
|
+
if (nameList.length > 1 && name !== nameList[nameList.length - 1]) {
|
|
3286
|
+
console.log("---");
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
} catch (error) {
|
|
3290
|
+
printError(error.message);
|
|
3291
|
+
process.exit(1);
|
|
3292
|
+
}
|
|
3293
|
+
});
|
|
3294
|
+
|
|
3295
|
+
// src/cli/commands/materialize.ts
|
|
3296
|
+
import { Command as Command24 } from "commander";
|
|
3297
|
+
var materializeCommand = new Command24("materialize").description("Materialize skills to agent-discoverable paths").option("--paths <dirs>", "Comma-separated target directories (e.g. .claude/skills,.agent/skills)").option("--agents-md <path>", "Path to write AGENTS.md").option("--format <format>", "AGENTS.md format: xml, markdown, json", "xml").option("--mode <mode>", "Materialization mode: symlink or copy", "symlink").option("-w, --watch", "Watch for changes and re-materialize").action(async (options, command) => {
|
|
3298
|
+
const globalOpts = command.optsWithGlobals();
|
|
3299
|
+
try {
|
|
3300
|
+
if (!options.paths && !options.agentsMd) {
|
|
3301
|
+
printError("Specify at least --paths or --agents-md");
|
|
3302
|
+
console.log("");
|
|
3303
|
+
printInfo("Examples:");
|
|
3304
|
+
console.log(" skill-tree materialize --paths .claude/skills,.agent/skills");
|
|
3305
|
+
console.log(" skill-tree materialize --agents-md ./AGENTS.md");
|
|
3306
|
+
console.log(" skill-tree materialize --paths .claude/skills --agents-md ./AGENTS.md --mode copy");
|
|
3307
|
+
process.exit(1);
|
|
3308
|
+
}
|
|
3309
|
+
const skillBank = await createSkillBankFromOptions(globalOpts);
|
|
3310
|
+
const storage = skillBank.getStorage();
|
|
3311
|
+
const basePath = getSkillPath(globalOpts);
|
|
3312
|
+
const symlinkPaths = options.paths ? options.paths.split(",").map((p) => p.trim()).filter(Boolean) : [];
|
|
3313
|
+
const config2 = {
|
|
3314
|
+
enabled: true,
|
|
3315
|
+
mode: options.mode ?? "symlink",
|
|
3316
|
+
symlinkPaths,
|
|
3317
|
+
agentsMdPath: options.agentsMd,
|
|
3318
|
+
agentsMdFormat: options.format ?? "xml",
|
|
3319
|
+
debounceMs: 500
|
|
3320
|
+
};
|
|
3321
|
+
const materializer = new Materializer(storage, basePath, config2);
|
|
3322
|
+
await materializer.initialize();
|
|
3323
|
+
const skills = await storage.listSkills();
|
|
3324
|
+
printSuccess(`Materialized ${skills.length} skill(s)`);
|
|
3325
|
+
if (symlinkPaths.length > 0) {
|
|
3326
|
+
const modeLabel = config2.mode === "copy" ? "Copied" : "Symlinked";
|
|
3327
|
+
for (const p of symlinkPaths) {
|
|
3328
|
+
printInfo(`${modeLabel} to: ${p}`);
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
if (options.agentsMd) {
|
|
3332
|
+
printInfo(`AGENTS.md written to: ${options.agentsMd}`);
|
|
3333
|
+
}
|
|
3334
|
+
if (options.watch) {
|
|
3335
|
+
printInfo("Watching for changes... (Ctrl+C to stop)");
|
|
3336
|
+
printWarning(
|
|
3337
|
+
"Note: Only detects changes to .skilltree/skills/. Changes via the SkillBank API are handled automatically when materialization is enabled in config."
|
|
3338
|
+
);
|
|
3339
|
+
materializer.watch();
|
|
3340
|
+
await new Promise((resolve3) => {
|
|
3341
|
+
process.on("SIGINT", () => {
|
|
3342
|
+
materializer.shutdown();
|
|
3343
|
+
resolve3();
|
|
3344
|
+
});
|
|
3345
|
+
});
|
|
3346
|
+
} else {
|
|
3347
|
+
materializer.shutdown();
|
|
3348
|
+
}
|
|
3349
|
+
} catch (error) {
|
|
3350
|
+
printError(error.message);
|
|
3351
|
+
process.exit(1);
|
|
3352
|
+
}
|
|
3353
|
+
});
|
|
3354
|
+
|
|
3442
3355
|
// src/cli/commands/loadout/index.ts
|
|
3443
|
-
import { Command as
|
|
3356
|
+
import { Command as Command37 } from "commander";
|
|
3444
3357
|
|
|
3445
3358
|
// src/cli/commands/loadout/list.ts
|
|
3446
|
-
import { Command as
|
|
3359
|
+
import { Command as Command25 } from "commander";
|
|
3447
3360
|
|
|
3448
3361
|
// src/serving/state-persistence.ts
|
|
3449
3362
|
import * as fs9 from "fs";
|
|
3450
|
-
import * as
|
|
3363
|
+
import * as path7 from "path";
|
|
3451
3364
|
var STATE_FILENAME = ".loadout-state.json";
|
|
3452
3365
|
function loadState(skillPath) {
|
|
3453
|
-
const filePath =
|
|
3366
|
+
const filePath = path7.join(skillPath, STATE_FILENAME);
|
|
3454
3367
|
try {
|
|
3455
3368
|
const raw = fs9.readFileSync(filePath, "utf-8");
|
|
3456
3369
|
const data = JSON.parse(raw);
|
|
@@ -3466,7 +3379,7 @@ function loadState(skillPath) {
|
|
|
3466
3379
|
}
|
|
3467
3380
|
}
|
|
3468
3381
|
function saveState(skillPath, state) {
|
|
3469
|
-
const filePath =
|
|
3382
|
+
const filePath = path7.join(skillPath, STATE_FILENAME);
|
|
3470
3383
|
const data = {
|
|
3471
3384
|
available: Array.from(state.available.entries()),
|
|
3472
3385
|
expanded: Array.from(state.expanded),
|
|
@@ -3477,7 +3390,7 @@ function saveState(skillPath, state) {
|
|
|
3477
3390
|
fs9.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
3478
3391
|
}
|
|
3479
3392
|
function clearState(skillPath) {
|
|
3480
|
-
const filePath =
|
|
3393
|
+
const filePath = path7.join(skillPath, STATE_FILENAME);
|
|
3481
3394
|
try {
|
|
3482
3395
|
fs9.unlinkSync(filePath);
|
|
3483
3396
|
return true;
|
|
@@ -3509,7 +3422,7 @@ async function createLoadoutServer(options) {
|
|
|
3509
3422
|
}
|
|
3510
3423
|
|
|
3511
3424
|
// src/cli/commands/loadout/list.ts
|
|
3512
|
-
var listSubcommand = new
|
|
3425
|
+
var listSubcommand = new Command25("list").description("List skills in the current loadout").option("-f, --filter <filter>", "Filter: all, expanded, available, pending", "all").action(async (options, command) => {
|
|
3513
3426
|
const globalOpts = command.optsWithGlobals();
|
|
3514
3427
|
try {
|
|
3515
3428
|
const { server } = await createLoadoutServer(globalOpts);
|
|
@@ -3553,8 +3466,8 @@ var listSubcommand = new Command23("list").description("List skills in the curre
|
|
|
3553
3466
|
});
|
|
3554
3467
|
|
|
3555
3468
|
// src/cli/commands/loadout/search.ts
|
|
3556
|
-
import { Command as
|
|
3557
|
-
var searchSubcommand = new
|
|
3469
|
+
import { Command as Command26 } from "commander";
|
|
3470
|
+
var searchSubcommand = new Command26("search").description("Search for skills to add to the loadout").argument("<query>", "Search query").option("-l, --limit <n>", "Maximum results", "10").action(async (query, options, command) => {
|
|
3558
3471
|
const globalOpts = command.optsWithGlobals();
|
|
3559
3472
|
try {
|
|
3560
3473
|
const { server } = await createLoadoutServer(globalOpts);
|
|
@@ -3584,8 +3497,8 @@ var searchSubcommand = new Command24("search").description("Search for skills to
|
|
|
3584
3497
|
});
|
|
3585
3498
|
|
|
3586
3499
|
// src/cli/commands/loadout/add.ts
|
|
3587
|
-
import { Command as
|
|
3588
|
-
var addSubcommand = new
|
|
3500
|
+
import { Command as Command27 } from "commander";
|
|
3501
|
+
var addSubcommand = new Command27("add").description("Add skills to the loadout").argument("<ids...>", "Skill IDs to add").action(async (ids, _options, command) => {
|
|
3589
3502
|
const globalOpts = command.optsWithGlobals();
|
|
3590
3503
|
try {
|
|
3591
3504
|
const { server, save } = await createLoadoutServer(globalOpts);
|
|
@@ -3603,8 +3516,8 @@ var addSubcommand = new Command25("add").description("Add skills to the loadout"
|
|
|
3603
3516
|
});
|
|
3604
3517
|
|
|
3605
3518
|
// src/cli/commands/loadout/remove.ts
|
|
3606
|
-
import { Command as
|
|
3607
|
-
var removeSubcommand = new
|
|
3519
|
+
import { Command as Command28 } from "commander";
|
|
3520
|
+
var removeSubcommand = new Command28("remove").description("Remove skills from the loadout").argument("<ids...>", "Skill IDs to remove").action(async (ids, _options, command) => {
|
|
3608
3521
|
const globalOpts = command.optsWithGlobals();
|
|
3609
3522
|
try {
|
|
3610
3523
|
const { server, save } = await createLoadoutServer(globalOpts);
|
|
@@ -3622,8 +3535,8 @@ var removeSubcommand = new Command26("remove").description("Remove skills from t
|
|
|
3622
3535
|
});
|
|
3623
3536
|
|
|
3624
3537
|
// src/cli/commands/loadout/profile.ts
|
|
3625
|
-
import { Command as
|
|
3626
|
-
var profileSubcommand = new
|
|
3538
|
+
import { Command as Command29 } from "commander";
|
|
3539
|
+
var profileSubcommand = new Command29("profile").description("Switch to a named skill profile").argument("[name]", "Profile name (e.g. debugging, security, code-review)").option("--list", "List available profiles").action(async (name, options, command) => {
|
|
3627
3540
|
const globalOpts = command.optsWithGlobals();
|
|
3628
3541
|
try {
|
|
3629
3542
|
const { server, save } = await createLoadoutServer(globalOpts);
|
|
@@ -3654,8 +3567,8 @@ var profileSubcommand = new Command27("profile").description("Switch to a named
|
|
|
3654
3567
|
});
|
|
3655
3568
|
|
|
3656
3569
|
// src/cli/commands/loadout/set.ts
|
|
3657
|
-
import { Command as
|
|
3658
|
-
var setSubcommand = new
|
|
3570
|
+
import { Command as Command30 } from "commander";
|
|
3571
|
+
var setSubcommand = new Command30("set").description("Set loadout from criteria (tags, task description, etc.)").option("--tags <tags>", "Comma-separated tags to match").option("--task <description>", "Task description for semantic matching").option("--max-skills <n>", "Maximum number of skills").action(async (options, command) => {
|
|
3659
3572
|
const globalOpts = command.optsWithGlobals();
|
|
3660
3573
|
try {
|
|
3661
3574
|
const { server, save } = await createLoadoutServer(globalOpts);
|
|
@@ -3684,8 +3597,8 @@ var setSubcommand = new Command28("set").description("Set loadout from criteria
|
|
|
3684
3597
|
});
|
|
3685
3598
|
|
|
3686
3599
|
// src/cli/commands/loadout/expand.ts
|
|
3687
|
-
import { Command as
|
|
3688
|
-
var expandSubcommand = new
|
|
3600
|
+
import { Command as Command31 } from "commander";
|
|
3601
|
+
var expandSubcommand = new Command31("expand").description("Expand a skill to see its full content").argument("<id>", "Skill ID to expand").action(async (id, _options, command) => {
|
|
3689
3602
|
const globalOpts = command.optsWithGlobals();
|
|
3690
3603
|
try {
|
|
3691
3604
|
const { server, save } = await createLoadoutServer(globalOpts);
|
|
@@ -3710,8 +3623,8 @@ var expandSubcommand = new Command29("expand").description("Expand a skill to se
|
|
|
3710
3623
|
});
|
|
3711
3624
|
|
|
3712
3625
|
// src/cli/commands/loadout/collapse.ts
|
|
3713
|
-
import { Command as
|
|
3714
|
-
var collapseSubcommand = new
|
|
3626
|
+
import { Command as Command32 } from "commander";
|
|
3627
|
+
var collapseSubcommand = new Command32("collapse").description("Collapse an expanded skill back to summary").argument("<id>", "Skill ID to collapse").action(async (id, _options, command) => {
|
|
3715
3628
|
const globalOpts = command.optsWithGlobals();
|
|
3716
3629
|
try {
|
|
3717
3630
|
const { server, save } = await createLoadoutServer(globalOpts);
|
|
@@ -3733,8 +3646,8 @@ var collapseSubcommand = new Command30("collapse").description("Collapse an expa
|
|
|
3733
3646
|
});
|
|
3734
3647
|
|
|
3735
3648
|
// src/cli/commands/loadout/use.ts
|
|
3736
|
-
import { Command as
|
|
3737
|
-
var useSubcommand = new
|
|
3649
|
+
import { Command as Command33 } from "commander";
|
|
3650
|
+
var useSubcommand = new Command33("use").description("Mark a skill as being used (auto-expands and records usage)").argument("<id>", "Skill ID to use").action(async (id, _options, command) => {
|
|
3738
3651
|
const globalOpts = command.optsWithGlobals();
|
|
3739
3652
|
try {
|
|
3740
3653
|
const { server, save } = await createLoadoutServer(globalOpts);
|
|
@@ -3760,8 +3673,8 @@ var useSubcommand = new Command31("use").description("Mark a skill as being used
|
|
|
3760
3673
|
});
|
|
3761
3674
|
|
|
3762
3675
|
// src/cli/commands/loadout/get.ts
|
|
3763
|
-
import { Command as
|
|
3764
|
-
var getSubcommand = new
|
|
3676
|
+
import { Command as Command34 } from "commander";
|
|
3677
|
+
var getSubcommand = new Command34("get").description("Get details about a skill in the loadout").argument("<id>", "Skill ID").action(async (id, _options, command) => {
|
|
3765
3678
|
const globalOpts = command.optsWithGlobals();
|
|
3766
3679
|
try {
|
|
3767
3680
|
const { server } = await createLoadoutServer(globalOpts);
|
|
@@ -3783,8 +3696,8 @@ var getSubcommand = new Command32("get").description("Get details about a skill
|
|
|
3783
3696
|
});
|
|
3784
3697
|
|
|
3785
3698
|
// src/cli/commands/loadout/render.ts
|
|
3786
|
-
import { Command as
|
|
3787
|
-
var renderSubcommand = new
|
|
3699
|
+
import { Command as Command35 } from "commander";
|
|
3700
|
+
var renderSubcommand = new Command35("render").description("Render the current loadout as a system prompt (XML or Markdown)").option("--format <format>", "Output format: xml or markdown", "xml").action(async (options, command) => {
|
|
3788
3701
|
const globalOpts = command.optsWithGlobals();
|
|
3789
3702
|
try {
|
|
3790
3703
|
const { server } = await createLoadoutServer(globalOpts);
|
|
@@ -3802,8 +3715,8 @@ var renderSubcommand = new Command33("render").description("Render the current l
|
|
|
3802
3715
|
});
|
|
3803
3716
|
|
|
3804
3717
|
// src/cli/commands/loadout/clear.ts
|
|
3805
|
-
import { Command as
|
|
3806
|
-
var clearSubcommand = new
|
|
3718
|
+
import { Command as Command36 } from "commander";
|
|
3719
|
+
var clearSubcommand = new Command36("clear").description("Clear the current loadout state").action(async (_options, command) => {
|
|
3807
3720
|
const globalOpts = command.optsWithGlobals();
|
|
3808
3721
|
try {
|
|
3809
3722
|
const skillPath = resolveSkillPath(globalOpts.path);
|
|
@@ -3824,10 +3737,10 @@ var clearSubcommand = new Command34("clear").description("Clear the current load
|
|
|
3824
3737
|
});
|
|
3825
3738
|
|
|
3826
3739
|
// src/cli/commands/loadout/index.ts
|
|
3827
|
-
var loadoutCommand = new
|
|
3740
|
+
var loadoutCommand = new Command37("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);
|
|
3828
3741
|
|
|
3829
3742
|
// src/cli/index.ts
|
|
3830
|
-
var program = new
|
|
3743
|
+
var program = new Command38();
|
|
3831
3744
|
var config = loadConfig();
|
|
3832
3745
|
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);
|
|
3833
3746
|
program.addCommand(listCommand);
|
|
@@ -3846,5 +3759,7 @@ program.addCommand(importCommand);
|
|
|
3846
3759
|
program.addCommand(indexerCommand);
|
|
3847
3760
|
program.addCommand(configCommand);
|
|
3848
3761
|
program.addCommand(syncCommand2);
|
|
3762
|
+
program.addCommand(readCommand);
|
|
3763
|
+
program.addCommand(materializeCommand);
|
|
3849
3764
|
program.addCommand(loadoutCommand);
|
|
3850
3765
|
program.parse();
|