skilldb 0.1.4 → 0.2.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/cli.js CHANGED
@@ -105,8 +105,9 @@ var SkillDBClient = class {
105
105
  /** Validate that the configured API key works. */
106
106
  async validate() {
107
107
  try {
108
- await this.list({ limit: 1 });
109
- return true;
108
+ const url = `${this.baseUrl}/keys/usage`;
109
+ const res = await fetch(url, { headers: this.headers() });
110
+ return res.ok;
110
111
  } catch {
111
112
  return false;
112
113
  }
@@ -433,9 +434,9 @@ function getIntegrationSnippet(ide) {
433
434
  ## SkillDB Skills
434
435
 
435
436
  Local skills are available in \`.skilldb/skills/\`. Use them as reference when working on tasks.
437
+ Search: \`skilldb search react server components\` (supports multiple words)
436
438
  Download a skill: \`skilldb get <pack>/<skill>\` (e.g. \`skilldb get software-skills/code-review\`)
437
439
  Download a full pack: \`skilldb add <pack>\`
438
- Search skills: \`skilldb search <query>\`
439
440
  `.trim();
440
441
  return `
441
442
 
@@ -495,7 +496,7 @@ async function initCommand() {
495
496
  }
496
497
  console.log(pc6.green("\nDone! Next steps:"));
497
498
  console.log(` ${pc6.dim("$")} skilldb login ${pc6.dim("# save your API key")}`);
498
- console.log(` ${pc6.dim("$")} skilldb search solana ${pc6.dim("# find skills")}`);
499
+ console.log(` ${pc6.dim("$")} skilldb search react server components ${pc6.dim("# multi-word search")}`);
499
500
  console.log(` ${pc6.dim("$")} skilldb get software-skills/code-review ${pc6.dim("# download one skill")}`);
500
501
  console.log(` ${pc6.dim("$")} skilldb add game-design-skills ${pc6.dim("# download full pack")}`);
501
502
  }
@@ -537,9 +538,1087 @@ async function loginCommand() {
537
538
  }
538
539
  }
539
540
 
541
+ // src/commands/use.ts
542
+ import fs4 from "fs";
543
+ import path4 from "path";
544
+ import pc8 from "picocolors";
545
+ var SKILLDB_DIR2 = ".skilldb";
546
+ var ACTIVE_DIR = "active";
547
+ var CONFIG_FILE = "config.json";
548
+ var PROFILES = {
549
+ frontend: ["react-patterns-skills", "web-polish-skills", "typescript-skills", "css-skills"],
550
+ backend: ["software-skills", "api-design-skills", "database-skills", "nodejs-skills"],
551
+ devops: ["devops-skills", "docker-skills", "ci-cd-skills", "infrastructure-skills"],
552
+ security: ["security-skills", "appsec-skills", "auth-skills"],
553
+ data: ["data-engineering-skills", "sql-skills", "analytics-skills"],
554
+ fullstack: ["react-patterns-skills", "software-skills", "typescript-skills", "api-design-skills"],
555
+ mobile: ["react-native-skills", "mobile-skills", "ios-skills", "android-skills"],
556
+ "ai-agent": ["ai-agent-skills", "prompt-engineering-skills", "llm-skills"]
557
+ };
558
+ function readConfig(cwd) {
559
+ const p = path4.join(cwd, SKILLDB_DIR2, CONFIG_FILE);
560
+ try {
561
+ return JSON.parse(fs4.readFileSync(p, "utf-8"));
562
+ } catch {
563
+ return {};
564
+ }
565
+ }
566
+ function writeConfig(cwd, config) {
567
+ const dir = path4.join(cwd, SKILLDB_DIR2);
568
+ if (!fs4.existsSync(dir)) fs4.mkdirSync(dir, { recursive: true });
569
+ fs4.writeFileSync(path4.join(dir, CONFIG_FILE), JSON.stringify(config, null, 2) + "\n");
570
+ }
571
+ function autoDetect(cwd) {
572
+ const has = (f) => fs4.existsSync(path4.join(cwd, f));
573
+ let pkgDeps = [];
574
+ try {
575
+ const pkg = JSON.parse(fs4.readFileSync(path4.join(cwd, "package.json"), "utf-8"));
576
+ pkgDeps = Object.keys({ ...pkg.dependencies, ...pkg.devDependencies });
577
+ } catch {
578
+ }
579
+ if (pkgDeps.some((d) => ["react-native", "expo"].includes(d))) return "mobile";
580
+ if (pkgDeps.some((d) => ["react", "next", "vue", "svelte", "angular"].includes(d))) {
581
+ if (pkgDeps.some((d) => ["express", "fastify", "nestjs", "prisma"].includes(d))) return "fullstack";
582
+ return "frontend";
583
+ }
584
+ if (has("Dockerfile") || has("docker-compose.yml") || has(".github/workflows")) return "devops";
585
+ if (pkgDeps.some((d) => ["langchain", "openai", "@anthropic-ai/sdk"].includes(d))) return "ai-agent";
586
+ if (pkgDeps.some((d) => ["express", "fastify", "koa", "nestjs"].includes(d))) return "backend";
587
+ return "fullstack";
588
+ }
589
+ function activateProfile(cwd, profile) {
590
+ const activeDir = path4.join(cwd, SKILLDB_DIR2, ACTIVE_DIR);
591
+ if (fs4.existsSync(activeDir)) fs4.rmSync(activeDir, { recursive: true });
592
+ fs4.mkdirSync(activeDir, { recursive: true });
593
+ const packs = PROFILES[profile] || [];
594
+ const skillsDir = path4.join(cwd, SKILLDB_DIR2, "skills");
595
+ let copied = 0;
596
+ for (const pack of packs) {
597
+ const packDir = path4.join(skillsDir, pack);
598
+ if (!fs4.existsSync(packDir)) continue;
599
+ const destDir = path4.join(activeDir, pack);
600
+ fs4.mkdirSync(destDir, { recursive: true });
601
+ for (const file of fs4.readdirSync(packDir)) {
602
+ fs4.copyFileSync(path4.join(packDir, file), path4.join(destDir, file));
603
+ copied++;
604
+ }
605
+ }
606
+ const config = readConfig(cwd);
607
+ config.activeProfile = profile;
608
+ writeConfig(cwd, config);
609
+ console.log(pc8.green(`Profile "${profile}" activated`) + pc8.dim(` (${copied} skills in .skilldb/active/)`));
610
+ if (copied === 0) {
611
+ console.log(pc8.yellow("No matching skills found locally. Install packs first:"));
612
+ for (const pack of packs.slice(0, 3)) {
613
+ console.log(pc8.dim(` skilldb add ${pack}`));
614
+ }
615
+ }
616
+ }
617
+ async function useCommand(profile, options) {
618
+ const cwd = process.cwd();
619
+ if (options.list) {
620
+ console.log(pc8.bold("Available profiles:\n"));
621
+ for (const [name, packs] of Object.entries(PROFILES)) {
622
+ console.log(` ${pc8.cyan(name.padEnd(12))} ${pc8.dim(packs.join(", "))}`);
623
+ }
624
+ console.log(pc8.dim('\nUse "skilldb use auto" to auto-detect from project files.'));
625
+ return;
626
+ }
627
+ if (options.current) {
628
+ const config = readConfig(cwd);
629
+ if (config.activeProfile) {
630
+ console.log(`Active profile: ${pc8.cyan(config.activeProfile)}`);
631
+ } else {
632
+ console.log(pc8.dim('No active profile. Run "skilldb use <profile>" to activate.'));
633
+ }
634
+ return;
635
+ }
636
+ if (profile === "none") {
637
+ const activeDir = path4.join(cwd, SKILLDB_DIR2, ACTIVE_DIR);
638
+ if (fs4.existsSync(activeDir)) fs4.rmSync(activeDir, { recursive: true });
639
+ const config = readConfig(cwd);
640
+ delete config.activeProfile;
641
+ writeConfig(cwd, config);
642
+ console.log(pc8.green("Profile deactivated. Active directory cleared."));
643
+ return;
644
+ }
645
+ if (profile === "auto") {
646
+ const detected = autoDetect(cwd);
647
+ console.log(pc8.dim(`Auto-detected profile: ${detected}`));
648
+ activateProfile(cwd, detected);
649
+ return;
650
+ }
651
+ if (!PROFILES[profile]) {
652
+ console.error(pc8.red(`Unknown profile "${profile}". Use --list to see available profiles.`));
653
+ process.exit(1);
654
+ }
655
+ activateProfile(cwd, profile);
656
+ }
657
+
658
+ // src/commands/budget.ts
659
+ import fs5 from "fs";
660
+ import path5 from "path";
661
+ import pc9 from "picocolors";
662
+ var SKILLDB_DIR3 = ".skilldb";
663
+ var CONFIG_FILE2 = "config.json";
664
+ var ACTIVE_DIR2 = "active";
665
+ var TOKENS_PER_LINE = 10;
666
+ function readConfig2(cwd) {
667
+ const p = path5.join(cwd, SKILLDB_DIR3, CONFIG_FILE2);
668
+ try {
669
+ return JSON.parse(fs5.readFileSync(p, "utf-8"));
670
+ } catch {
671
+ return {};
672
+ }
673
+ }
674
+ function writeConfig2(cwd, config) {
675
+ const dir = path5.join(cwd, SKILLDB_DIR3);
676
+ if (!fs5.existsSync(dir)) fs5.mkdirSync(dir, { recursive: true });
677
+ fs5.writeFileSync(path5.join(dir, CONFIG_FILE2), JSON.stringify(config, null, 2) + "\n");
678
+ }
679
+ function countActiveSkills(cwd) {
680
+ const activeDir = path5.join(cwd, SKILLDB_DIR3, ACTIVE_DIR2);
681
+ const files = [];
682
+ let totalLines = 0;
683
+ if (!fs5.existsSync(activeDir)) return { files, totalLines, totalTokens: 0 };
684
+ function walk(dir) {
685
+ for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
686
+ const full = path5.join(dir, entry.name);
687
+ if (entry.isDirectory()) {
688
+ walk(full);
689
+ continue;
690
+ }
691
+ if (!entry.name.endsWith(".md")) continue;
692
+ const content = fs5.readFileSync(full, "utf-8");
693
+ const lines = content.split("\n").length;
694
+ files.push(path5.relative(path5.join(cwd, SKILLDB_DIR3), full));
695
+ totalLines += lines;
696
+ }
697
+ }
698
+ walk(activeDir);
699
+ return { files, totalLines, totalTokens: totalLines * TOKENS_PER_LINE };
700
+ }
701
+ function parseBudget(value) {
702
+ const lower = value.toLowerCase();
703
+ if (lower.endsWith("k")) {
704
+ return { max: parseInt(lower) * 1e3, unit: "tokens" };
705
+ }
706
+ const num = parseInt(value);
707
+ if (num > 1e4) return { max: num, unit: "tokens" };
708
+ return { max: num, unit: "lines" };
709
+ }
710
+ async function budgetCommand(action, value) {
711
+ const cwd = process.cwd();
712
+ const config = readConfig2(cwd);
713
+ const usage = countActiveSkills(cwd);
714
+ if (action === "set" && value) {
715
+ const budget = parseBudget(value);
716
+ config.budget = budget;
717
+ writeConfig2(cwd, config);
718
+ console.log(pc9.green(`Budget set to ${budget.max.toLocaleString()} ${budget.unit}`));
719
+ return;
720
+ }
721
+ if (action === "optimize") {
722
+ let walk2 = function(dir) {
723
+ for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
724
+ const full = path5.join(dir, entry.name);
725
+ if (entry.isDirectory()) {
726
+ walk2(full);
727
+ continue;
728
+ }
729
+ if (!entry.name.endsWith(".md")) continue;
730
+ const lines = fs5.readFileSync(full, "utf-8").split("\n").length;
731
+ skillFiles.push({ path: full, lines });
732
+ }
733
+ };
734
+ var walk = walk2;
735
+ if (!config.budget) {
736
+ console.error(pc9.red('No budget set. Run "skilldb budget set <value>" first.'));
737
+ process.exit(1);
738
+ }
739
+ const budgetValue = config.budget.unit === "tokens" ? usage.totalTokens : usage.totalLines;
740
+ if (budgetValue <= config.budget.max) {
741
+ console.log(pc9.green("Already within budget. No changes needed."));
742
+ return;
743
+ }
744
+ const activeDir = path5.join(cwd, SKILLDB_DIR3, ACTIVE_DIR2);
745
+ const skillFiles = [];
746
+ walk2(activeDir);
747
+ skillFiles.sort((a, b) => b.lines - a.lines);
748
+ let current = config.budget.unit === "tokens" ? usage.totalTokens : usage.totalLines;
749
+ let removed = 0;
750
+ for (const sf of skillFiles) {
751
+ if (current <= config.budget.max) break;
752
+ fs5.unlinkSync(sf.path);
753
+ current -= config.budget.unit === "tokens" ? sf.lines * TOKENS_PER_LINE : sf.lines;
754
+ removed++;
755
+ console.log(pc9.yellow(` removed ${path5.basename(sf.path)}`) + pc9.dim(` (${sf.lines} lines)`));
756
+ }
757
+ console.log(pc9.green(`
758
+ Optimized: removed ${removed} skill(s) to fit budget.`));
759
+ return;
760
+ }
761
+ console.log(pc9.bold("Budget Status\n"));
762
+ console.log(` ${pc9.cyan("Active skills:")} ${usage.files.length}`);
763
+ console.log(` ${pc9.cyan("Total lines:")} ${usage.totalLines.toLocaleString()}`);
764
+ console.log(` ${pc9.cyan("Est. tokens:")} ${usage.totalTokens.toLocaleString()}`);
765
+ if (config.budget) {
766
+ const budgetValue = config.budget.unit === "tokens" ? usage.totalTokens : usage.totalLines;
767
+ const pct = Math.round(budgetValue / config.budget.max * 100);
768
+ const color = pct > 100 ? pc9.red : pct > 80 ? pc9.yellow : pc9.green;
769
+ console.log(` ${pc9.cyan("Budget:")} ${config.budget.max.toLocaleString()} ${config.budget.unit}`);
770
+ console.log(` ${pc9.cyan("Usage:")} ${color(`${pct}%`)}`);
771
+ } else {
772
+ console.log(pc9.dim('\n No budget set. Use "skilldb budget set <value>" to set one.'));
773
+ }
774
+ }
775
+
776
+ // src/commands/slim.ts
777
+ import fs6 from "fs";
778
+ import path6 from "path";
779
+ import pc10 from "picocolors";
780
+ var SKILLDB_DIR4 = ".skilldb";
781
+ var ACTIVE_DIR3 = "active";
782
+ var SLIM_DIR = "slim";
783
+ var SKILLS_DIR2 = "skills";
784
+ function ensureDir2(dir) {
785
+ if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
786
+ }
787
+ function slimContent(content, ratio) {
788
+ const lines = content.split("\n");
789
+ const kept = [];
790
+ let inCodeBlock = false;
791
+ let codeBlockLines = 0;
792
+ let skipExplanation = false;
793
+ for (const line of lines) {
794
+ if (line.startsWith("#")) {
795
+ kept.push(line);
796
+ skipExplanation = false;
797
+ continue;
798
+ }
799
+ if (line.startsWith("```")) {
800
+ if (!inCodeBlock) {
801
+ inCodeBlock = true;
802
+ codeBlockLines = 0;
803
+ kept.push(line);
804
+ } else {
805
+ inCodeBlock = false;
806
+ kept.push(line);
807
+ }
808
+ continue;
809
+ }
810
+ if (inCodeBlock) {
811
+ codeBlockLines++;
812
+ if (codeBlockLines <= 15) kept.push(line);
813
+ else if (codeBlockLines === 16) kept.push(" // ... (trimmed)");
814
+ continue;
815
+ }
816
+ if (line.match(/^[-*]\s+\[[ x]\]/)) {
817
+ kept.push(line);
818
+ continue;
819
+ }
820
+ if (line.match(/^[-*]\s+\*\*/)) {
821
+ kept.push(line);
822
+ continue;
823
+ }
824
+ if (line.match(/^\d+\.\s+\*\*/)) {
825
+ kept.push(line);
826
+ continue;
827
+ }
828
+ if (line.match(/^[-*]\s+/) && line.length < 120) {
829
+ kept.push(line);
830
+ continue;
831
+ }
832
+ if (line.match(/^\d+\.\s+/) && line.length < 120) {
833
+ kept.push(line);
834
+ continue;
835
+ }
836
+ if (line.match(/^\*\*.+\*\*/)) {
837
+ kept.push(line);
838
+ continue;
839
+ }
840
+ if (line.trim() === "") {
841
+ if (!skipExplanation) kept.push(line);
842
+ continue;
843
+ }
844
+ if (kept.length / Math.max(lines.length, 1) < ratio) {
845
+ kept.push(line);
846
+ } else {
847
+ skipExplanation = true;
848
+ }
849
+ }
850
+ return kept.join("\n");
851
+ }
852
+ function processDir(sourceDir, destDir, ratio) {
853
+ ensureDir2(destDir);
854
+ let count = 0;
855
+ for (const entry of fs6.readdirSync(sourceDir, { withFileTypes: true })) {
856
+ const srcPath = path6.join(sourceDir, entry.name);
857
+ if (entry.isDirectory()) {
858
+ count += processDir(srcPath, path6.join(destDir, entry.name), ratio);
859
+ continue;
860
+ }
861
+ if (!entry.name.endsWith(".md")) continue;
862
+ const content = fs6.readFileSync(srcPath, "utf-8");
863
+ const slimmed = slimContent(content, ratio);
864
+ const footer = `
865
+
866
+ ---
867
+ *Full skill: \`skilldb get ${entry.name.replace(".md", "")}\`*
868
+ `;
869
+ ensureDir2(destDir);
870
+ fs6.writeFileSync(path6.join(destDir, entry.name), slimmed + footer);
871
+ count++;
872
+ const origLines = content.split("\n").length;
873
+ const slimLines = slimmed.split("\n").length;
874
+ console.log(
875
+ pc10.green(` slim ${entry.name}`) + pc10.dim(` ${origLines} \u2192 ${slimLines} lines (${Math.round(slimLines / origLines * 100)}%)`)
876
+ );
877
+ }
878
+ return count;
879
+ }
880
+ async function slimCommand(pack, options) {
881
+ const cwd = process.cwd();
882
+ const ratio = options?.ratio ? parseFloat(options.ratio) : 0.3;
883
+ if (ratio <= 0 || ratio >= 1) {
884
+ console.error(pc10.red("Ratio must be between 0 and 1 (e.g. 0.3 for 30%)."));
885
+ process.exit(1);
886
+ }
887
+ const slimDir = path6.join(cwd, SKILLDB_DIR4, SLIM_DIR);
888
+ console.log(pc10.bold(`Generating slim versions (${Math.round(ratio * 100)}% ratio)
889
+ `));
890
+ let sourceDir;
891
+ if (pack) {
892
+ sourceDir = path6.join(cwd, SKILLDB_DIR4, SKILLS_DIR2, pack);
893
+ if (!fs6.existsSync(sourceDir)) {
894
+ sourceDir = path6.join(cwd, SKILLDB_DIR4, ACTIVE_DIR3, pack);
895
+ }
896
+ if (!fs6.existsSync(sourceDir)) {
897
+ console.error(pc10.red(`Pack "${pack}" not found in skills/ or active/.`));
898
+ process.exit(1);
899
+ }
900
+ } else {
901
+ sourceDir = path6.join(cwd, SKILLDB_DIR4, ACTIVE_DIR3);
902
+ if (!fs6.existsSync(sourceDir)) {
903
+ sourceDir = path6.join(cwd, SKILLDB_DIR4, SKILLS_DIR2);
904
+ }
905
+ }
906
+ if (!fs6.existsSync(sourceDir)) {
907
+ console.error(pc10.red('No skills found. Install skills first with "skilldb add <pack>".'));
908
+ process.exit(1);
909
+ }
910
+ const destDir = pack ? path6.join(slimDir, pack) : slimDir;
911
+ const count = processDir(sourceDir, destDir, ratio);
912
+ console.log(pc10.green(`
913
+ ${count} skill(s) slimmed \u2192 .skilldb/slim/`));
914
+ }
915
+
916
+ // src/commands/recommend.ts
917
+ import fs7 from "fs";
918
+ import path7 from "path";
919
+ import pc11 from "picocolors";
920
+ var TECH_MAP = {
921
+ react: ["react-patterns-skills", "web-polish-skills"],
922
+ next: ["react-patterns-skills", "web-polish-skills", "nextjs-skills"],
923
+ vue: ["vue-skills", "web-polish-skills"],
924
+ angular: ["angular-skills", "web-polish-skills"],
925
+ express: ["software-skills", "api-design-skills", "nodejs-skills"],
926
+ fastify: ["software-skills", "api-design-skills", "nodejs-skills"],
927
+ nestjs: ["software-skills", "api-design-skills", "nodejs-skills"],
928
+ prisma: ["database-skills", "software-skills"],
929
+ typescript: ["typescript-skills", "software-skills"],
930
+ tailwindcss: ["css-skills", "web-polish-skills"],
931
+ docker: ["devops-skills", "docker-skills"],
932
+ openai: ["ai-agent-skills", "prompt-engineering-skills"],
933
+ "@anthropic-ai/sdk": ["ai-agent-skills", "prompt-engineering-skills"],
934
+ langchain: ["ai-agent-skills", "llm-skills"],
935
+ "react-native": ["mobile-skills", "react-native-skills"],
936
+ expo: ["mobile-skills", "react-native-skills"],
937
+ jest: ["testing-skills", "software-skills"],
938
+ vitest: ["testing-skills", "software-skills"]
939
+ };
940
+ function scanProject(cwd) {
941
+ const detected = [];
942
+ const packs = /* @__PURE__ */ new Set();
943
+ try {
944
+ const pkg = JSON.parse(fs7.readFileSync(path7.join(cwd, "package.json"), "utf-8"));
945
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
946
+ for (const dep of Object.keys(allDeps)) {
947
+ const key = dep.replace(/^@[^/]+\//, "");
948
+ if (TECH_MAP[dep]) {
949
+ detected.push(dep);
950
+ TECH_MAP[dep].forEach((p) => packs.add(p));
951
+ } else if (TECH_MAP[key]) {
952
+ detected.push(key);
953
+ TECH_MAP[key].forEach((p) => packs.add(p));
954
+ }
955
+ }
956
+ } catch {
957
+ }
958
+ if (fs7.existsSync(path7.join(cwd, "Dockerfile"))) {
959
+ detected.push("docker");
960
+ (TECH_MAP.docker || []).forEach((p) => packs.add(p));
961
+ }
962
+ if (fs7.existsSync(path7.join(cwd, "tsconfig.json"))) {
963
+ detected.push("typescript");
964
+ (TECH_MAP.typescript || []).forEach((p) => packs.add(p));
965
+ }
966
+ packs.add("software-skills");
967
+ return { detected, packs };
968
+ }
969
+ async function recommendCommand(options) {
970
+ const cwd = process.cwd();
971
+ const manifest = readManifest(cwd);
972
+ const installed = new Set(Object.keys(manifest.installed).map((id) => id.split("/")[0]));
973
+ console.log(pc11.bold("Scanning project...\n"));
974
+ const { detected, packs } = scanProject(cwd);
975
+ if (detected.length > 0) {
976
+ console.log(pc11.dim("Detected technologies: ") + detected.join(", "));
977
+ console.log();
978
+ }
979
+ const missing = [...packs].filter((p) => !installed.has(p));
980
+ const existing = [...packs].filter((p) => installed.has(p));
981
+ if (existing.length > 0) {
982
+ console.log(pc11.green("Already installed:"));
983
+ for (const p of existing) {
984
+ console.log(pc11.dim(` + ${p}`));
985
+ }
986
+ console.log();
987
+ }
988
+ if (missing.length === 0) {
989
+ console.log(pc11.green("You have all recommended packs installed!"));
990
+ return;
991
+ }
992
+ console.log(pc11.yellow("Recommended packs to install:"));
993
+ for (const p of missing) {
994
+ console.log(` ${pc11.cyan(p)}`);
995
+ }
996
+ if (!options.install) {
997
+ console.log(pc11.dim('\nRun "skilldb recommend --install" to auto-install all.'));
998
+ return;
999
+ }
1000
+ console.log(pc11.bold("\nInstalling recommended packs...\n"));
1001
+ const client = new SkillDBClient();
1002
+ initCache(cwd);
1003
+ for (const pack of missing) {
1004
+ try {
1005
+ const res = await client.list({ pack, limit: 500, includeContent: true });
1006
+ if (res.skills.length === 0) {
1007
+ console.log(pc11.dim(` skip ${pack} (not found)`));
1008
+ continue;
1009
+ }
1010
+ let added = 0;
1011
+ for (const skill of res.skills) {
1012
+ cacheSkill(skill, cwd);
1013
+ added++;
1014
+ }
1015
+ console.log(pc11.green(` add ${pack}`) + pc11.dim(` (${added} skills)`));
1016
+ } catch (err) {
1017
+ console.log(pc11.red(` fail ${pack}: ${err.message}`));
1018
+ }
1019
+ }
1020
+ console.log(pc11.green('\nDone! Run "skilldb use auto" to activate a profile.'));
1021
+ }
1022
+
1023
+ // src/commands/doctor.ts
1024
+ import fs8 from "fs";
1025
+ import path8 from "path";
1026
+ import pc12 from "picocolors";
1027
+ var SKILLDB_DIR5 = ".skilldb";
1028
+ var SKILLS_DIR3 = "skills";
1029
+ var ACTIVE_DIR4 = "active";
1030
+ var CONFIG_FILE3 = "config.json";
1031
+ var TOKENS_PER_LINE2 = 10;
1032
+ function readConfig3(cwd) {
1033
+ const p = path8.join(cwd, SKILLDB_DIR5, CONFIG_FILE3);
1034
+ try {
1035
+ return JSON.parse(fs8.readFileSync(p, "utf-8"));
1036
+ } catch {
1037
+ return {};
1038
+ }
1039
+ }
1040
+ function collectFiles(dir) {
1041
+ const result = [];
1042
+ if (!fs8.existsSync(dir)) return result;
1043
+ function walk(d) {
1044
+ for (const entry of fs8.readdirSync(d, { withFileTypes: true })) {
1045
+ const full = path8.join(d, entry.name);
1046
+ if (entry.isDirectory()) {
1047
+ walk(full);
1048
+ continue;
1049
+ }
1050
+ if (!entry.name.endsWith(".md")) continue;
1051
+ const lines = fs8.readFileSync(full, "utf-8").split("\n").length;
1052
+ result.push({ name: path8.relative(dir, full), lines });
1053
+ }
1054
+ }
1055
+ walk(dir);
1056
+ return result;
1057
+ }
1058
+ async function doctorCommand() {
1059
+ const cwd = process.cwd();
1060
+ const manifest = readManifest(cwd);
1061
+ const config = readConfig3(cwd);
1062
+ let issues = 0;
1063
+ console.log(pc12.bold("SkillDB Doctor\n"));
1064
+ const skilldbDir = path8.join(cwd, SKILLDB_DIR5);
1065
+ if (!fs8.existsSync(skilldbDir)) {
1066
+ console.log(pc12.red(' [!] .skilldb/ not found. Run "skilldb init" first.'));
1067
+ return;
1068
+ }
1069
+ console.log(pc12.green(" [ok] .skilldb/ directory exists"));
1070
+ const installedIds = Object.keys(manifest.installed);
1071
+ const skillsDir = path8.join(cwd, SKILLDB_DIR5, SKILLS_DIR3);
1072
+ const skillFiles = collectFiles(skillsDir);
1073
+ console.log(pc12.green(` [ok] ${installedIds.length} skills in manifest, ${skillFiles.length} files on disk`));
1074
+ let orphaned = 0;
1075
+ for (const id of installedIds) {
1076
+ const [pack, file] = id.split("/");
1077
+ const name = file.replace(".md", "").replace(/[/\\:*?"<>|]/g, "-");
1078
+ const filePath = path8.join(skillsDir, pack, `${name}.md`);
1079
+ if (!fs8.existsSync(filePath)) {
1080
+ orphaned++;
1081
+ if (orphaned <= 3) console.log(pc12.yellow(` [!] Missing file for manifest entry: ${id}`));
1082
+ }
1083
+ }
1084
+ if (orphaned > 3) console.log(pc12.yellow(` [!] ...and ${orphaned - 3} more missing files`));
1085
+ if (orphaned > 0) issues++;
1086
+ const activeDir = path8.join(cwd, SKILLDB_DIR5, ACTIVE_DIR4);
1087
+ const activeFiles = collectFiles(activeDir);
1088
+ if (config.activeProfile) {
1089
+ console.log(pc12.green(` [ok] Active profile: ${config.activeProfile} (${activeFiles.length} skills)`));
1090
+ } else {
1091
+ console.log(pc12.yellow(' [!] No active profile. Run "skilldb use <profile>" to set one.'));
1092
+ issues++;
1093
+ }
1094
+ const activeNames = new Set(activeFiles.map((f) => f.name));
1095
+ const unused = skillFiles.filter((f) => !activeNames.has(f.name));
1096
+ if (unused.length > 0 && config.activeProfile) {
1097
+ console.log(pc12.yellow(` [!] ${unused.length} installed skill(s) not in active profile`));
1098
+ issues++;
1099
+ }
1100
+ const totalLines = skillFiles.reduce((sum, f) => sum + f.lines, 0);
1101
+ const activeLines = activeFiles.reduce((sum, f) => sum + f.lines, 0);
1102
+ const totalTokens = totalLines * TOKENS_PER_LINE2;
1103
+ const activeTokens = activeLines * TOKENS_PER_LINE2;
1104
+ console.log(pc12.dim("\n Summary:"));
1105
+ console.log(` Installed: ${totalLines.toLocaleString()} lines (~${totalTokens.toLocaleString()} tokens)`);
1106
+ console.log(` Active: ${activeLines.toLocaleString()} lines (~${activeTokens.toLocaleString()} tokens)`);
1107
+ if (config.budget) {
1108
+ const budgetValue = config.budget.unit === "tokens" ? activeTokens : activeLines;
1109
+ const pct = Math.round(budgetValue / config.budget.max * 100);
1110
+ const color = pct > 100 ? pc12.red : pct > 80 ? pc12.yellow : pc12.green;
1111
+ console.log(` Budget: ${color(`${pct}%`)} of ${config.budget.max.toLocaleString()} ${config.budget.unit}`);
1112
+ if (pct > 100) issues++;
1113
+ }
1114
+ const packs = new Set(installedIds.map((id) => id.split("/")[0]));
1115
+ console.log(` Packs: ${packs.size} installed`);
1116
+ console.log(
1117
+ issues === 0 ? pc12.green("\nNo issues found!") : pc12.yellow(`
1118
+ ${issues} issue(s) found. Run suggested commands to fix.`)
1119
+ );
1120
+ }
1121
+
1122
+ // src/commands/update.ts
1123
+ import fs9 from "fs";
1124
+ import path9 from "path";
1125
+ import pc13 from "picocolors";
1126
+ var SKILLDB_DIR6 = ".skilldb";
1127
+ var SKILLS_DIR4 = "skills";
1128
+ async function updateCommand(pack) {
1129
+ const cwd = process.cwd();
1130
+ const client = new SkillDBClient();
1131
+ const manifest = readManifest(cwd);
1132
+ const installedIds = Object.keys(manifest.installed);
1133
+ if (installedIds.length === 0) {
1134
+ console.log(pc13.yellow('No skills installed. Use "skilldb add <pack>" first.'));
1135
+ return;
1136
+ }
1137
+ const targetIds = pack ? installedIds.filter((id) => id.startsWith(pack + "/")) : installedIds;
1138
+ if (targetIds.length === 0) {
1139
+ console.error(pc13.red(`No installed skills found for pack "${pack}".`));
1140
+ process.exit(1);
1141
+ }
1142
+ console.log(pc13.bold(`Updating ${targetIds.length} skill(s)...
1143
+ `));
1144
+ const packGroups = /* @__PURE__ */ new Map();
1145
+ for (const id of targetIds) {
1146
+ const p = id.split("/")[0];
1147
+ if (!packGroups.has(p)) packGroups.set(p, []);
1148
+ packGroups.get(p).push(id);
1149
+ }
1150
+ let updated = 0;
1151
+ let unchanged = 0;
1152
+ let failed = 0;
1153
+ for (const [packName, ids] of packGroups) {
1154
+ try {
1155
+ const res = await client.list({ pack: packName, limit: 500, includeContent: true });
1156
+ const remoteMap = new Map(res.skills.map((s) => [s.id, s]));
1157
+ for (const id of ids) {
1158
+ const remote = remoteMap.get(id);
1159
+ if (!remote) {
1160
+ console.log(pc13.dim(` skip ${id} (not found on remote)`));
1161
+ continue;
1162
+ }
1163
+ const localPath = path9.join(
1164
+ cwd,
1165
+ SKILLDB_DIR6,
1166
+ SKILLS_DIR4,
1167
+ packName,
1168
+ id.split("/")[1].replace(".md", "").replace(/[/\\:*?"<>|]/g, "-") + ".md"
1169
+ );
1170
+ let localContent = "";
1171
+ try {
1172
+ localContent = fs9.readFileSync(localPath, "utf-8");
1173
+ } catch {
1174
+ }
1175
+ if (remote.content && remote.content !== localContent) {
1176
+ const oldLines = localContent.split("\n").length;
1177
+ const newLines = remote.content.split("\n").length;
1178
+ const diff = newLines - oldLines;
1179
+ const diffStr = diff > 0 ? `+${diff}` : `${diff}`;
1180
+ cacheSkill(remote, cwd);
1181
+ updated++;
1182
+ console.log(
1183
+ pc13.green(` update ${id}`) + pc13.dim(` (${diffStr} lines)`)
1184
+ );
1185
+ } else {
1186
+ unchanged++;
1187
+ }
1188
+ }
1189
+ } catch (err) {
1190
+ failed += ids.length;
1191
+ console.log(pc13.red(` fail ${packName}: ${err.message}`));
1192
+ }
1193
+ }
1194
+ console.log(
1195
+ `
1196
+ ${pc13.green(`${updated} updated`)}` + pc13.dim(`, ${unchanged} unchanged`) + (failed > 0 ? pc13.red(`, ${failed} failed`) : "")
1197
+ );
1198
+ }
1199
+
1200
+ // src/commands/remove.ts
1201
+ import fs10 from "fs";
1202
+ import path10 from "path";
1203
+ import pc14 from "picocolors";
1204
+ var SKILLDB_DIR7 = ".skilldb";
1205
+ var SKILLS_DIR5 = "skills";
1206
+ var ACTIVE_DIR5 = "active";
1207
+ function collectActiveSkills(cwd) {
1208
+ const activeDir = path10.join(cwd, SKILLDB_DIR7, ACTIVE_DIR5);
1209
+ const result = /* @__PURE__ */ new Set();
1210
+ if (!fs10.existsSync(activeDir)) return result;
1211
+ for (const pack of fs10.readdirSync(activeDir, { withFileTypes: true })) {
1212
+ if (!pack.isDirectory()) continue;
1213
+ for (const file of fs10.readdirSync(path10.join(activeDir, pack.name))) {
1214
+ if (file.endsWith(".md")) {
1215
+ result.add(`${pack.name}/${file}`);
1216
+ }
1217
+ }
1218
+ }
1219
+ return result;
1220
+ }
1221
+ function removeSkillFile(cwd, id) {
1222
+ const [pack, file] = id.split("/");
1223
+ const name = file.replace(".md", "").replace(/[/\\:*?"<>|]/g, "-");
1224
+ const filePath = path10.join(cwd, SKILLDB_DIR7, SKILLS_DIR5, pack, `${name}.md`);
1225
+ if (fs10.existsSync(filePath)) {
1226
+ fs10.unlinkSync(filePath);
1227
+ const packDir = path10.join(cwd, SKILLDB_DIR7, SKILLS_DIR5, pack);
1228
+ if (fs10.existsSync(packDir) && fs10.readdirSync(packDir).length === 0) {
1229
+ fs10.rmdirSync(packDir);
1230
+ }
1231
+ return true;
1232
+ }
1233
+ return false;
1234
+ }
1235
+ async function removeCommand(target, options) {
1236
+ const cwd = process.cwd();
1237
+ const manifest = readManifest(cwd);
1238
+ if (options.unused) {
1239
+ const activeSkills2 = collectActiveSkills(cwd);
1240
+ const installedIds = Object.keys(manifest.installed);
1241
+ let removed = 0;
1242
+ for (const id2 of installedIds) {
1243
+ const [pack2, file2] = id2.split("/");
1244
+ const name2 = file2.replace(".md", "").replace(/[/\\:*?"<>|]/g, "-") + ".md";
1245
+ const key = `${pack2}/${name2}`;
1246
+ if (!activeSkills2.has(key)) {
1247
+ removeSkillFile(cwd, id2);
1248
+ delete manifest.installed[id2];
1249
+ removed++;
1250
+ console.log(pc14.yellow(` remove ${id2}`));
1251
+ }
1252
+ }
1253
+ writeManifest(manifest, cwd);
1254
+ console.log(
1255
+ removed > 0 ? pc14.green(`
1256
+ ${removed} unused skill(s) removed.`) : pc14.dim("No unused skills found.")
1257
+ );
1258
+ return;
1259
+ }
1260
+ if (!target.includes("/")) {
1261
+ const packIds = Object.keys(manifest.installed).filter((id2) => id2.startsWith(target + "/"));
1262
+ if (packIds.length === 0) {
1263
+ console.error(pc14.red(`No installed skills found for "${target}".`));
1264
+ process.exit(1);
1265
+ }
1266
+ const activeSkills2 = collectActiveSkills(cwd);
1267
+ let activeWarning = false;
1268
+ for (const id2 of packIds) {
1269
+ const [pack2, file2] = id2.split("/");
1270
+ const name2 = file2.replace(".md", "").replace(/[/\\:*?"<>|]/g, "-") + ".md";
1271
+ if (activeSkills2.has(`${pack2}/${name2}`)) activeWarning = true;
1272
+ removeSkillFile(cwd, id2);
1273
+ delete manifest.installed[id2];
1274
+ console.log(pc14.yellow(` remove ${id2}`));
1275
+ }
1276
+ writeManifest(manifest, cwd);
1277
+ console.log(pc14.green(`
1278
+ ${packIds.length} skill(s) from "${target}" removed.`));
1279
+ if (activeWarning) {
1280
+ console.log(pc14.yellow("Warning: Some removed skills were in your active profile."));
1281
+ console.log(pc14.yellow('Run "skilldb use <profile>" to refresh.'));
1282
+ }
1283
+ return;
1284
+ }
1285
+ let id = target;
1286
+ if (!id.includes(".md")) id = id + ".md";
1287
+ if (!(id in manifest.installed)) {
1288
+ console.error(pc14.red(`Skill "${id}" is not installed.`));
1289
+ process.exit(1);
1290
+ }
1291
+ const activeSkills = collectActiveSkills(cwd);
1292
+ const [pack, file] = id.split("/");
1293
+ const name = file.replace(".md", "").replace(/[/\\:*?"<>|]/g, "-") + ".md";
1294
+ removeSkillFile(cwd, id);
1295
+ delete manifest.installed[id];
1296
+ writeManifest(manifest, cwd);
1297
+ console.log(pc14.green(`Removed ${id}`));
1298
+ if (activeSkills.has(`${pack}/${name}`)) {
1299
+ console.log(pc14.yellow('Warning: This skill was in your active profile. Run "skilldb use <profile>" to refresh.'));
1300
+ }
1301
+ }
1302
+
1303
+ // src/commands/export.ts
1304
+ import fs11 from "fs";
1305
+ import path11 from "path";
1306
+ import pc15 from "picocolors";
1307
+ var SKILLDB_DIR8 = ".skilldb";
1308
+ var ACTIVE_DIR6 = "active";
1309
+ var CONFIG_FILE4 = "config.json";
1310
+ function readConfig4(cwd) {
1311
+ const p = path11.join(cwd, SKILLDB_DIR8, CONFIG_FILE4);
1312
+ try {
1313
+ return JSON.parse(fs11.readFileSync(p, "utf-8"));
1314
+ } catch {
1315
+ return {};
1316
+ }
1317
+ }
1318
+ function collectActiveSkills2(cwd) {
1319
+ const activeDir = path11.join(cwd, SKILLDB_DIR8, ACTIVE_DIR6);
1320
+ const result = [];
1321
+ if (!fs11.existsSync(activeDir)) return result;
1322
+ for (const pack of fs11.readdirSync(activeDir, { withFileTypes: true })) {
1323
+ if (!pack.isDirectory()) continue;
1324
+ for (const file of fs11.readdirSync(path11.join(activeDir, pack.name))) {
1325
+ if (file.endsWith(".md")) {
1326
+ result.push({
1327
+ pack: pack.name,
1328
+ name: file.replace(".md", ""),
1329
+ path: path11.join(activeDir, pack.name, file)
1330
+ });
1331
+ }
1332
+ }
1333
+ }
1334
+ return result;
1335
+ }
1336
+ function exportClaude(cwd) {
1337
+ const skills = collectActiveSkills2(cwd);
1338
+ if (skills.length === 0) {
1339
+ console.error(pc15.red('No active skills. Run "skilldb use <profile>" first.'));
1340
+ process.exit(1);
1341
+ }
1342
+ const lines = ["<!-- skilldb:start -->", "## SkillDB Active Skills\n"];
1343
+ lines.push("Reference these skills from `.skilldb/active/` when working on tasks:\n");
1344
+ for (const s of skills) {
1345
+ lines.push(`- \`.skilldb/active/${s.pack}/${s.name}.md\``);
1346
+ }
1347
+ lines.push("", "<!-- skilldb:end -->");
1348
+ const output = lines.join("\n");
1349
+ console.log(output);
1350
+ console.log(pc15.dim("\n--- Copy the above into your CLAUDE.md ---"));
1351
+ }
1352
+ function exportCursor(cwd) {
1353
+ const skills = collectActiveSkills2(cwd);
1354
+ if (skills.length === 0) {
1355
+ console.error(pc15.red('No active skills. Run "skilldb use <profile>" first.'));
1356
+ process.exit(1);
1357
+ }
1358
+ const lines = ["# SkillDB Active Skills", ""];
1359
+ lines.push("Reference these skills from `.skilldb/active/` when working on tasks:", "");
1360
+ for (const s of skills) {
1361
+ lines.push(`@file .skilldb/active/${s.pack}/${s.name}.md`);
1362
+ }
1363
+ const output = lines.join("\n");
1364
+ console.log(output);
1365
+ console.log(pc15.dim("\n--- Copy the above into your .cursorrules ---"));
1366
+ }
1367
+ function exportProfile(cwd) {
1368
+ const config = readConfig4(cwd);
1369
+ const manifest = readManifest(cwd);
1370
+ const profile = {
1371
+ profile: config.activeProfile || "custom",
1372
+ budget: config.budget || null,
1373
+ skills: Object.keys(manifest.installed),
1374
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString()
1375
+ };
1376
+ const filename = ".skilldb-profile.json";
1377
+ fs11.writeFileSync(path11.join(cwd, filename), JSON.stringify(profile, null, 2) + "\n");
1378
+ console.log(pc15.green(`Exported profile to ${filename}`));
1379
+ console.log(pc15.dim('Share this file and import with "skilldb use --import .skilldb-profile.json"'));
1380
+ }
1381
+ function exportInject(cwd, id) {
1382
+ if (!id.includes(".md")) id = id + ".md";
1383
+ const cachedPath = getCachedPath(id, cwd);
1384
+ if (!cachedPath) {
1385
+ console.error(pc15.red(`Skill "${id}" not found locally. Run "skilldb get ${id}" first.`));
1386
+ process.exit(1);
1387
+ }
1388
+ const content = fs11.readFileSync(cachedPath, "utf-8");
1389
+ process.stdout.write(content);
1390
+ }
1391
+ async function exportCommand(format, target) {
1392
+ const cwd = process.cwd();
1393
+ switch (format) {
1394
+ case "claude":
1395
+ exportClaude(cwd);
1396
+ break;
1397
+ case "cursor":
1398
+ exportCursor(cwd);
1399
+ break;
1400
+ case "profile":
1401
+ exportProfile(cwd);
1402
+ break;
1403
+ case "inject":
1404
+ if (!target) {
1405
+ console.error(pc15.red("Usage: skilldb export inject <skill-id>"));
1406
+ process.exit(1);
1407
+ }
1408
+ exportInject(cwd, target);
1409
+ break;
1410
+ default:
1411
+ console.error(pc15.red(`Unknown format "${format}". Use: claude, cursor, profile, inject`));
1412
+ process.exit(1);
1413
+ }
1414
+ }
1415
+
1416
+ // src/commands/stats.ts
1417
+ import fs12 from "fs";
1418
+ import path12 from "path";
1419
+ import pc16 from "picocolors";
1420
+ var SKILLDB_DIR9 = ".skilldb";
1421
+ var SKILLS_DIR6 = "skills";
1422
+ var ACTIVE_DIR7 = "active";
1423
+ var CONFIG_FILE5 = "config.json";
1424
+ var TOKENS_PER_LINE3 = 10;
1425
+ function readConfig5(cwd) {
1426
+ const p = path12.join(cwd, SKILLDB_DIR9, CONFIG_FILE5);
1427
+ try {
1428
+ return JSON.parse(fs12.readFileSync(p, "utf-8"));
1429
+ } catch {
1430
+ return {};
1431
+ }
1432
+ }
1433
+ function countDir(dir) {
1434
+ const result = { files: 0, lines: 0, packs: /* @__PURE__ */ new Set(), categories: /* @__PURE__ */ new Set() };
1435
+ if (!fs12.existsSync(dir)) return result;
1436
+ for (const pack of fs12.readdirSync(dir, { withFileTypes: true })) {
1437
+ if (!pack.isDirectory()) continue;
1438
+ result.packs.add(pack.name);
1439
+ const cat = pack.name.replace(/-skills$/, "");
1440
+ result.categories.add(cat);
1441
+ for (const file of fs12.readdirSync(path12.join(dir, pack.name))) {
1442
+ if (!file.endsWith(".md")) continue;
1443
+ result.files++;
1444
+ result.lines += fs12.readFileSync(path12.join(dir, pack.name, file), "utf-8").split("\n").length;
1445
+ }
1446
+ }
1447
+ return result;
1448
+ }
1449
+ async function statsCommand() {
1450
+ const cwd = process.cwd();
1451
+ const config = readConfig5(cwd);
1452
+ const manifest = readManifest(cwd);
1453
+ const skillsDir = path12.join(cwd, SKILLDB_DIR9, SKILLS_DIR6);
1454
+ const activeDir = path12.join(cwd, SKILLDB_DIR9, ACTIVE_DIR7);
1455
+ const installed = countDir(skillsDir);
1456
+ const active = countDir(activeDir);
1457
+ console.log(pc16.bold("SkillDB Stats\n"));
1458
+ console.log(pc16.cyan("Installed:"));
1459
+ console.log(` Skills: ${installed.files}`);
1460
+ console.log(` Packs: ${installed.packs.size}` + (installed.packs.size > 0 ? pc16.dim(` (${[...installed.packs].join(", ")})`) : ""));
1461
+ console.log(` Lines: ${installed.lines.toLocaleString()}`);
1462
+ console.log(` Est tokens: ${(installed.lines * TOKENS_PER_LINE3).toLocaleString()}`);
1463
+ console.log(pc16.cyan("\nActive:"));
1464
+ if (config.activeProfile) {
1465
+ console.log(` Profile: ${config.activeProfile}`);
1466
+ } else {
1467
+ console.log(` Profile: ${pc16.dim("none")}`);
1468
+ }
1469
+ console.log(` Skills: ${active.files}`);
1470
+ console.log(` Lines: ${active.lines.toLocaleString()}`);
1471
+ console.log(` Est tokens: ${(active.lines * TOKENS_PER_LINE3).toLocaleString()}`);
1472
+ if (config.budget) {
1473
+ const budgetValue = config.budget.unit === "tokens" ? active.lines * TOKENS_PER_LINE3 : active.lines;
1474
+ const pct = Math.round(budgetValue / config.budget.max * 100);
1475
+ const color = pct > 100 ? pc16.red : pct > 80 ? pc16.yellow : pc16.green;
1476
+ console.log(pc16.cyan("\nBudget:"));
1477
+ console.log(` Limit: ${config.budget.max.toLocaleString()} ${config.budget.unit}`);
1478
+ console.log(` Usage: ${color(`${pct}%`)}`);
1479
+ }
1480
+ console.log(pc16.cyan("\nCoverage:"));
1481
+ console.log(` Categories: ${installed.categories.size} covered`);
1482
+ const manifestPacks = new Set(Object.keys(manifest.installed).map((id) => id.split("/")[0]));
1483
+ const activePacks = active.packs;
1484
+ const unusedPacks = [...manifestPacks].filter((p) => !activePacks.has(p));
1485
+ if (unusedPacks.length > 0) {
1486
+ console.log(pc16.yellow(`
1487
+ Installed but not active: ${unusedPacks.join(", ")}`));
1488
+ }
1489
+ }
1490
+
1491
+ // src/commands/diff.ts
1492
+ import fs13 from "fs";
1493
+ import path13 from "path";
1494
+ import pc17 from "picocolors";
1495
+ var SKILLDB_DIR10 = ".skilldb";
1496
+ var SKILLS_DIR7 = "skills";
1497
+ function diffLines(local, remote) {
1498
+ const localLines = local.split("\n");
1499
+ const remoteLines = remote.split("\n");
1500
+ let added = 0;
1501
+ let removed = 0;
1502
+ const localSet = new Set(localLines);
1503
+ const remoteSet = new Set(remoteLines);
1504
+ for (const line of remoteLines) {
1505
+ if (!localSet.has(line)) added++;
1506
+ }
1507
+ for (const line of localLines) {
1508
+ if (!remoteSet.has(line)) removed++;
1509
+ }
1510
+ return { added, removed, changed: added > 0 || removed > 0 };
1511
+ }
1512
+ function showDiff(id, local, remote) {
1513
+ const result = diffLines(local, remote);
1514
+ if (!result.changed) {
1515
+ console.log(pc17.dim(` ${id}: up to date`));
1516
+ return;
1517
+ }
1518
+ console.log(pc17.bold(` ${id}:`));
1519
+ if (result.added > 0) console.log(pc17.green(` +${result.added} lines added`));
1520
+ if (result.removed > 0) console.log(pc17.red(` -${result.removed} lines removed`));
1521
+ const localLines = local.split("\n");
1522
+ const remoteLines = remote.split("\n");
1523
+ const localSet = new Set(localLines);
1524
+ const remoteSet = new Set(remoteLines);
1525
+ let shown = 0;
1526
+ for (const line of remoteLines) {
1527
+ if (!localSet.has(line) && line.trim()) {
1528
+ console.log(pc17.green(` + ${line.slice(0, 80)}`));
1529
+ if (++shown >= 3) break;
1530
+ }
1531
+ }
1532
+ shown = 0;
1533
+ for (const line of localLines) {
1534
+ if (!remoteSet.has(line) && line.trim()) {
1535
+ console.log(pc17.red(` - ${line.slice(0, 80)}`));
1536
+ if (++shown >= 3) break;
1537
+ }
1538
+ }
1539
+ }
1540
+ async function diffCommand(target) {
1541
+ const cwd = process.cwd();
1542
+ const client = new SkillDBClient();
1543
+ const manifest = readManifest(cwd);
1544
+ const installedIds = Object.keys(manifest.installed);
1545
+ if (installedIds.length === 0) {
1546
+ console.log(pc17.yellow("No skills installed."));
1547
+ return;
1548
+ }
1549
+ if (target) {
1550
+ let id = target;
1551
+ if (!id.includes(".md")) id = id + ".md";
1552
+ const [pack, file] = id.split("/");
1553
+ const name = file.replace(".md", "").replace(/[/\\:*?"<>|]/g, "-");
1554
+ const localPath = path13.join(cwd, SKILLDB_DIR10, SKILLS_DIR7, pack, `${name}.md`);
1555
+ if (!fs13.existsSync(localPath)) {
1556
+ console.error(pc17.red(`Skill "${id}" not found locally.`));
1557
+ process.exit(1);
1558
+ }
1559
+ console.log(pc17.bold("Comparing with remote...\n"));
1560
+ try {
1561
+ const remote = await client.get(id);
1562
+ const localContent = fs13.readFileSync(localPath, "utf-8");
1563
+ if (!remote.content) {
1564
+ console.log(pc17.yellow("Remote content not available (API key required)."));
1565
+ return;
1566
+ }
1567
+ showDiff(id, localContent, remote.content);
1568
+ } catch (err) {
1569
+ console.error(pc17.red(`Error: ${err.message}`));
1570
+ process.exit(1);
1571
+ }
1572
+ return;
1573
+ }
1574
+ console.log(pc17.bold(`Comparing ${installedIds.length} skill(s) with remote...
1575
+ `));
1576
+ const packGroups = /* @__PURE__ */ new Map();
1577
+ for (const id of installedIds) {
1578
+ const p = id.split("/")[0];
1579
+ if (!packGroups.has(p)) packGroups.set(p, []);
1580
+ packGroups.get(p).push(id);
1581
+ }
1582
+ let changed = 0;
1583
+ let upToDate = 0;
1584
+ for (const [packName, ids] of packGroups) {
1585
+ try {
1586
+ const res = await client.list({ pack: packName, limit: 500, includeContent: true });
1587
+ const remoteMap = new Map(res.skills.map((s) => [s.id, s]));
1588
+ for (const id of ids) {
1589
+ const remote = remoteMap.get(id);
1590
+ if (!remote?.content) continue;
1591
+ const [pack, file] = id.split("/");
1592
+ const name = file.replace(".md", "").replace(/[/\\:*?"<>|]/g, "-");
1593
+ const localPath = path13.join(cwd, SKILLDB_DIR10, SKILLS_DIR7, pack, `${name}.md`);
1594
+ let localContent = "";
1595
+ try {
1596
+ localContent = fs13.readFileSync(localPath, "utf-8");
1597
+ } catch {
1598
+ continue;
1599
+ }
1600
+ const result = diffLines(localContent, remote.content);
1601
+ if (result.changed) {
1602
+ showDiff(id, localContent, remote.content);
1603
+ changed++;
1604
+ } else {
1605
+ upToDate++;
1606
+ }
1607
+ }
1608
+ } catch (err) {
1609
+ console.log(pc17.red(` Error fetching ${packName}: ${err.message}`));
1610
+ }
1611
+ }
1612
+ console.log(`
1613
+ ${pc17.green(`${upToDate} up to date`)}` + (changed > 0 ? pc17.yellow(`, ${changed} changed`) : ""));
1614
+ if (changed > 0) {
1615
+ console.log(pc17.dim('Run "skilldb update" to pull latest versions.'));
1616
+ }
1617
+ }
1618
+
540
1619
  // src/cli.ts
541
1620
  var program = new Command();
542
- program.name("skilldb").description("SkillDB CLI \u2014 discover, install, and manage AI agent skills").version("0.1.4");
1621
+ program.name("skilldb").description("SkillDB CLI \u2014 discover, install, and manage AI agent skills").version("0.1.5");
543
1622
  program.command("init").description("Initialize SkillDB in this project (detect IDE, create .skilldb/)").action(initCommand);
544
1623
  program.command("login").description("Save your SkillDB API key").action(loginCommand);
545
1624
  program.command("search <query...>").description("Search skills by keyword(s)").option("-c, --category <name>", "Filter by category").option("-l, --limit <n>", "Max results", "20").action((queryParts, options) => {
@@ -549,5 +1628,32 @@ program.command("list").description("List skills, optionally filtered").option("
549
1628
  program.command("get <id>").description('Download a single skill to .skilldb/skills/ (e.g. "software-skills/code-review")').action(getCommand);
550
1629
  program.command("add <pack>").description("Download an entire skill pack to local .skilldb/skills/ cache").action(addCommand);
551
1630
  program.command("info <id>").description('Show metadata and preview for a skill (e.g. "software-skills/code-review")').action(infoCommand);
1631
+ program.command("use [profile]").description("Activate a skill profile (frontend, backend, devops, security, data, fullstack, mobile, ai-agent, auto, none)").option("--list", "List available profiles").option("--current", "Show active profile").action((profile, options) => {
1632
+ if (!profile && !options.list && !options.current) {
1633
+ options.list = true;
1634
+ }
1635
+ useCommand(profile || "", options);
1636
+ });
1637
+ program.command("budget [action] [value]").description("Manage token/line budget for active skills").action((action, value) => {
1638
+ budgetCommand(action, value);
1639
+ });
1640
+ program.command("slim [pack]").description("Generate compressed skill summaries").option("-r, --ratio <ratio>", "Keep ratio (0-1, default 0.3)", "0.3").action((pack, options) => {
1641
+ slimCommand(pack, options);
1642
+ });
1643
+ program.command("recommend").description("Scan project and suggest skill packs").option("-i, --install", "Auto-install recommendations").action(recommendCommand);
1644
+ program.command("doctor").description("Run health check and audit on installed skills").action(doctorCommand);
1645
+ program.command("update [pack]").description("Update installed skills from remote").action((pack) => {
1646
+ updateCommand(pack);
1647
+ });
1648
+ program.command("remove <target>").description('Remove a skill or pack (e.g. "software-skills/code-review" or "software-skills")').option("--unused", "Remove skills not in active profile").action((target, options) => {
1649
+ removeCommand(target, options);
1650
+ });
1651
+ program.command("export <format> [target]").description("Export skills (formats: claude, cursor, profile, inject)").action((format, target) => {
1652
+ exportCommand(format, target);
1653
+ });
1654
+ program.command("stats").description("Show local statistics and coverage").action(statsCommand);
1655
+ program.command("diff [target]").description("Compare local skills with latest remote versions").action((target) => {
1656
+ diffCommand(target);
1657
+ });
552
1658
  program.parse();
553
1659
  //# sourceMappingURL=cli.js.map