@skillkit/core 1.16.0 → 1.17.0

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/index.js CHANGED
@@ -4555,6 +4555,16 @@ import { existsSync as existsSync7, mkdirSync as mkdirSync2, writeFileSync as wr
4555
4555
  import { join as join7, basename as basename6, resolve as resolve3, sep as sep2 } from "path";
4556
4556
  import { tmpdir as tmpdir4 } from "os";
4557
4557
  import { randomUUID as randomUUID4 } from "crypto";
4558
+ function skillNameFromUrl(url) {
4559
+ try {
4560
+ const parsed = new URL(url);
4561
+ const segments = parsed.pathname.split("/").filter(Boolean);
4562
+ return segments[segments.length - 1] || parsed.hostname.split(".")[0] || "default";
4563
+ } catch {
4564
+ return basename6(url) || "default";
4565
+ }
4566
+ }
4567
+ var FRONTMATTER_REGEX = /^---\s*\n[\s\S]*?name:\s*.+/;
4558
4568
  function sanitizeSkillName(name) {
4559
4569
  if (!name || typeof name !== "string") return null;
4560
4570
  const base = basename6(name);
@@ -4566,6 +4576,11 @@ function sanitizeSkillName(name) {
4566
4576
  }
4567
4577
  return name;
4568
4578
  }
4579
+ var MINTLIFY_PATHS = [
4580
+ "/.well-known/skills/default/skill.md",
4581
+ "/skill.md",
4582
+ "/.well-known/skill.md"
4583
+ ];
4569
4584
  var WellKnownProvider = class {
4570
4585
  type = "wellknown";
4571
4586
  name = "Well-Known";
@@ -4597,6 +4612,51 @@ var WellKnownProvider = class {
4597
4612
  getSshUrl(_owner, _repo) {
4598
4613
  return "";
4599
4614
  }
4615
+ async discoverFromUrl(url) {
4616
+ const baseUrl = url.replace(/\/$/, "");
4617
+ const tempDir = join7(tmpdir4(), `skillkit-wellknown-${randomUUID4()}`);
4618
+ try {
4619
+ mkdirSync2(tempDir, { recursive: true });
4620
+ for (const mintlifyPath of MINTLIFY_PATHS) {
4621
+ const fullUrl = `${baseUrl}${mintlifyPath}`;
4622
+ try {
4623
+ const response = await fetch(fullUrl);
4624
+ if (response.ok) {
4625
+ const content = await response.text();
4626
+ if (FRONTMATTER_REGEX.test(content)) {
4627
+ const skillName = skillNameFromUrl(baseUrl);
4628
+ const safeName = sanitizeSkillName(skillName) ?? "default";
4629
+ const skillDir = join7(tempDir, safeName);
4630
+ mkdirSync2(skillDir, { recursive: true });
4631
+ writeFileSync2(join7(skillDir, "SKILL.md"), content);
4632
+ return {
4633
+ success: true,
4634
+ path: tempDir,
4635
+ tempRoot: tempDir,
4636
+ skills: [safeName],
4637
+ discoveredSkills: [{ name: safeName, dirName: safeName, path: skillDir }]
4638
+ };
4639
+ }
4640
+ }
4641
+ } catch {
4642
+ continue;
4643
+ }
4644
+ }
4645
+ const indexResult = await this.clone(baseUrl, "", {});
4646
+ if (indexResult.success) {
4647
+ rmSync4(tempDir, { recursive: true, force: true });
4648
+ return indexResult;
4649
+ }
4650
+ rmSync4(tempDir, { recursive: true, force: true });
4651
+ return { success: false, error: "No well-known skills found" };
4652
+ } catch (error) {
4653
+ if (existsSync7(tempDir)) {
4654
+ rmSync4(tempDir, { recursive: true, force: true });
4655
+ }
4656
+ const message = error instanceof Error ? error.message : String(error);
4657
+ return { success: false, error: `Failed to discover skills: ${message}` };
4658
+ }
4659
+ }
4600
4660
  async clone(source, _targetDir, _options = {}) {
4601
4661
  const tempDir = join7(tmpdir4(), `skillkit-wellknown-${randomUUID4()}`);
4602
4662
  try {
@@ -4621,6 +4681,32 @@ var WellKnownProvider = class {
4621
4681
  }
4622
4682
  }
4623
4683
  if (!index || !index.skills || index.skills.length === 0) {
4684
+ for (const mintlifyPath of MINTLIFY_PATHS) {
4685
+ const fullUrl = `${baseUrl}${mintlifyPath}`;
4686
+ try {
4687
+ const response = await fetch(fullUrl);
4688
+ if (response.ok) {
4689
+ const content = await response.text();
4690
+ if (FRONTMATTER_REGEX.test(content)) {
4691
+ const skillName = skillNameFromUrl(baseUrl);
4692
+ const safeName = sanitizeSkillName(skillName) ?? "default";
4693
+ const skillDir = join7(tempDir, safeName);
4694
+ mkdirSync2(skillDir, { recursive: true });
4695
+ writeFileSync2(join7(skillDir, "SKILL.md"), content);
4696
+ return {
4697
+ success: true,
4698
+ path: tempDir,
4699
+ tempRoot: tempDir,
4700
+ skills: [safeName],
4701
+ discoveredSkills: [{ name: safeName, dirName: safeName, path: skillDir }]
4702
+ };
4703
+ }
4704
+ }
4705
+ } catch {
4706
+ continue;
4707
+ }
4708
+ }
4709
+ rmSync4(tempDir, { recursive: true, force: true });
4624
4710
  return {
4625
4711
  success: false,
4626
4712
  error: `No skills found at ${baseUrl}/.well-known/skills/index.json`
@@ -27643,10 +27729,37 @@ function evaluateSkillFile(filePath) {
27643
27729
  return null;
27644
27730
  }
27645
27731
  }
27732
+ function checkNameDirectoryMatch(content, dirPath) {
27733
+ const frontmatter = extractFrontmatter4(content);
27734
+ if (!frontmatter || typeof frontmatter.name !== "string") return null;
27735
+ const dirName = basename17(dirPath);
27736
+ if (frontmatter.name !== dirName) {
27737
+ return `Skill name "${frontmatter.name}" does not match directory "${dirName}"`;
27738
+ }
27739
+ return null;
27740
+ }
27741
+ function checkTokenBudget(content) {
27742
+ const normalizedContent = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
27743
+ const fmMatch = normalizedContent.match(/^---\s*\n[\s\S]*?\n---\s*\n?/);
27744
+ const body = fmMatch ? normalizedContent.slice(fmMatch[0].length) : normalizedContent;
27745
+ const bodyLines = body.split("\n").length;
27746
+ if (bodyLines > 500) {
27747
+ return `Body is ${bodyLines} lines (exceeds 500). Consider moving content to references/ directory`;
27748
+ }
27749
+ return null;
27750
+ }
27646
27751
  function evaluateSkillDirectory(dirPath) {
27647
27752
  const skillMdPath = join42(dirPath, "SKILL.md");
27648
27753
  if (existsSync41(skillMdPath)) {
27649
- return evaluateSkillFile(skillMdPath);
27754
+ const result = evaluateSkillFile(skillMdPath);
27755
+ if (result) {
27756
+ const content = readFileSync30(skillMdPath, "utf-8");
27757
+ const nameWarning = checkNameDirectoryMatch(content, dirPath);
27758
+ if (nameWarning) result.warnings.push(nameWarning);
27759
+ const budgetWarning = checkTokenBudget(content);
27760
+ if (budgetWarning) result.warnings.push(budgetWarning);
27761
+ }
27762
+ return result;
27650
27763
  }
27651
27764
  const mdcFiles = ["index.mdc", `${basename17(dirPath)}.mdc`];
27652
27765
  for (const file of mdcFiles) {
@@ -33181,7 +33294,7 @@ var SkillScanner = class {
33181
33294
 
33182
33295
  // src/scanner/reporter.ts
33183
33296
  import { basename as basename21 } from "path";
33184
- var SCANNER_VERSION = true ? "1.16.0" : "0.0.0";
33297
+ var SCANNER_VERSION = true ? "1.17.0" : "0.0.0";
33185
33298
  var SEVERITY_COLORS = {
33186
33299
  ["critical" /* CRITICAL */]: "\x1B[91m",
33187
33300
  ["high" /* HIGH */]: "\x1B[31m",
@@ -33452,6 +33565,764 @@ function getThreatInfo(category) {
33452
33565
  function getDefaultSeverity(category) {
33453
33566
  return THREAT_TAXONOMY[category].defaultSeverity;
33454
33567
  }
33568
+
33569
+ // src/validation/spec-validator.ts
33570
+ import { readFileSync as readFileSync38, existsSync as existsSync53 } from "fs";
33571
+ import { join as join54, basename as basename22 } from "path";
33572
+ var NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
33573
+ var SPEC_VERSION = "1.0";
33574
+ var SpecValidator = class {
33575
+ validate(skillPath, options) {
33576
+ const checks = [];
33577
+ const errors = [];
33578
+ const warnings = [];
33579
+ const skillMdPath = skillPath.endsWith(".md") ? skillPath : join54(skillPath, "SKILL.md");
33580
+ if (!existsSync53(skillMdPath)) {
33581
+ errors.push(`SKILL.md not found at ${skillMdPath}`);
33582
+ return { valid: false, errors, warnings, specVersion: SPEC_VERSION, checks };
33583
+ }
33584
+ const raw = readFileSync38(skillMdPath, "utf-8");
33585
+ const { frontmatter, body } = stripFrontmatter(raw);
33586
+ const hasName = typeof frontmatter.name === "string" && frontmatter.name.length > 0;
33587
+ checks.push({
33588
+ name: "name-present",
33589
+ passed: hasName,
33590
+ message: hasName ? "Name field is present" : 'Missing required "name" field in frontmatter',
33591
+ severity: hasName ? "info" : "error"
33592
+ });
33593
+ if (!hasName) errors.push('Missing required "name" field in frontmatter');
33594
+ const hasDescription = typeof frontmatter.description === "string" && frontmatter.description.length > 0;
33595
+ checks.push({
33596
+ name: "description-present",
33597
+ passed: hasDescription,
33598
+ message: hasDescription ? "Description field is present" : 'Missing required "description" field in frontmatter',
33599
+ severity: hasDescription ? "info" : "error"
33600
+ });
33601
+ if (!hasDescription) errors.push('Missing required "description" field in frontmatter');
33602
+ if (hasName) {
33603
+ const nameStr = frontmatter.name;
33604
+ const nameValid = NAME_PATTERN.test(nameStr);
33605
+ checks.push({
33606
+ name: "name-format",
33607
+ passed: nameValid,
33608
+ message: nameValid ? "Name matches required pattern" : `Name "${nameStr}" does not match pattern: lowercase alphanumeric with hyphens`,
33609
+ severity: nameValid ? "info" : "error"
33610
+ });
33611
+ if (!nameValid) errors.push(`Name "${nameStr}" does not match pattern: lowercase alphanumeric with hyphens`);
33612
+ }
33613
+ if (options?.strict) {
33614
+ if (frontmatter.version !== void 0) {
33615
+ warnings.push("version should be under metadata.skillkit-version");
33616
+ checks.push({
33617
+ name: "version-placement",
33618
+ passed: false,
33619
+ message: "version should be under metadata.skillkit-version",
33620
+ severity: "warning"
33621
+ });
33622
+ }
33623
+ if (frontmatter.author !== void 0) {
33624
+ warnings.push("author should be under metadata.skillkit-author");
33625
+ checks.push({
33626
+ name: "author-placement",
33627
+ passed: false,
33628
+ message: "author should be under metadata.skillkit-author",
33629
+ severity: "warning"
33630
+ });
33631
+ }
33632
+ if (frontmatter.tags !== void 0) {
33633
+ warnings.push("tags should be under metadata.skillkit-tags");
33634
+ checks.push({
33635
+ name: "tags-placement",
33636
+ passed: false,
33637
+ message: "tags should be under metadata.skillkit-tags",
33638
+ severity: "warning"
33639
+ });
33640
+ }
33641
+ if (frontmatter.agents !== void 0) {
33642
+ warnings.push("agents should be under metadata.skillkit-agents");
33643
+ checks.push({
33644
+ name: "agents-placement",
33645
+ passed: false,
33646
+ message: "agents should be under metadata.skillkit-agents",
33647
+ severity: "warning"
33648
+ });
33649
+ }
33650
+ if (hasName) {
33651
+ const skillDir = skillPath.endsWith(".md") ? join54(skillPath, "..") : skillPath;
33652
+ const dirName = basename22(skillDir);
33653
+ const nameStr = frontmatter.name;
33654
+ const nameMatchesDir = nameStr === dirName;
33655
+ checks.push({
33656
+ name: "name-directory-match",
33657
+ passed: nameMatchesDir,
33658
+ message: nameMatchesDir ? "Name matches directory" : `Name "${nameStr}" does not match directory "${dirName}"`,
33659
+ severity: nameMatchesDir ? "info" : "warning"
33660
+ });
33661
+ if (!nameMatchesDir) warnings.push(`Name "${nameStr}" does not match directory "${dirName}"`);
33662
+ }
33663
+ const bodyLines = body.split("\n").length;
33664
+ const bodyWithinLimit = bodyLines <= 500;
33665
+ checks.push({
33666
+ name: "body-length",
33667
+ passed: bodyWithinLimit,
33668
+ message: bodyWithinLimit ? `Body is ${bodyLines} lines` : `Body is ${bodyLines} lines (exceeds 500). Consider moving content to references/ directory`,
33669
+ severity: bodyWithinLimit ? "info" : "warning"
33670
+ });
33671
+ if (!bodyWithinLimit) warnings.push(`Body is ${bodyLines} lines (exceeds 500). Consider moving content to references/ directory`);
33672
+ }
33673
+ return {
33674
+ valid: errors.length === 0,
33675
+ errors,
33676
+ warnings,
33677
+ specVersion: SPEC_VERSION,
33678
+ checks
33679
+ };
33680
+ }
33681
+ };
33682
+
33683
+ // src/agents-md/generator.ts
33684
+ import { existsSync as existsSync54, readFileSync as readFileSync39 } from "fs";
33685
+ import { join as join55 } from "path";
33686
+ function escapeTableCell(text) {
33687
+ return text.replace(/\|/g, "\\|").replace(/\n/g, " ");
33688
+ }
33689
+ var MANAGED_START = "<!-- skillkit:managed:start -->";
33690
+ var MANAGED_END = "<!-- skillkit:managed:end -->";
33691
+ var AgentsMdGenerator = class {
33692
+ config;
33693
+ detector;
33694
+ constructor(config) {
33695
+ this.config = config;
33696
+ this.detector = new ProjectDetector(config.projectPath);
33697
+ }
33698
+ generate() {
33699
+ this.detector.analyze();
33700
+ const sections = [];
33701
+ sections.push({
33702
+ id: "project-overview",
33703
+ title: "Project Overview",
33704
+ content: this.generateProjectSection(),
33705
+ managed: true
33706
+ });
33707
+ sections.push({
33708
+ id: "technology-stack",
33709
+ title: "Technology Stack",
33710
+ content: this.generateStackSection(),
33711
+ managed: true
33712
+ });
33713
+ if (this.config.includeSkills !== false) {
33714
+ const skillsContent = this.generateSkillsSection(findAllSkills([join55(this.config.projectPath, "skills")]));
33715
+ if (skillsContent) {
33716
+ sections.push({
33717
+ id: "installed-skills",
33718
+ title: "Installed Skills",
33719
+ content: skillsContent,
33720
+ managed: true
33721
+ });
33722
+ }
33723
+ }
33724
+ if (this.config.includeBuildCommands !== false) {
33725
+ const buildContent = this.generateBuildSection();
33726
+ if (buildContent) {
33727
+ sections.push({
33728
+ id: "build-test",
33729
+ title: "Build & Test",
33730
+ content: buildContent,
33731
+ managed: true
33732
+ });
33733
+ }
33734
+ }
33735
+ if (this.config.includeCodeStyle !== false) {
33736
+ const styleContent = this.generateCodeStyleSection();
33737
+ if (styleContent) {
33738
+ sections.push({
33739
+ id: "code-style",
33740
+ title: "Code Style",
33741
+ content: styleContent,
33742
+ managed: true
33743
+ });
33744
+ }
33745
+ }
33746
+ const lines = ["# AGENTS.md", "", MANAGED_START];
33747
+ for (const section of sections) {
33748
+ lines.push(`## ${section.title}`);
33749
+ lines.push(section.content);
33750
+ lines.push("");
33751
+ }
33752
+ lines.push(MANAGED_END, "");
33753
+ const content = lines.join("\n");
33754
+ return {
33755
+ content,
33756
+ sections,
33757
+ path: join55(this.config.projectPath, "AGENTS.md")
33758
+ };
33759
+ }
33760
+ generateSkillsSection(skills) {
33761
+ if (skills.length === 0) {
33762
+ return "";
33763
+ }
33764
+ const lines = [
33765
+ "| Skill | Description | Tags |",
33766
+ "|-------|-------------|------|"
33767
+ ];
33768
+ for (const skill of skills) {
33769
+ const name = escapeTableCell(skill.name);
33770
+ const desc = escapeTableCell(skill.description);
33771
+ lines.push(`| ${name} | ${desc} | |`);
33772
+ }
33773
+ return lines.join("\n");
33774
+ }
33775
+ generateProjectSection() {
33776
+ const name = this.detector.getProjectName();
33777
+ const description = this.detector.getProjectDescription();
33778
+ const projectType = this.detector.detectProjectType();
33779
+ const lines = [];
33780
+ lines.push(`- **Name**: ${name}`);
33781
+ if (description) {
33782
+ lines.push(`- **Description**: ${description}`);
33783
+ }
33784
+ lines.push(`- **Type**: ${projectType}`);
33785
+ return lines.join("\n");
33786
+ }
33787
+ generateBuildSection() {
33788
+ const packageJsonPath = join55(this.config.projectPath, "package.json");
33789
+ if (!existsSync54(packageJsonPath)) {
33790
+ return "";
33791
+ }
33792
+ try {
33793
+ const pkg = JSON.parse(readFileSync39(packageJsonPath, "utf-8"));
33794
+ const scripts = pkg.scripts;
33795
+ if (!scripts || Object.keys(scripts).length === 0) {
33796
+ return "";
33797
+ }
33798
+ const relevantScripts = ["build", "dev", "start", "test", "lint", "format", "typecheck", "check"];
33799
+ const lines = ["```bash"];
33800
+ for (const key of relevantScripts) {
33801
+ if (scripts[key]) {
33802
+ lines.push(`# ${key}`);
33803
+ lines.push(scripts[key]);
33804
+ lines.push("");
33805
+ }
33806
+ }
33807
+ if (lines.length === 1) {
33808
+ return "";
33809
+ }
33810
+ lines.push("```");
33811
+ return lines.join("\n");
33812
+ } catch {
33813
+ return "";
33814
+ }
33815
+ }
33816
+ generateCodeStyleSection() {
33817
+ const patterns = this.detector.detectPatterns();
33818
+ const lines = [];
33819
+ if (patterns.linting) {
33820
+ lines.push(`- **Linting**: ${patterns.linting}`);
33821
+ }
33822
+ if (patterns.formatting) {
33823
+ lines.push(`- **Formatting**: ${patterns.formatting}`);
33824
+ }
33825
+ if (patterns.testing) {
33826
+ lines.push(`- **Testing**: ${patterns.testing}`);
33827
+ }
33828
+ if (patterns.styling) {
33829
+ lines.push(`- **Styling**: ${patterns.styling}`);
33830
+ }
33831
+ return lines.length > 0 ? lines.join("\n") : "";
33832
+ }
33833
+ generateStackSection() {
33834
+ const stack = this.detector.analyze();
33835
+ const lines = [];
33836
+ if (stack.languages.length > 0) {
33837
+ lines.push(`- **Languages**: ${stack.languages.map((l) => l.version ? `${l.name} ${l.version}` : l.name).join(", ")}`);
33838
+ }
33839
+ if (stack.frameworks.length > 0) {
33840
+ lines.push(`- **Frameworks**: ${stack.frameworks.map((f) => f.version ? `${f.name} ${f.version}` : f.name).join(", ")}`);
33841
+ }
33842
+ if (stack.libraries.length > 0) {
33843
+ lines.push(`- **Libraries**: ${stack.libraries.map((l) => l.name).join(", ")}`);
33844
+ }
33845
+ if (stack.databases.length > 0) {
33846
+ lines.push(`- **Databases**: ${stack.databases.map((d) => d.name).join(", ")}`);
33847
+ }
33848
+ if (stack.runtime.length > 0) {
33849
+ lines.push(`- **Runtime**: ${stack.runtime.map((r) => r.version ? `${r.name} ${r.version}` : r.name).join(", ")}`);
33850
+ }
33851
+ return lines.length > 0 ? lines.join("\n") : "No technology stack detected.";
33852
+ }
33853
+ };
33854
+
33855
+ // src/agents-md/parser.ts
33856
+ var MANAGED_START2 = "<!-- skillkit:managed:start -->";
33857
+ var MANAGED_END2 = "<!-- skillkit:managed:end -->";
33858
+ var AgentsMdParser = class {
33859
+ parse(content) {
33860
+ const sections = [];
33861
+ const lines = content.split("\n");
33862
+ let inManaged = false;
33863
+ let currentSection = null;
33864
+ for (const line of lines) {
33865
+ if (line.trim() === MANAGED_START2) {
33866
+ inManaged = true;
33867
+ continue;
33868
+ }
33869
+ if (line.trim() === MANAGED_END2) {
33870
+ if (currentSection) {
33871
+ sections.push({
33872
+ id: currentSection.id,
33873
+ title: currentSection.title,
33874
+ content: currentSection.lines.join("\n").trim(),
33875
+ managed: currentSection.managed
33876
+ });
33877
+ currentSection = null;
33878
+ }
33879
+ inManaged = false;
33880
+ continue;
33881
+ }
33882
+ const headingMatch = line.match(/^##\s+(.+)$/);
33883
+ if (headingMatch) {
33884
+ if (currentSection) {
33885
+ sections.push({
33886
+ id: currentSection.id,
33887
+ title: currentSection.title,
33888
+ content: currentSection.lines.join("\n").trim(),
33889
+ managed: currentSection.managed
33890
+ });
33891
+ }
33892
+ const title = headingMatch[1];
33893
+ currentSection = {
33894
+ id: title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""),
33895
+ title,
33896
+ lines: [],
33897
+ managed: inManaged
33898
+ };
33899
+ continue;
33900
+ }
33901
+ if (currentSection) {
33902
+ currentSection.lines.push(line);
33903
+ }
33904
+ }
33905
+ if (currentSection) {
33906
+ sections.push({
33907
+ id: currentSection.id,
33908
+ title: currentSection.title,
33909
+ content: currentSection.lines.join("\n").trim(),
33910
+ managed: currentSection.managed
33911
+ });
33912
+ }
33913
+ return sections;
33914
+ }
33915
+ hasManagedSections(content) {
33916
+ return content.includes(MANAGED_START2) && content.includes(MANAGED_END2);
33917
+ }
33918
+ updateManagedSections(existing, newManaged) {
33919
+ if (!this.hasManagedSections(existing)) {
33920
+ return existing;
33921
+ }
33922
+ const startIdx = existing.indexOf(MANAGED_START2);
33923
+ const endIdx = existing.indexOf(MANAGED_END2);
33924
+ if (startIdx >= endIdx) {
33925
+ return existing;
33926
+ }
33927
+ const managedBlock = this.buildManagedBlock(newManaged);
33928
+ const before = existing.substring(0, startIdx);
33929
+ const after = existing.substring(endIdx + MANAGED_END2.length);
33930
+ return `${before}${managedBlock}${after}`;
33931
+ }
33932
+ buildManagedBlock(sections) {
33933
+ const lines = [MANAGED_START2];
33934
+ for (const section of sections) {
33935
+ lines.push(`## ${section.title}`);
33936
+ lines.push(section.content);
33937
+ lines.push("");
33938
+ }
33939
+ lines.push(MANAGED_END2);
33940
+ return lines.join("\n");
33941
+ }
33942
+ };
33943
+
33944
+ // src/save/extractor.ts
33945
+ import { existsSync as existsSync55, readFileSync as readFileSync40 } from "fs";
33946
+ import { basename as basename23, extname as extname7 } from "path";
33947
+ import TurndownService from "turndown";
33948
+ var LANGUAGE_MAP = {
33949
+ ".ts": "typescript",
33950
+ ".tsx": "typescript",
33951
+ ".js": "javascript",
33952
+ ".jsx": "javascript",
33953
+ ".py": "python",
33954
+ ".rb": "ruby",
33955
+ ".go": "go",
33956
+ ".rs": "rust",
33957
+ ".java": "java",
33958
+ ".kt": "kotlin",
33959
+ ".swift": "swift",
33960
+ ".c": "c",
33961
+ ".cpp": "cpp",
33962
+ ".cs": "csharp",
33963
+ ".php": "php",
33964
+ ".sh": "shell",
33965
+ ".bash": "shell",
33966
+ ".zsh": "shell",
33967
+ ".yml": "yaml",
33968
+ ".yaml": "yaml",
33969
+ ".json": "json",
33970
+ ".toml": "toml",
33971
+ ".md": "markdown",
33972
+ ".mdx": "markdown",
33973
+ ".html": "html",
33974
+ ".css": "css",
33975
+ ".scss": "scss",
33976
+ ".sql": "sql",
33977
+ ".r": "r",
33978
+ ".lua": "lua",
33979
+ ".dart": "dart",
33980
+ ".ex": "elixir",
33981
+ ".exs": "elixir",
33982
+ ".zig": "zig",
33983
+ ".nim": "nim",
33984
+ ".vue": "vue",
33985
+ ".svelte": "svelte"
33986
+ };
33987
+ var GITHUB_URL_PATTERN = /^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+)$/;
33988
+ var GITHUB_RAW_PATTERN = /^https?:\/\/raw\.githubusercontent\.com\//;
33989
+ var FETCH_TIMEOUT = 3e4;
33990
+ var ContentExtractor = class {
33991
+ turndown;
33992
+ constructor() {
33993
+ this.turndown = new TurndownService({
33994
+ headingStyle: "atx",
33995
+ codeBlockStyle: "fenced"
33996
+ });
33997
+ }
33998
+ async extractFromUrl(url, options) {
33999
+ if (this.isGitHubUrl(url)) {
34000
+ return this.fetchGitHubContent(url, options);
34001
+ }
34002
+ const response = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT) });
34003
+ if (!response.ok) {
34004
+ throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
34005
+ }
34006
+ const contentType = response.headers.get("content-type") ?? "";
34007
+ const body = await response.text();
34008
+ if (contentType.includes("text/html")) {
34009
+ const { title: title2, content } = this.htmlToMarkdown(body, url);
34010
+ const finalContent2 = options?.maxLength ? content.slice(0, options.maxLength) : content;
34011
+ return {
34012
+ title: options?.preferredTitle ?? title2,
34013
+ content: finalContent2,
34014
+ sourceUrl: url,
34015
+ tags: [],
34016
+ extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
34017
+ contentType: "webpage",
34018
+ metadata: { url }
34019
+ };
34020
+ }
34021
+ const title = options?.preferredTitle ?? new URL(url).pathname.split("/").pop() ?? "Untitled";
34022
+ const finalContent = options?.maxLength ? body.slice(0, options.maxLength) : body;
34023
+ return {
34024
+ title,
34025
+ content: finalContent,
34026
+ sourceUrl: url,
34027
+ tags: [],
34028
+ extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
34029
+ contentType: "text",
34030
+ metadata: { url }
34031
+ };
34032
+ }
34033
+ extractFromText(text, options) {
34034
+ const firstLine = text.split("\n")[0]?.trim() ?? "";
34035
+ const title = options?.preferredTitle ?? (firstLine.length > 0 && firstLine.length <= 100 ? firstLine : "Untitled");
34036
+ const content = options?.maxLength ? text.slice(0, options.maxLength) : text;
34037
+ return {
34038
+ title,
34039
+ content,
34040
+ tags: [],
34041
+ extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
34042
+ contentType: "text",
34043
+ metadata: {}
34044
+ };
34045
+ }
34046
+ extractFromFile(filePath, options) {
34047
+ if (!existsSync55(filePath)) {
34048
+ throw new Error(`File not found: ${filePath}`);
34049
+ }
34050
+ const raw = readFileSync40(filePath, "utf-8");
34051
+ const name = basename23(filePath);
34052
+ const language = this.detectLanguage(name);
34053
+ const isCode = language !== void 0 && language !== "markdown";
34054
+ const title = options?.preferredTitle ?? name;
34055
+ const content = options?.maxLength ? raw.slice(0, options.maxLength) : raw;
34056
+ return {
34057
+ title,
34058
+ content: isCode ? `\`\`\`${language}
34059
+ ${content}
34060
+ \`\`\`` : content,
34061
+ sourcePath: filePath,
34062
+ tags: language ? [language] : [],
34063
+ extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
34064
+ contentType: isCode ? "code" : "file",
34065
+ language,
34066
+ metadata: { filename: name }
34067
+ };
34068
+ }
34069
+ isGitHubUrl(url) {
34070
+ return GITHUB_URL_PATTERN.test(url) || GITHUB_RAW_PATTERN.test(url);
34071
+ }
34072
+ async fetchGitHubContent(url, options) {
34073
+ let rawUrl = url;
34074
+ const match = url.match(GITHUB_URL_PATTERN);
34075
+ if (match) {
34076
+ const [, owner, repo, branch, path4] = match;
34077
+ rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path4}`;
34078
+ }
34079
+ const response = await fetch(rawUrl, { signal: AbortSignal.timeout(FETCH_TIMEOUT) });
34080
+ if (!response.ok) {
34081
+ throw new Error(`Failed to fetch GitHub content: ${response.status} ${response.statusText}`);
34082
+ }
34083
+ const body = await response.text();
34084
+ const filename = rawUrl.split("/").pop() ?? "file";
34085
+ const language = this.detectLanguage(filename);
34086
+ const isCode = language !== void 0 && language !== "markdown";
34087
+ const title = options?.preferredTitle ?? filename;
34088
+ const content = options?.maxLength ? body.slice(0, options.maxLength) : body;
34089
+ return {
34090
+ title,
34091
+ content: isCode ? `\`\`\`${language}
34092
+ ${content}
34093
+ \`\`\`` : content,
34094
+ sourceUrl: url,
34095
+ tags: language ? ["github", language] : ["github"],
34096
+ extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
34097
+ contentType: "github",
34098
+ language,
34099
+ metadata: {
34100
+ url,
34101
+ rawUrl,
34102
+ filename
34103
+ }
34104
+ };
34105
+ }
34106
+ htmlToMarkdown(html, url) {
34107
+ const titleMatch = html.match(/<title[^>]*>([^<]+)<\/title>/i);
34108
+ const title = titleMatch?.[1]?.trim() ?? new URL(url).hostname;
34109
+ const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
34110
+ const bodyHtml = bodyMatch?.[1] ?? html;
34111
+ const content = this.turndown.turndown(bodyHtml);
34112
+ return { title, content };
34113
+ }
34114
+ detectLanguage(filename) {
34115
+ const ext = extname7(filename).toLowerCase();
34116
+ return LANGUAGE_MAP[ext];
34117
+ }
34118
+ };
34119
+
34120
+ // src/save/tagger.ts
34121
+ var TECH_KEYWORDS = /* @__PURE__ */ new Set([
34122
+ "react",
34123
+ "vue",
34124
+ "angular",
34125
+ "svelte",
34126
+ "nextjs",
34127
+ "nuxt",
34128
+ "remix",
34129
+ "typescript",
34130
+ "javascript",
34131
+ "python",
34132
+ "rust",
34133
+ "go",
34134
+ "java",
34135
+ "ruby",
34136
+ "node",
34137
+ "deno",
34138
+ "bun",
34139
+ "docker",
34140
+ "kubernetes",
34141
+ "terraform",
34142
+ "aws",
34143
+ "gcp",
34144
+ "azure",
34145
+ "vercel",
34146
+ "netlify",
34147
+ "cloudflare",
34148
+ "graphql",
34149
+ "rest",
34150
+ "grpc",
34151
+ "websocket",
34152
+ "redis",
34153
+ "postgres",
34154
+ "mongodb",
34155
+ "sqlite",
34156
+ "mysql",
34157
+ "prisma",
34158
+ "drizzle",
34159
+ "tailwind",
34160
+ "css",
34161
+ "html",
34162
+ "sass",
34163
+ "webpack",
34164
+ "vite",
34165
+ "esbuild",
34166
+ "git",
34167
+ "ci",
34168
+ "cd",
34169
+ "testing",
34170
+ "security",
34171
+ "authentication",
34172
+ "api",
34173
+ "cli",
34174
+ "sdk",
34175
+ "mcp",
34176
+ "llm",
34177
+ "ai",
34178
+ "ml",
34179
+ "openai",
34180
+ "anthropic"
34181
+ ]);
34182
+ var TAG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
34183
+ var AutoTagger = class {
34184
+ detectTags(content) {
34185
+ const tagCounts = /* @__PURE__ */ new Map();
34186
+ this.extractFromUrl(content.sourceUrl, tagCounts);
34187
+ this.extractFromHeadings(content.content, tagCounts);
34188
+ this.extractFromCodeBlocks(content.content, tagCounts);
34189
+ this.extractFromKeywords(content.content, tagCounts);
34190
+ if (content.language) {
34191
+ this.addTag(content.language.toLowerCase(), tagCounts, 3);
34192
+ }
34193
+ if (content.contentType !== "text") {
34194
+ this.addTag(content.contentType, tagCounts, 1);
34195
+ }
34196
+ return Array.from(tagCounts.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([tag]) => tag);
34197
+ }
34198
+ extractFromUrl(url, counts) {
34199
+ if (!url) return;
34200
+ try {
34201
+ const parsed = new URL(url);
34202
+ const segments = parsed.pathname.split("/").filter(Boolean).map((s) => s.toLowerCase().replace(/[^a-z0-9-]/g, ""));
34203
+ for (const seg of segments) {
34204
+ if (seg.length >= 2 && seg.length <= 30 && TAG_PATTERN.test(seg)) {
34205
+ this.addTag(seg, counts, 2);
34206
+ }
34207
+ }
34208
+ } catch {
34209
+ }
34210
+ }
34211
+ extractFromHeadings(content, counts) {
34212
+ const headingRe = /^#{1,2}\s+(.+)$/gm;
34213
+ let match;
34214
+ while ((match = headingRe.exec(content)) !== null) {
34215
+ const words = match[1].toLowerCase().split(/\s+/);
34216
+ for (const word of words) {
34217
+ const cleaned = word.replace(/[^a-z0-9-]/g, "");
34218
+ if (cleaned.length >= 2 && TAG_PATTERN.test(cleaned)) {
34219
+ this.addTag(cleaned, counts, 2);
34220
+ }
34221
+ }
34222
+ }
34223
+ }
34224
+ extractFromCodeBlocks(content, counts) {
34225
+ const codeBlockRe = /^```(\w+)/gm;
34226
+ let match;
34227
+ while ((match = codeBlockRe.exec(content)) !== null) {
34228
+ const lang = match[1].toLowerCase();
34229
+ if (lang.length >= 2 && TAG_PATTERN.test(lang)) {
34230
+ this.addTag(lang, counts, 3);
34231
+ }
34232
+ }
34233
+ }
34234
+ extractFromKeywords(content, counts) {
34235
+ const lower = content.toLowerCase();
34236
+ for (const keyword of TECH_KEYWORDS) {
34237
+ const re = new RegExp(`\\b${keyword}\\b`, "i");
34238
+ if (re.test(lower)) {
34239
+ this.addTag(keyword, counts, 1);
34240
+ }
34241
+ }
34242
+ }
34243
+ addTag(tag, counts, weight) {
34244
+ if (!TAG_PATTERN.test(tag)) return;
34245
+ counts.set(tag, (counts.get(tag) ?? 0) + weight);
34246
+ }
34247
+ };
34248
+
34249
+ // src/save/skill-generator.ts
34250
+ import { mkdirSync as mkdirSync28, writeFileSync as writeFileSync28 } from "fs";
34251
+ import { join as join56 } from "path";
34252
+ import { homedir as homedir19 } from "os";
34253
+ var MAX_NAME_LENGTH = 64;
34254
+ var SUMMARY_LINE_LIMIT = 100;
34255
+ var SPLIT_THRESHOLD = 500;
34256
+ var DESCRIPTION_MAX = 200;
34257
+ var SkillGenerator = class {
34258
+ tagger = new AutoTagger();
34259
+ generate(content, options = {}) {
34260
+ const name = options.name ? this.slugify(options.name) : this.slugify(content.title || "untitled-skill");
34261
+ const tags = this.tagger.detectTags(content);
34262
+ const description = this.makeDescription(content.content);
34263
+ const source = content.sourceUrl ?? content.sourcePath ?? "";
34264
+ const frontmatter = this.buildFrontmatter(name, description, tags, source);
34265
+ const lines = content.content.split("\n");
34266
+ const needsSplit = lines.length > SPLIT_THRESHOLD;
34267
+ const body = needsSplit ? lines.slice(0, SUMMARY_LINE_LIMIT).join("\n") : content.content;
34268
+ const skillMd = `${frontmatter}
34269
+ ${body}
34270
+ `;
34271
+ const outputDir = options.outputDir ?? this.defaultOutputDir(name, options.global);
34272
+ mkdirSync28(outputDir, { recursive: true });
34273
+ const skillPath = join56(outputDir, "SKILL.md");
34274
+ writeFileSync28(skillPath, skillMd, "utf-8");
34275
+ if (needsSplit) {
34276
+ const refsDir = join56(outputDir, "references");
34277
+ mkdirSync28(refsDir, { recursive: true });
34278
+ writeFileSync28(
34279
+ join56(refsDir, "full-content.md"),
34280
+ content.content,
34281
+ "utf-8"
34282
+ );
34283
+ }
34284
+ return { skillPath, skillMd, name };
34285
+ }
34286
+ slugify(input) {
34287
+ const slug = input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-");
34288
+ const trimmed = slug.slice(0, MAX_NAME_LENGTH).replace(/-+$/, "");
34289
+ return trimmed || "untitled-skill";
34290
+ }
34291
+ makeDescription(content) {
34292
+ const firstLine = content.split("\n").find((l) => l.trim().length > 0) ?? "";
34293
+ const cleaned = firstLine.replace(/^#+\s*/, "").trim();
34294
+ return cleaned.length > DESCRIPTION_MAX ? cleaned.slice(0, DESCRIPTION_MAX - 3) + "..." : cleaned || "Saved skill";
34295
+ }
34296
+ buildFrontmatter(name, description, tags, source) {
34297
+ const yamlTags = tags.map((t) => ` - ${t}`).join("\n");
34298
+ const savedAt = (/* @__PURE__ */ new Date()).toISOString();
34299
+ const lines = [
34300
+ "---",
34301
+ `name: ${name}`,
34302
+ `description: ${this.yamlEscape(description)}`,
34303
+ tags.length > 0 ? `tags:
34304
+ ${yamlTags}` : null,
34305
+ "metadata:",
34306
+ source ? ` source: ${source}` : null,
34307
+ ` savedAt: ${savedAt}`,
34308
+ "---"
34309
+ ].filter((l) => l !== null);
34310
+ return lines.join("\n") + "\n";
34311
+ }
34312
+ yamlEscape(value) {
34313
+ const singleLine = value.replace(/\r?\n/g, " ").trim();
34314
+ if (/[:#{}[\],&*?|>!%@`]/.test(singleLine) || singleLine.startsWith("'") || singleLine.startsWith('"')) {
34315
+ return `"${singleLine.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
34316
+ }
34317
+ return singleLine;
34318
+ }
34319
+ defaultOutputDir(name, global) {
34320
+ if (global) {
34321
+ return join56(homedir19(), ".skillkit", "skills", name);
34322
+ }
34323
+ return join56(".skillkit", "skills", name);
34324
+ }
34325
+ };
33455
34326
  export {
33456
34327
  AGENT_CLI_CONFIGS,
33457
34328
  AGENT_CONFIG,
@@ -33471,8 +34342,11 @@ export {
33471
34342
  AgentOptimizer,
33472
34343
  AgentPermissionMode,
33473
34344
  AgentType,
34345
+ AgentsMdGenerator,
34346
+ AgentsMdParser,
33474
34347
  AnthropicProvider,
33475
34348
  AuditLogger,
34349
+ AutoTagger,
33476
34350
  BUILTIN_PIPELINES,
33477
34351
  BaseAIProvider,
33478
34352
  BitbucketProvider,
@@ -33493,6 +34367,7 @@ export {
33493
34367
  ConnectorConfigSchema,
33494
34368
  ConnectorMappingSchema,
33495
34369
  ConnectorPlaceholderSchema,
34370
+ ContentExtractor,
33496
34371
  ContextEngine,
33497
34372
  ContextLoader,
33498
34373
  ContextManager,
@@ -33597,6 +34472,7 @@ export {
33597
34472
  SkillComposer,
33598
34473
  SkillExecutionEngine,
33599
34474
  SkillFrontmatter,
34475
+ SkillGenerator,
33600
34476
  SkillInjector,
33601
34477
  SkillLocation,
33602
34478
  SkillMdTranslator,
@@ -33609,6 +34485,7 @@ export {
33609
34485
  SkillWizard,
33610
34486
  SkillkitConfig,
33611
34487
  SkillsSource,
34488
+ SpecValidator,
33612
34489
  StaticAnalyzer,
33613
34490
  TAG_TO_CATEGORY,
33614
34491
  TAG_TO_TECH,