skilldb 0.4.3 → 0.5.2
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 +7 -4
- package/dist/cli.js +492 -186
- package/dist/cli.js.map +1 -1
- package/dist/mcp.js +152 -9
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -221,6 +221,8 @@ function truncate2(str, max) {
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
// src/commands/add.ts
|
|
224
|
+
import fs3 from "fs";
|
|
225
|
+
import path3 from "path";
|
|
224
226
|
import pc3 from "picocolors";
|
|
225
227
|
|
|
226
228
|
// src/cache.ts
|
|
@@ -306,9 +308,38 @@ function updateGitignore(cwd) {
|
|
|
306
308
|
}
|
|
307
309
|
|
|
308
310
|
// src/commands/add.ts
|
|
309
|
-
|
|
311
|
+
function getConfig() {
|
|
312
|
+
try {
|
|
313
|
+
return JSON.parse(fs3.readFileSync(path3.join(process.cwd(), ".skilldb", "config.json"), "utf-8"));
|
|
314
|
+
} catch {
|
|
315
|
+
return {};
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function getSkillsDir() {
|
|
319
|
+
const config = getConfig();
|
|
320
|
+
const dir = config.skillsDir;
|
|
321
|
+
if (dir && fs3.existsSync(path3.dirname(dir))) return dir;
|
|
322
|
+
const cwd = process.cwd();
|
|
323
|
+
if (fs3.existsSync(path3.join(cwd, ".claude"))) return path3.join(cwd, ".claude", "skills", "skilldb");
|
|
324
|
+
if (fs3.existsSync(path3.join(cwd, ".cursor"))) return path3.join(cwd, ".cursor", "rules", "skilldb");
|
|
325
|
+
if (fs3.existsSync(path3.join(cwd, ".codex"))) return path3.join(cwd, ".codex", "skills", "skilldb");
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
async function addCommand(packName, options) {
|
|
310
329
|
const client = new SkillDBClient();
|
|
330
|
+
const target = options?.target || getConfig().installMode || "cache";
|
|
311
331
|
console.log(pc3.bold(`Adding pack: ${packName}`));
|
|
332
|
+
const useSkillsDir = target === "skills-dir" || target === "hybrid";
|
|
333
|
+
const skillsDir = useSkillsDir ? getSkillsDir() : null;
|
|
334
|
+
if (useSkillsDir && !skillsDir) {
|
|
335
|
+
console.error(pc3.red('No IDE skills directory found. Run "skilldb init" first.'));
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
if (useSkillsDir && skillsDir) {
|
|
339
|
+
console.log(`Target: ${pc3.cyan(skillsDir)} ${pc3.dim("(IDE auto-loads these)")}`);
|
|
340
|
+
} else {
|
|
341
|
+
console.log(`Target: ${pc3.dim(".skilldb/skills/ (cache)")}`);
|
|
342
|
+
}
|
|
312
343
|
initCache();
|
|
313
344
|
try {
|
|
314
345
|
const res = await client.list({ pack: packName, limit: 500, includeContent: true });
|
|
@@ -319,19 +350,40 @@ async function addCommand(packName) {
|
|
|
319
350
|
let added = 0;
|
|
320
351
|
let skipped = 0;
|
|
321
352
|
for (const skill of res.skills) {
|
|
322
|
-
if (isCached(skill.id)) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
353
|
+
if (!isCached(skill.id)) {
|
|
354
|
+
cacheSkill(skill);
|
|
355
|
+
}
|
|
356
|
+
if (useSkillsDir && skillsDir && skill.content) {
|
|
357
|
+
const packDir = path3.join(skillsDir, skill.pack);
|
|
358
|
+
fs3.mkdirSync(packDir, { recursive: true });
|
|
359
|
+
const skillFile = path3.join(packDir, skill.name + ".md");
|
|
360
|
+
if (fs3.existsSync(skillFile)) {
|
|
361
|
+
skipped++;
|
|
362
|
+
console.log(pc3.dim(` skip ${skill.id} (already installed)`));
|
|
363
|
+
} else {
|
|
364
|
+
fs3.writeFileSync(skillFile, skill.content);
|
|
365
|
+
added++;
|
|
366
|
+
console.log(pc3.green(` add ${skill.id}`) + pc3.dim(` \u2192 ${path3.relative(process.cwd(), skillFile)}`));
|
|
367
|
+
}
|
|
368
|
+
} else if (!useSkillsDir) {
|
|
369
|
+
if (isCached(skill.id)) {
|
|
370
|
+
skipped++;
|
|
371
|
+
console.log(pc3.dim(` skip ${skill.id} (already cached)`));
|
|
372
|
+
} else {
|
|
373
|
+
added++;
|
|
374
|
+
console.log(pc3.green(` add ${skill.id}`));
|
|
375
|
+
}
|
|
326
376
|
}
|
|
327
|
-
const filePath = cacheSkill(skill);
|
|
328
|
-
added++;
|
|
329
|
-
console.log(pc3.green(` add ${skill.id}`) + pc3.dim(` \u2192 ${filePath}`));
|
|
330
377
|
}
|
|
331
378
|
console.log(
|
|
332
379
|
`
|
|
333
380
|
${pc3.green(`${added} skill${added === 1 ? "" : "s"} added`)}` + (skipped > 0 ? pc3.dim(`, ${skipped} skipped`) : "")
|
|
334
381
|
);
|
|
382
|
+
if (useSkillsDir && skillsDir) {
|
|
383
|
+
console.log(pc3.dim(`
|
|
384
|
+
Skills installed to ${path3.relative(process.cwd(), skillsDir)}`));
|
|
385
|
+
console.log(pc3.dim("Your IDE will auto-load them \u2014 no CLAUDE.md changes needed."));
|
|
386
|
+
}
|
|
335
387
|
if (res.skills.some((s) => !s.content)) {
|
|
336
388
|
console.log(pc3.yellow("\nNote: Some skills were cached without content (metadata only)."));
|
|
337
389
|
console.log(pc3.yellow('Run "skilldb login" with a Pro key to download full content.'));
|
|
@@ -403,8 +455,8 @@ async function infoCommand(id) {
|
|
|
403
455
|
}
|
|
404
456
|
|
|
405
457
|
// src/commands/init.ts
|
|
406
|
-
import
|
|
407
|
-
import
|
|
458
|
+
import fs4 from "fs";
|
|
459
|
+
import path4 from "path";
|
|
408
460
|
import pc6 from "picocolors";
|
|
409
461
|
import readline from "readline";
|
|
410
462
|
function prompt(question) {
|
|
@@ -417,48 +469,38 @@ function prompt(question) {
|
|
|
417
469
|
});
|
|
418
470
|
}
|
|
419
471
|
function detectIDE(cwd) {
|
|
420
|
-
if (
|
|
472
|
+
if (fs4.existsSync(path4.join(cwd, "CLAUDE.md")) || fs4.existsSync(path4.join(cwd, ".claude"))) {
|
|
421
473
|
return {
|
|
422
474
|
ide: "claude-code",
|
|
423
475
|
label: "Claude Code",
|
|
424
|
-
|
|
476
|
+
skillsDir: path4.join(cwd, ".claude", "skills", "skilldb"),
|
|
477
|
+
configFile: path4.join(cwd, "CLAUDE.md")
|
|
425
478
|
};
|
|
426
479
|
}
|
|
427
|
-
if (
|
|
480
|
+
if (fs4.existsSync(path4.join(cwd, ".cursor")) || fs4.existsSync(path4.join(cwd, ".cursorrules"))) {
|
|
428
481
|
return {
|
|
429
482
|
ide: "cursor",
|
|
430
483
|
label: "Cursor",
|
|
431
|
-
|
|
484
|
+
skillsDir: path4.join(cwd, ".cursor", "rules", "skilldb"),
|
|
485
|
+
configFile: fs4.existsSync(path4.join(cwd, ".cursorrules")) ? path4.join(cwd, ".cursorrules") : path4.join(cwd, ".cursor", "rules", "skilldb.md")
|
|
432
486
|
};
|
|
433
487
|
}
|
|
434
|
-
if (
|
|
488
|
+
if (fs4.existsSync(path4.join(cwd, "codex.md")) || fs4.existsSync(path4.join(cwd, ".codex"))) {
|
|
435
489
|
return {
|
|
436
490
|
ide: "codex",
|
|
437
491
|
label: "Codex CLI",
|
|
438
|
-
|
|
492
|
+
skillsDir: path4.join(cwd, ".codex", "skills", "skilldb"),
|
|
493
|
+
configFile: path4.join(cwd, "codex.md")
|
|
439
494
|
};
|
|
440
495
|
}
|
|
441
496
|
return null;
|
|
442
497
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
Local skills are available in \`.skilldb/skills/\`. Use them as reference when working on tasks.
|
|
450
|
-
Search: \`skilldb search react server components\` (supports multiple words)
|
|
451
|
-
Download a skill: \`skilldb get <pack>/<skill>\` (e.g. \`skilldb get software-skills/code-review\`)
|
|
452
|
-
Download a full pack: \`skilldb add <pack>\`
|
|
453
|
-
`.trim();
|
|
454
|
-
return `
|
|
455
|
-
|
|
456
|
-
${marker}
|
|
457
|
-
${content}
|
|
458
|
-
${endMarker}
|
|
459
|
-
`;
|
|
460
|
-
}
|
|
461
|
-
async function initCommand() {
|
|
498
|
+
var IDE_SKILLS_DIRS = {
|
|
499
|
+
"claude-code": ".claude/skills/skilldb",
|
|
500
|
+
"cursor": ".cursor/rules/skilldb",
|
|
501
|
+
"codex": ".codex/skills/skilldb"
|
|
502
|
+
};
|
|
503
|
+
async function initCommand(options) {
|
|
462
504
|
const cwd = process.cwd();
|
|
463
505
|
console.log(pc6.bold("SkillDB Init\n"));
|
|
464
506
|
let detection = detectIDE(cwd);
|
|
@@ -476,42 +518,131 @@ async function initCommand() {
|
|
|
476
518
|
console.log(pc6.red("Invalid choice."));
|
|
477
519
|
process.exit(1);
|
|
478
520
|
}
|
|
479
|
-
const configFiles = {
|
|
480
|
-
"claude-code": path3.join(cwd, "CLAUDE.md"),
|
|
481
|
-
"cursor": path3.join(cwd, ".cursorrules"),
|
|
482
|
-
"codex": path3.join(cwd, "codex.md")
|
|
483
|
-
};
|
|
484
521
|
detection = {
|
|
485
522
|
ide,
|
|
486
523
|
label: ide === "claude-code" ? "Claude Code" : ide === "cursor" ? "Cursor" : "Codex CLI",
|
|
487
|
-
|
|
524
|
+
skillsDir: path4.join(cwd, IDE_SKILLS_DIRS[ide]),
|
|
525
|
+
configFile: path4.join(cwd, ide === "claude-code" ? "CLAUDE.md" : ide === "cursor" ? ".cursorrules" : "codex.md")
|
|
488
526
|
};
|
|
489
527
|
}
|
|
528
|
+
const targetArg = options?.target;
|
|
529
|
+
let mode;
|
|
530
|
+
if (targetArg === "mcp") mode = "mcp";
|
|
531
|
+
else if (targetArg === "skills-dir") mode = "skills-dir";
|
|
532
|
+
else if (targetArg === "claude-md" || targetArg === "config") mode = "claude-md";
|
|
533
|
+
else {
|
|
534
|
+
console.log(`
|
|
535
|
+
Where should skills be loaded from?
|
|
536
|
+
`);
|
|
537
|
+
console.log(` 1) ${pc6.cyan("MCP Server")} ${pc6.dim("\u2014 agent fetches on demand, nothing added to files (recommended)")}`);
|
|
538
|
+
console.log(` 2) ${pc6.cyan("Skills directory")} ${pc6.dim(`\u2014 installed to ${IDE_SKILLS_DIRS[detection.ide]}, auto-loaded by IDE`)}`);
|
|
539
|
+
console.log(` 3) ${pc6.cyan("Hybrid")} ${pc6.dim("\u2014 MCP for search + skills dir for always-on core skills")}`);
|
|
540
|
+
console.log(` 4) ${pc6.cyan(path4.basename(detection.configFile))} ${pc6.dim("\u2014 append to config file (legacy, can bloat)")}`);
|
|
541
|
+
const modeChoice = await prompt("\nChoice (1-4): ");
|
|
542
|
+
mode = { "1": "mcp", "2": "skills-dir", "3": "hybrid", "4": "claude-md" }[modeChoice] || "hybrid";
|
|
543
|
+
}
|
|
490
544
|
const cacheDir = initCache(cwd);
|
|
491
|
-
console.log(`
|
|
545
|
+
console.log(`
|
|
546
|
+
Created ${pc6.dim(cacheDir)}`);
|
|
492
547
|
updateGitignore(cwd);
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
548
|
+
if (mode === "mcp" || mode === "hybrid") {
|
|
549
|
+
console.log(`
|
|
550
|
+
${pc6.cyan("MCP Server setup:")}`);
|
|
551
|
+
console.log(` Install globally: ${pc6.dim("npm install -g skilldb")}`);
|
|
552
|
+
if (detection.ide === "claude-code") {
|
|
553
|
+
console.log(` Add to Claude: ${pc6.dim("claude mcp add skilldb -- skilldb-mcp")}`);
|
|
554
|
+
} else if (detection.ide === "cursor") {
|
|
555
|
+
console.log(` Add to Cursor: ${pc6.dim("Add to .cursor/mcp.json:")}`);
|
|
556
|
+
console.log(pc6.dim(` { "mcpServers": { "skilldb": { "command": "skilldb-mcp" } } }`));
|
|
557
|
+
} else {
|
|
558
|
+
console.log(` Run: ${pc6.dim("skilldb-mcp")}`);
|
|
559
|
+
}
|
|
560
|
+
const marker = "<!-- skilldb:start -->";
|
|
561
|
+
const snippet = `
|
|
562
|
+
|
|
563
|
+
${marker}
|
|
564
|
+
## SkillDB
|
|
565
|
+
Skills available via MCP server. Use \`skilldb_search\` to find skills and \`skilldb_get\` to load them.
|
|
566
|
+
<!-- skilldb:end -->
|
|
567
|
+
`;
|
|
568
|
+
const configFile = detection.configFile;
|
|
569
|
+
let existing = "";
|
|
570
|
+
if (fs4.existsSync(configFile)) existing = fs4.readFileSync(configFile, "utf-8");
|
|
571
|
+
if (!existing.includes("skilldb:start")) {
|
|
572
|
+
fs4.mkdirSync(path4.dirname(configFile), { recursive: true });
|
|
573
|
+
fs4.writeFileSync(configFile, existing + snippet);
|
|
574
|
+
console.log(`
|
|
575
|
+
Added MCP reference to ${pc6.cyan(path4.basename(configFile))} ${pc6.dim("(2 lines, no bloat)")}`);
|
|
506
576
|
}
|
|
507
|
-
fs3.writeFileSync(configFile, existing + snippet);
|
|
508
|
-
console.log(`Added SkillDB snippet to ${pc6.cyan(path3.basename(configFile))}`);
|
|
509
577
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
578
|
+
if (mode === "skills-dir" || mode === "hybrid") {
|
|
579
|
+
const skillsDir = detection.skillsDir;
|
|
580
|
+
fs4.mkdirSync(skillsDir, { recursive: true });
|
|
581
|
+
console.log(`
|
|
582
|
+
${pc6.cyan("Skills directory:")} ${pc6.dim(skillsDir)}`);
|
|
583
|
+
console.log(` Skills installed here are auto-loaded by ${detection.label}.`);
|
|
584
|
+
console.log(` Add skills: ${pc6.dim("skilldb add software-skills --target skills-dir")}`);
|
|
585
|
+
fs4.writeFileSync(path4.join(skillsDir, "README.md"), `# SkillDB Skills
|
|
586
|
+
|
|
587
|
+
Skills in this directory are automatically loaded by ${detection.label}.
|
|
588
|
+
|
|
589
|
+
Manage with:
|
|
590
|
+
- \`skilldb add <pack> --target skills-dir\`
|
|
591
|
+
- \`skilldb remove <pack>\`
|
|
592
|
+
- \`skilldb use <profile>\`
|
|
593
|
+
|
|
594
|
+
Browse: https://skilldb.dev
|
|
595
|
+
`);
|
|
596
|
+
}
|
|
597
|
+
if (mode === "claude-md") {
|
|
598
|
+
const marker = "<!-- skilldb:start -->";
|
|
599
|
+
const endMarker = "<!-- skilldb:end -->";
|
|
600
|
+
const content = `
|
|
601
|
+
## SkillDB Skills
|
|
602
|
+
|
|
603
|
+
Local skills: \`.skilldb/skills/\`. Use as reference for tasks.
|
|
604
|
+
Search: \`skilldb search <query>\`
|
|
605
|
+
Download: \`skilldb add <pack>\`
|
|
606
|
+
`;
|
|
607
|
+
const snippet = `
|
|
608
|
+
|
|
609
|
+
${marker}${content}${endMarker}
|
|
610
|
+
`;
|
|
611
|
+
const configFile = detection.configFile;
|
|
612
|
+
let existing = "";
|
|
613
|
+
if (fs4.existsSync(configFile)) existing = fs4.readFileSync(configFile, "utf-8");
|
|
614
|
+
if (!existing.includes("skilldb:start")) {
|
|
615
|
+
fs4.mkdirSync(path4.dirname(configFile), { recursive: true });
|
|
616
|
+
fs4.writeFileSync(configFile, existing + snippet);
|
|
617
|
+
console.log(`
|
|
618
|
+
Added SkillDB snippet to ${pc6.cyan(path4.basename(configFile))}`);
|
|
619
|
+
}
|
|
620
|
+
console.log(pc6.yellow("\n\u26A0 Config-file mode can bloat over time. Consider --target mcp or --target skills-dir."));
|
|
621
|
+
}
|
|
622
|
+
const configPath = path4.join(cwd, ".skilldb", "config.json");
|
|
623
|
+
let config = {};
|
|
624
|
+
try {
|
|
625
|
+
config = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
|
|
626
|
+
} catch {
|
|
627
|
+
}
|
|
628
|
+
config.installMode = mode;
|
|
629
|
+
config.ide = detection.ide;
|
|
630
|
+
config.skillsDir = detection.skillsDir;
|
|
631
|
+
fs4.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
632
|
+
console.log(pc6.green("\n\u2713 Done! Next steps:"));
|
|
633
|
+
if (mode === "mcp" || mode === "hybrid") {
|
|
634
|
+
console.log(` ${pc6.dim("$")} npm install -g skilldb`);
|
|
635
|
+
if (detection.ide === "claude-code") {
|
|
636
|
+
console.log(` ${pc6.dim("$")} claude mcp add skilldb -- skilldb-mcp`);
|
|
637
|
+
}
|
|
638
|
+
console.log(` Then ask your agent: ${pc6.dim('"Search SkillDB for code review skills"')}`);
|
|
639
|
+
}
|
|
640
|
+
if (mode === "skills-dir" || mode === "hybrid") {
|
|
641
|
+
console.log(` ${pc6.dim("$")} skilldb add software-skills --target skills-dir`);
|
|
642
|
+
}
|
|
643
|
+
if (mode === "claude-md") {
|
|
644
|
+
console.log(` ${pc6.dim("$")} skilldb add software-skills`);
|
|
645
|
+
}
|
|
515
646
|
}
|
|
516
647
|
|
|
517
648
|
// src/commands/login.ts
|
|
@@ -552,8 +683,8 @@ async function loginCommand() {
|
|
|
552
683
|
}
|
|
553
684
|
|
|
554
685
|
// src/commands/use.ts
|
|
555
|
-
import
|
|
556
|
-
import
|
|
686
|
+
import fs5 from "fs";
|
|
687
|
+
import path5 from "path";
|
|
557
688
|
import pc8 from "picocolors";
|
|
558
689
|
var SKILLDB_DIR2 = ".skilldb";
|
|
559
690
|
var ACTIVE_DIR = "active";
|
|
@@ -569,23 +700,23 @@ var PROFILES = {
|
|
|
569
700
|
"ai-agent": ["ai-agent-skills", "prompt-engineering-skills", "llm-skills"]
|
|
570
701
|
};
|
|
571
702
|
function readConfig(cwd) {
|
|
572
|
-
const p =
|
|
703
|
+
const p = path5.join(cwd, SKILLDB_DIR2, CONFIG_FILE);
|
|
573
704
|
try {
|
|
574
|
-
return JSON.parse(
|
|
705
|
+
return JSON.parse(fs5.readFileSync(p, "utf-8"));
|
|
575
706
|
} catch {
|
|
576
707
|
return {};
|
|
577
708
|
}
|
|
578
709
|
}
|
|
579
710
|
function writeConfig(cwd, config) {
|
|
580
|
-
const dir =
|
|
581
|
-
if (!
|
|
582
|
-
|
|
711
|
+
const dir = path5.join(cwd, SKILLDB_DIR2);
|
|
712
|
+
if (!fs5.existsSync(dir)) fs5.mkdirSync(dir, { recursive: true });
|
|
713
|
+
fs5.writeFileSync(path5.join(dir, CONFIG_FILE), JSON.stringify(config, null, 2) + "\n");
|
|
583
714
|
}
|
|
584
715
|
function autoDetect(cwd) {
|
|
585
|
-
const has = (f) =>
|
|
716
|
+
const has = (f) => fs5.existsSync(path5.join(cwd, f));
|
|
586
717
|
let pkgDeps = [];
|
|
587
718
|
try {
|
|
588
|
-
const pkg = JSON.parse(
|
|
719
|
+
const pkg = JSON.parse(fs5.readFileSync(path5.join(cwd, "package.json"), "utf-8"));
|
|
589
720
|
pkgDeps = Object.keys({ ...pkg.dependencies, ...pkg.devDependencies });
|
|
590
721
|
} catch {
|
|
591
722
|
}
|
|
@@ -600,19 +731,19 @@ function autoDetect(cwd) {
|
|
|
600
731
|
return "fullstack";
|
|
601
732
|
}
|
|
602
733
|
function activateProfile(cwd, profile) {
|
|
603
|
-
const activeDir =
|
|
604
|
-
if (
|
|
605
|
-
|
|
734
|
+
const activeDir = path5.join(cwd, SKILLDB_DIR2, ACTIVE_DIR);
|
|
735
|
+
if (fs5.existsSync(activeDir)) fs5.rmSync(activeDir, { recursive: true });
|
|
736
|
+
fs5.mkdirSync(activeDir, { recursive: true });
|
|
606
737
|
const packs = PROFILES[profile] || [];
|
|
607
|
-
const skillsDir =
|
|
738
|
+
const skillsDir = path5.join(cwd, SKILLDB_DIR2, "skills");
|
|
608
739
|
let copied = 0;
|
|
609
740
|
for (const pack of packs) {
|
|
610
|
-
const packDir =
|
|
611
|
-
if (!
|
|
612
|
-
const destDir =
|
|
613
|
-
|
|
614
|
-
for (const file of
|
|
615
|
-
|
|
741
|
+
const packDir = path5.join(skillsDir, pack);
|
|
742
|
+
if (!fs5.existsSync(packDir)) continue;
|
|
743
|
+
const destDir = path5.join(activeDir, pack);
|
|
744
|
+
fs5.mkdirSync(destDir, { recursive: true });
|
|
745
|
+
for (const file of fs5.readdirSync(packDir)) {
|
|
746
|
+
fs5.copyFileSync(path5.join(packDir, file), path5.join(destDir, file));
|
|
616
747
|
copied++;
|
|
617
748
|
}
|
|
618
749
|
}
|
|
@@ -647,8 +778,8 @@ async function useCommand(profile, options) {
|
|
|
647
778
|
return;
|
|
648
779
|
}
|
|
649
780
|
if (profile === "none") {
|
|
650
|
-
const activeDir =
|
|
651
|
-
if (
|
|
781
|
+
const activeDir = path5.join(cwd, SKILLDB_DIR2, ACTIVE_DIR);
|
|
782
|
+
if (fs5.existsSync(activeDir)) fs5.rmSync(activeDir, { recursive: true });
|
|
652
783
|
const config = readConfig(cwd);
|
|
653
784
|
delete config.activeProfile;
|
|
654
785
|
writeConfig(cwd, config);
|
|
@@ -669,42 +800,42 @@ async function useCommand(profile, options) {
|
|
|
669
800
|
}
|
|
670
801
|
|
|
671
802
|
// src/commands/budget.ts
|
|
672
|
-
import
|
|
673
|
-
import
|
|
803
|
+
import fs6 from "fs";
|
|
804
|
+
import path6 from "path";
|
|
674
805
|
import pc9 from "picocolors";
|
|
675
806
|
var SKILLDB_DIR3 = ".skilldb";
|
|
676
807
|
var CONFIG_FILE2 = "config.json";
|
|
677
808
|
var ACTIVE_DIR2 = "active";
|
|
678
809
|
var TOKENS_PER_LINE = 10;
|
|
679
810
|
function readConfig2(cwd) {
|
|
680
|
-
const p =
|
|
811
|
+
const p = path6.join(cwd, SKILLDB_DIR3, CONFIG_FILE2);
|
|
681
812
|
try {
|
|
682
|
-
return JSON.parse(
|
|
813
|
+
return JSON.parse(fs6.readFileSync(p, "utf-8"));
|
|
683
814
|
} catch {
|
|
684
815
|
return {};
|
|
685
816
|
}
|
|
686
817
|
}
|
|
687
818
|
function writeConfig2(cwd, config) {
|
|
688
|
-
const dir =
|
|
689
|
-
if (!
|
|
690
|
-
|
|
819
|
+
const dir = path6.join(cwd, SKILLDB_DIR3);
|
|
820
|
+
if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
|
|
821
|
+
fs6.writeFileSync(path6.join(dir, CONFIG_FILE2), JSON.stringify(config, null, 2) + "\n");
|
|
691
822
|
}
|
|
692
823
|
function countActiveSkills(cwd) {
|
|
693
|
-
const activeDir =
|
|
824
|
+
const activeDir = path6.join(cwd, SKILLDB_DIR3, ACTIVE_DIR2);
|
|
694
825
|
const files = [];
|
|
695
826
|
let totalLines = 0;
|
|
696
|
-
if (!
|
|
827
|
+
if (!fs6.existsSync(activeDir)) return { files, totalLines, totalTokens: 0 };
|
|
697
828
|
function walk(dir) {
|
|
698
|
-
for (const entry of
|
|
699
|
-
const full =
|
|
829
|
+
for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
|
|
830
|
+
const full = path6.join(dir, entry.name);
|
|
700
831
|
if (entry.isDirectory()) {
|
|
701
832
|
walk(full);
|
|
702
833
|
continue;
|
|
703
834
|
}
|
|
704
835
|
if (!entry.name.endsWith(".md")) continue;
|
|
705
|
-
const content =
|
|
836
|
+
const content = fs6.readFileSync(full, "utf-8");
|
|
706
837
|
const lines = content.split("\n").length;
|
|
707
|
-
files.push(
|
|
838
|
+
files.push(path6.relative(path6.join(cwd, SKILLDB_DIR3), full));
|
|
708
839
|
totalLines += lines;
|
|
709
840
|
}
|
|
710
841
|
}
|
|
@@ -733,14 +864,14 @@ async function budgetCommand(action, value) {
|
|
|
733
864
|
}
|
|
734
865
|
if (action === "optimize") {
|
|
735
866
|
let walk2 = function(dir) {
|
|
736
|
-
for (const entry of
|
|
737
|
-
const full =
|
|
867
|
+
for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
|
|
868
|
+
const full = path6.join(dir, entry.name);
|
|
738
869
|
if (entry.isDirectory()) {
|
|
739
870
|
walk2(full);
|
|
740
871
|
continue;
|
|
741
872
|
}
|
|
742
873
|
if (!entry.name.endsWith(".md")) continue;
|
|
743
|
-
const lines =
|
|
874
|
+
const lines = fs6.readFileSync(full, "utf-8").split("\n").length;
|
|
744
875
|
skillFiles.push({ path: full, lines });
|
|
745
876
|
}
|
|
746
877
|
};
|
|
@@ -754,7 +885,7 @@ async function budgetCommand(action, value) {
|
|
|
754
885
|
console.log(pc9.green("Already within budget. No changes needed."));
|
|
755
886
|
return;
|
|
756
887
|
}
|
|
757
|
-
const activeDir =
|
|
888
|
+
const activeDir = path6.join(cwd, SKILLDB_DIR3, ACTIVE_DIR2);
|
|
758
889
|
const skillFiles = [];
|
|
759
890
|
walk2(activeDir);
|
|
760
891
|
skillFiles.sort((a, b) => b.lines - a.lines);
|
|
@@ -762,10 +893,10 @@ async function budgetCommand(action, value) {
|
|
|
762
893
|
let removed = 0;
|
|
763
894
|
for (const sf of skillFiles) {
|
|
764
895
|
if (current <= config.budget.max) break;
|
|
765
|
-
|
|
896
|
+
fs6.unlinkSync(sf.path);
|
|
766
897
|
current -= config.budget.unit === "tokens" ? sf.lines * TOKENS_PER_LINE : sf.lines;
|
|
767
898
|
removed++;
|
|
768
|
-
console.log(pc9.yellow(` removed ${
|
|
899
|
+
console.log(pc9.yellow(` removed ${path6.basename(sf.path)}`) + pc9.dim(` (${sf.lines} lines)`));
|
|
769
900
|
}
|
|
770
901
|
console.log(pc9.green(`
|
|
771
902
|
Optimized: removed ${removed} skill(s) to fit budget.`));
|
|
@@ -787,15 +918,15 @@ Optimized: removed ${removed} skill(s) to fit budget.`));
|
|
|
787
918
|
}
|
|
788
919
|
|
|
789
920
|
// src/commands/slim.ts
|
|
790
|
-
import
|
|
791
|
-
import
|
|
921
|
+
import fs7 from "fs";
|
|
922
|
+
import path7 from "path";
|
|
792
923
|
import pc10 from "picocolors";
|
|
793
924
|
var SKILLDB_DIR4 = ".skilldb";
|
|
794
925
|
var ACTIVE_DIR3 = "active";
|
|
795
926
|
var SLIM_DIR = "slim";
|
|
796
927
|
var SKILLS_DIR2 = "skills";
|
|
797
928
|
function ensureDir2(dir) {
|
|
798
|
-
if (!
|
|
929
|
+
if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
|
|
799
930
|
}
|
|
800
931
|
function slimContent(content, ratio) {
|
|
801
932
|
const lines = content.split("\n");
|
|
@@ -865,14 +996,14 @@ function slimContent(content, ratio) {
|
|
|
865
996
|
function processDir(sourceDir, destDir, ratio) {
|
|
866
997
|
ensureDir2(destDir);
|
|
867
998
|
let count = 0;
|
|
868
|
-
for (const entry of
|
|
869
|
-
const srcPath =
|
|
999
|
+
for (const entry of fs7.readdirSync(sourceDir, { withFileTypes: true })) {
|
|
1000
|
+
const srcPath = path7.join(sourceDir, entry.name);
|
|
870
1001
|
if (entry.isDirectory()) {
|
|
871
|
-
count += processDir(srcPath,
|
|
1002
|
+
count += processDir(srcPath, path7.join(destDir, entry.name), ratio);
|
|
872
1003
|
continue;
|
|
873
1004
|
}
|
|
874
1005
|
if (!entry.name.endsWith(".md")) continue;
|
|
875
|
-
const content =
|
|
1006
|
+
const content = fs7.readFileSync(srcPath, "utf-8");
|
|
876
1007
|
const slimmed = slimContent(content, ratio);
|
|
877
1008
|
const footer = `
|
|
878
1009
|
|
|
@@ -880,7 +1011,7 @@ function processDir(sourceDir, destDir, ratio) {
|
|
|
880
1011
|
*Full skill: \`skilldb get ${entry.name.replace(".md", "")}\`*
|
|
881
1012
|
`;
|
|
882
1013
|
ensureDir2(destDir);
|
|
883
|
-
|
|
1014
|
+
fs7.writeFileSync(path7.join(destDir, entry.name), slimmed + footer);
|
|
884
1015
|
count++;
|
|
885
1016
|
const origLines = content.split("\n").length;
|
|
886
1017
|
const slimLines = slimmed.split("\n").length;
|
|
@@ -897,38 +1028,38 @@ async function slimCommand(pack, options) {
|
|
|
897
1028
|
console.error(pc10.red("Ratio must be between 0 and 1 (e.g. 0.3 for 30%)."));
|
|
898
1029
|
process.exit(1);
|
|
899
1030
|
}
|
|
900
|
-
const slimDir =
|
|
1031
|
+
const slimDir = path7.join(cwd, SKILLDB_DIR4, SLIM_DIR);
|
|
901
1032
|
console.log(pc10.bold(`Generating slim versions (${Math.round(ratio * 100)}% ratio)
|
|
902
1033
|
`));
|
|
903
1034
|
let sourceDir;
|
|
904
1035
|
if (pack) {
|
|
905
|
-
sourceDir =
|
|
906
|
-
if (!
|
|
907
|
-
sourceDir =
|
|
1036
|
+
sourceDir = path7.join(cwd, SKILLDB_DIR4, SKILLS_DIR2, pack);
|
|
1037
|
+
if (!fs7.existsSync(sourceDir)) {
|
|
1038
|
+
sourceDir = path7.join(cwd, SKILLDB_DIR4, ACTIVE_DIR3, pack);
|
|
908
1039
|
}
|
|
909
|
-
if (!
|
|
1040
|
+
if (!fs7.existsSync(sourceDir)) {
|
|
910
1041
|
console.error(pc10.red(`Pack "${pack}" not found in skills/ or active/.`));
|
|
911
1042
|
process.exit(1);
|
|
912
1043
|
}
|
|
913
1044
|
} else {
|
|
914
|
-
sourceDir =
|
|
915
|
-
if (!
|
|
916
|
-
sourceDir =
|
|
1045
|
+
sourceDir = path7.join(cwd, SKILLDB_DIR4, ACTIVE_DIR3);
|
|
1046
|
+
if (!fs7.existsSync(sourceDir)) {
|
|
1047
|
+
sourceDir = path7.join(cwd, SKILLDB_DIR4, SKILLS_DIR2);
|
|
917
1048
|
}
|
|
918
1049
|
}
|
|
919
|
-
if (!
|
|
1050
|
+
if (!fs7.existsSync(sourceDir)) {
|
|
920
1051
|
console.error(pc10.red('No skills found. Install skills first with "skilldb add <pack>".'));
|
|
921
1052
|
process.exit(1);
|
|
922
1053
|
}
|
|
923
|
-
const destDir = pack ?
|
|
1054
|
+
const destDir = pack ? path7.join(slimDir, pack) : slimDir;
|
|
924
1055
|
const count = processDir(sourceDir, destDir, ratio);
|
|
925
1056
|
console.log(pc10.green(`
|
|
926
1057
|
${count} skill(s) slimmed \u2192 .skilldb/slim/`));
|
|
927
1058
|
}
|
|
928
1059
|
|
|
929
1060
|
// src/commands/recommend.ts
|
|
930
|
-
import
|
|
931
|
-
import
|
|
1061
|
+
import fs8 from "fs";
|
|
1062
|
+
import path8 from "path";
|
|
932
1063
|
import pc11 from "picocolors";
|
|
933
1064
|
var TECH_MAP = {
|
|
934
1065
|
react: ["react-patterns-skills", "web-polish-skills"],
|
|
@@ -954,7 +1085,7 @@ function scanProject(cwd) {
|
|
|
954
1085
|
const detected = [];
|
|
955
1086
|
const packs = /* @__PURE__ */ new Set();
|
|
956
1087
|
try {
|
|
957
|
-
const pkg = JSON.parse(
|
|
1088
|
+
const pkg = JSON.parse(fs8.readFileSync(path8.join(cwd, "package.json"), "utf-8"));
|
|
958
1089
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
959
1090
|
for (const dep of Object.keys(allDeps)) {
|
|
960
1091
|
const key = dep.replace(/^@[^/]+\//, "");
|
|
@@ -968,11 +1099,11 @@ function scanProject(cwd) {
|
|
|
968
1099
|
}
|
|
969
1100
|
} catch {
|
|
970
1101
|
}
|
|
971
|
-
if (
|
|
1102
|
+
if (fs8.existsSync(path8.join(cwd, "Dockerfile"))) {
|
|
972
1103
|
detected.push("docker");
|
|
973
1104
|
(TECH_MAP.docker || []).forEach((p) => packs.add(p));
|
|
974
1105
|
}
|
|
975
|
-
if (
|
|
1106
|
+
if (fs8.existsSync(path8.join(cwd, "tsconfig.json"))) {
|
|
976
1107
|
detected.push("typescript");
|
|
977
1108
|
(TECH_MAP.typescript || []).forEach((p) => packs.add(p));
|
|
978
1109
|
}
|
|
@@ -1034,8 +1165,8 @@ async function recommendCommand(options) {
|
|
|
1034
1165
|
}
|
|
1035
1166
|
|
|
1036
1167
|
// src/commands/doctor.ts
|
|
1037
|
-
import
|
|
1038
|
-
import
|
|
1168
|
+
import fs9 from "fs";
|
|
1169
|
+
import path9 from "path";
|
|
1039
1170
|
import pc12 from "picocolors";
|
|
1040
1171
|
var SKILLDB_DIR5 = ".skilldb";
|
|
1041
1172
|
var SKILLS_DIR3 = "skills";
|
|
@@ -1043,26 +1174,26 @@ var ACTIVE_DIR4 = "active";
|
|
|
1043
1174
|
var CONFIG_FILE3 = "config.json";
|
|
1044
1175
|
var TOKENS_PER_LINE2 = 10;
|
|
1045
1176
|
function readConfig3(cwd) {
|
|
1046
|
-
const p =
|
|
1177
|
+
const p = path9.join(cwd, SKILLDB_DIR5, CONFIG_FILE3);
|
|
1047
1178
|
try {
|
|
1048
|
-
return JSON.parse(
|
|
1179
|
+
return JSON.parse(fs9.readFileSync(p, "utf-8"));
|
|
1049
1180
|
} catch {
|
|
1050
1181
|
return {};
|
|
1051
1182
|
}
|
|
1052
1183
|
}
|
|
1053
1184
|
function collectFiles(dir) {
|
|
1054
1185
|
const result = [];
|
|
1055
|
-
if (!
|
|
1186
|
+
if (!fs9.existsSync(dir)) return result;
|
|
1056
1187
|
function walk(d) {
|
|
1057
|
-
for (const entry of
|
|
1058
|
-
const full =
|
|
1188
|
+
for (const entry of fs9.readdirSync(d, { withFileTypes: true })) {
|
|
1189
|
+
const full = path9.join(d, entry.name);
|
|
1059
1190
|
if (entry.isDirectory()) {
|
|
1060
1191
|
walk(full);
|
|
1061
1192
|
continue;
|
|
1062
1193
|
}
|
|
1063
1194
|
if (!entry.name.endsWith(".md")) continue;
|
|
1064
|
-
const lines =
|
|
1065
|
-
result.push({ name:
|
|
1195
|
+
const lines = fs9.readFileSync(full, "utf-8").split("\n").length;
|
|
1196
|
+
result.push({ name: path9.relative(dir, full), lines });
|
|
1066
1197
|
}
|
|
1067
1198
|
}
|
|
1068
1199
|
walk(dir);
|
|
@@ -1074,29 +1205,29 @@ async function doctorCommand() {
|
|
|
1074
1205
|
const config = readConfig3(cwd);
|
|
1075
1206
|
let issues = 0;
|
|
1076
1207
|
console.log(pc12.bold("SkillDB Doctor\n"));
|
|
1077
|
-
const skilldbDir =
|
|
1078
|
-
if (!
|
|
1208
|
+
const skilldbDir = path9.join(cwd, SKILLDB_DIR5);
|
|
1209
|
+
if (!fs9.existsSync(skilldbDir)) {
|
|
1079
1210
|
console.log(pc12.red(' [!] .skilldb/ not found. Run "skilldb init" first.'));
|
|
1080
1211
|
return;
|
|
1081
1212
|
}
|
|
1082
1213
|
console.log(pc12.green(" [ok] .skilldb/ directory exists"));
|
|
1083
1214
|
const installedIds = Object.keys(manifest.installed);
|
|
1084
|
-
const skillsDir =
|
|
1215
|
+
const skillsDir = path9.join(cwd, SKILLDB_DIR5, SKILLS_DIR3);
|
|
1085
1216
|
const skillFiles = collectFiles(skillsDir);
|
|
1086
1217
|
console.log(pc12.green(` [ok] ${installedIds.length} skills in manifest, ${skillFiles.length} files on disk`));
|
|
1087
1218
|
let orphaned = 0;
|
|
1088
1219
|
for (const id of installedIds) {
|
|
1089
1220
|
const [pack, file] = id.split("/");
|
|
1090
1221
|
const name = file.replace(".md", "").replace(/[/\\:*?"<>|]/g, "-");
|
|
1091
|
-
const filePath =
|
|
1092
|
-
if (!
|
|
1222
|
+
const filePath = path9.join(skillsDir, pack, `${name}.md`);
|
|
1223
|
+
if (!fs9.existsSync(filePath)) {
|
|
1093
1224
|
orphaned++;
|
|
1094
1225
|
if (orphaned <= 3) console.log(pc12.yellow(` [!] Missing file for manifest entry: ${id}`));
|
|
1095
1226
|
}
|
|
1096
1227
|
}
|
|
1097
1228
|
if (orphaned > 3) console.log(pc12.yellow(` [!] ...and ${orphaned - 3} more missing files`));
|
|
1098
1229
|
if (orphaned > 0) issues++;
|
|
1099
|
-
const activeDir =
|
|
1230
|
+
const activeDir = path9.join(cwd, SKILLDB_DIR5, ACTIVE_DIR4);
|
|
1100
1231
|
const activeFiles = collectFiles(activeDir);
|
|
1101
1232
|
if (config.activeProfile) {
|
|
1102
1233
|
console.log(pc12.green(` [ok] Active profile: ${config.activeProfile} (${activeFiles.length} skills)`));
|
|
@@ -1133,8 +1264,8 @@ ${issues} issue(s) found. Run suggested commands to fix.`)
|
|
|
1133
1264
|
}
|
|
1134
1265
|
|
|
1135
1266
|
// src/commands/update.ts
|
|
1136
|
-
import
|
|
1137
|
-
import
|
|
1267
|
+
import fs10 from "fs";
|
|
1268
|
+
import path10 from "path";
|
|
1138
1269
|
import pc13 from "picocolors";
|
|
1139
1270
|
var SKILLDB_DIR6 = ".skilldb";
|
|
1140
1271
|
var SKILLS_DIR4 = "skills";
|
|
@@ -1173,7 +1304,7 @@ async function updateCommand(pack) {
|
|
|
1173
1304
|
console.log(pc13.dim(` skip ${id} (not found on remote)`));
|
|
1174
1305
|
continue;
|
|
1175
1306
|
}
|
|
1176
|
-
const localPath =
|
|
1307
|
+
const localPath = path10.join(
|
|
1177
1308
|
cwd,
|
|
1178
1309
|
SKILLDB_DIR6,
|
|
1179
1310
|
SKILLS_DIR4,
|
|
@@ -1182,7 +1313,7 @@ async function updateCommand(pack) {
|
|
|
1182
1313
|
);
|
|
1183
1314
|
let localContent = "";
|
|
1184
1315
|
try {
|
|
1185
|
-
localContent =
|
|
1316
|
+
localContent = fs10.readFileSync(localPath, "utf-8");
|
|
1186
1317
|
} catch {
|
|
1187
1318
|
}
|
|
1188
1319
|
if (remote.content && remote.content !== localContent) {
|
|
@@ -1211,19 +1342,19 @@ ${pc13.green(`${updated} updated`)}` + pc13.dim(`, ${unchanged} unchanged`) + (f
|
|
|
1211
1342
|
}
|
|
1212
1343
|
|
|
1213
1344
|
// src/commands/remove.ts
|
|
1214
|
-
import
|
|
1215
|
-
import
|
|
1345
|
+
import fs11 from "fs";
|
|
1346
|
+
import path11 from "path";
|
|
1216
1347
|
import pc14 from "picocolors";
|
|
1217
1348
|
var SKILLDB_DIR7 = ".skilldb";
|
|
1218
1349
|
var SKILLS_DIR5 = "skills";
|
|
1219
1350
|
var ACTIVE_DIR5 = "active";
|
|
1220
1351
|
function collectActiveSkills(cwd) {
|
|
1221
|
-
const activeDir =
|
|
1352
|
+
const activeDir = path11.join(cwd, SKILLDB_DIR7, ACTIVE_DIR5);
|
|
1222
1353
|
const result = /* @__PURE__ */ new Set();
|
|
1223
|
-
if (!
|
|
1224
|
-
for (const pack of
|
|
1354
|
+
if (!fs11.existsSync(activeDir)) return result;
|
|
1355
|
+
for (const pack of fs11.readdirSync(activeDir, { withFileTypes: true })) {
|
|
1225
1356
|
if (!pack.isDirectory()) continue;
|
|
1226
|
-
for (const file of
|
|
1357
|
+
for (const file of fs11.readdirSync(path11.join(activeDir, pack.name))) {
|
|
1227
1358
|
if (file.endsWith(".md")) {
|
|
1228
1359
|
result.add(`${pack.name}/${file}`);
|
|
1229
1360
|
}
|
|
@@ -1234,12 +1365,12 @@ function collectActiveSkills(cwd) {
|
|
|
1234
1365
|
function removeSkillFile(cwd, id) {
|
|
1235
1366
|
const [pack, file] = id.split("/");
|
|
1236
1367
|
const name = file.replace(".md", "").replace(/[/\\:*?"<>|]/g, "-");
|
|
1237
|
-
const filePath =
|
|
1238
|
-
if (
|
|
1239
|
-
|
|
1240
|
-
const packDir =
|
|
1241
|
-
if (
|
|
1242
|
-
|
|
1368
|
+
const filePath = path11.join(cwd, SKILLDB_DIR7, SKILLS_DIR5, pack, `${name}.md`);
|
|
1369
|
+
if (fs11.existsSync(filePath)) {
|
|
1370
|
+
fs11.unlinkSync(filePath);
|
|
1371
|
+
const packDir = path11.join(cwd, SKILLDB_DIR7, SKILLS_DIR5, pack);
|
|
1372
|
+
if (fs11.existsSync(packDir) && fs11.readdirSync(packDir).length === 0) {
|
|
1373
|
+
fs11.rmdirSync(packDir);
|
|
1243
1374
|
}
|
|
1244
1375
|
return true;
|
|
1245
1376
|
}
|
|
@@ -1314,32 +1445,32 @@ ${packIds.length} skill(s) from "${target}" removed.`));
|
|
|
1314
1445
|
}
|
|
1315
1446
|
|
|
1316
1447
|
// src/commands/export.ts
|
|
1317
|
-
import
|
|
1318
|
-
import
|
|
1448
|
+
import fs12 from "fs";
|
|
1449
|
+
import path12 from "path";
|
|
1319
1450
|
import pc15 from "picocolors";
|
|
1320
1451
|
var SKILLDB_DIR8 = ".skilldb";
|
|
1321
1452
|
var ACTIVE_DIR6 = "active";
|
|
1322
1453
|
var CONFIG_FILE4 = "config.json";
|
|
1323
1454
|
function readConfig4(cwd) {
|
|
1324
|
-
const p =
|
|
1455
|
+
const p = path12.join(cwd, SKILLDB_DIR8, CONFIG_FILE4);
|
|
1325
1456
|
try {
|
|
1326
|
-
return JSON.parse(
|
|
1457
|
+
return JSON.parse(fs12.readFileSync(p, "utf-8"));
|
|
1327
1458
|
} catch {
|
|
1328
1459
|
return {};
|
|
1329
1460
|
}
|
|
1330
1461
|
}
|
|
1331
1462
|
function collectActiveSkills2(cwd) {
|
|
1332
|
-
const activeDir =
|
|
1463
|
+
const activeDir = path12.join(cwd, SKILLDB_DIR8, ACTIVE_DIR6);
|
|
1333
1464
|
const result = [];
|
|
1334
|
-
if (!
|
|
1335
|
-
for (const pack of
|
|
1465
|
+
if (!fs12.existsSync(activeDir)) return result;
|
|
1466
|
+
for (const pack of fs12.readdirSync(activeDir, { withFileTypes: true })) {
|
|
1336
1467
|
if (!pack.isDirectory()) continue;
|
|
1337
|
-
for (const file of
|
|
1468
|
+
for (const file of fs12.readdirSync(path12.join(activeDir, pack.name))) {
|
|
1338
1469
|
if (file.endsWith(".md")) {
|
|
1339
1470
|
result.push({
|
|
1340
1471
|
pack: pack.name,
|
|
1341
1472
|
name: file.replace(".md", ""),
|
|
1342
|
-
path:
|
|
1473
|
+
path: path12.join(activeDir, pack.name, file)
|
|
1343
1474
|
});
|
|
1344
1475
|
}
|
|
1345
1476
|
}
|
|
@@ -1387,7 +1518,7 @@ function exportProfile(cwd) {
|
|
|
1387
1518
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1388
1519
|
};
|
|
1389
1520
|
const filename = ".skilldb-profile.json";
|
|
1390
|
-
|
|
1521
|
+
fs12.writeFileSync(path12.join(cwd, filename), JSON.stringify(profile, null, 2) + "\n");
|
|
1391
1522
|
console.log(pc15.green(`Exported profile to ${filename}`));
|
|
1392
1523
|
console.log(pc15.dim('Share this file and import with "skilldb use --import .skilldb-profile.json"'));
|
|
1393
1524
|
}
|
|
@@ -1398,7 +1529,7 @@ function exportInject(cwd, id) {
|
|
|
1398
1529
|
console.error(pc15.red(`Skill "${id}" not found locally. Run "skilldb get ${id}" first.`));
|
|
1399
1530
|
process.exit(1);
|
|
1400
1531
|
}
|
|
1401
|
-
const content =
|
|
1532
|
+
const content = fs12.readFileSync(cachedPath, "utf-8");
|
|
1402
1533
|
process.stdout.write(content);
|
|
1403
1534
|
}
|
|
1404
1535
|
async function exportCommand(format, target) {
|
|
@@ -1427,8 +1558,8 @@ async function exportCommand(format, target) {
|
|
|
1427
1558
|
}
|
|
1428
1559
|
|
|
1429
1560
|
// src/commands/stats.ts
|
|
1430
|
-
import
|
|
1431
|
-
import
|
|
1561
|
+
import fs13 from "fs";
|
|
1562
|
+
import path13 from "path";
|
|
1432
1563
|
import pc16 from "picocolors";
|
|
1433
1564
|
var SKILLDB_DIR9 = ".skilldb";
|
|
1434
1565
|
var SKILLS_DIR6 = "skills";
|
|
@@ -1436,25 +1567,25 @@ var ACTIVE_DIR7 = "active";
|
|
|
1436
1567
|
var CONFIG_FILE5 = "config.json";
|
|
1437
1568
|
var TOKENS_PER_LINE3 = 10;
|
|
1438
1569
|
function readConfig5(cwd) {
|
|
1439
|
-
const p =
|
|
1570
|
+
const p = path13.join(cwd, SKILLDB_DIR9, CONFIG_FILE5);
|
|
1440
1571
|
try {
|
|
1441
|
-
return JSON.parse(
|
|
1572
|
+
return JSON.parse(fs13.readFileSync(p, "utf-8"));
|
|
1442
1573
|
} catch {
|
|
1443
1574
|
return {};
|
|
1444
1575
|
}
|
|
1445
1576
|
}
|
|
1446
1577
|
function countDir(dir) {
|
|
1447
1578
|
const result = { files: 0, lines: 0, packs: /* @__PURE__ */ new Set(), categories: /* @__PURE__ */ new Set() };
|
|
1448
|
-
if (!
|
|
1449
|
-
for (const pack of
|
|
1579
|
+
if (!fs13.existsSync(dir)) return result;
|
|
1580
|
+
for (const pack of fs13.readdirSync(dir, { withFileTypes: true })) {
|
|
1450
1581
|
if (!pack.isDirectory()) continue;
|
|
1451
1582
|
result.packs.add(pack.name);
|
|
1452
1583
|
const cat = pack.name.replace(/-skills$/, "");
|
|
1453
1584
|
result.categories.add(cat);
|
|
1454
|
-
for (const file of
|
|
1585
|
+
for (const file of fs13.readdirSync(path13.join(dir, pack.name))) {
|
|
1455
1586
|
if (!file.endsWith(".md")) continue;
|
|
1456
1587
|
result.files++;
|
|
1457
|
-
result.lines +=
|
|
1588
|
+
result.lines += fs13.readFileSync(path13.join(dir, pack.name, file), "utf-8").split("\n").length;
|
|
1458
1589
|
}
|
|
1459
1590
|
}
|
|
1460
1591
|
return result;
|
|
@@ -1463,8 +1594,8 @@ async function statsCommand() {
|
|
|
1463
1594
|
const cwd = process.cwd();
|
|
1464
1595
|
const config = readConfig5(cwd);
|
|
1465
1596
|
const manifest = readManifest(cwd);
|
|
1466
|
-
const skillsDir =
|
|
1467
|
-
const activeDir =
|
|
1597
|
+
const skillsDir = path13.join(cwd, SKILLDB_DIR9, SKILLS_DIR6);
|
|
1598
|
+
const activeDir = path13.join(cwd, SKILLDB_DIR9, ACTIVE_DIR7);
|
|
1468
1599
|
const installed = countDir(skillsDir);
|
|
1469
1600
|
const active = countDir(activeDir);
|
|
1470
1601
|
console.log(pc16.bold("SkillDB Stats\n"));
|
|
@@ -1502,8 +1633,8 @@ async function statsCommand() {
|
|
|
1502
1633
|
}
|
|
1503
1634
|
|
|
1504
1635
|
// src/commands/diff.ts
|
|
1505
|
-
import
|
|
1506
|
-
import
|
|
1636
|
+
import fs14 from "fs";
|
|
1637
|
+
import path14 from "path";
|
|
1507
1638
|
import pc17 from "picocolors";
|
|
1508
1639
|
var SKILLDB_DIR10 = ".skilldb";
|
|
1509
1640
|
var SKILLS_DIR7 = "skills";
|
|
@@ -1564,15 +1695,15 @@ async function diffCommand(target) {
|
|
|
1564
1695
|
if (!id.includes(".md")) id = id + ".md";
|
|
1565
1696
|
const [pack, file] = id.split("/");
|
|
1566
1697
|
const name = file.replace(".md", "").replace(/[/\\:*?"<>|]/g, "-");
|
|
1567
|
-
const localPath =
|
|
1568
|
-
if (!
|
|
1698
|
+
const localPath = path14.join(cwd, SKILLDB_DIR10, SKILLS_DIR7, pack, `${name}.md`);
|
|
1699
|
+
if (!fs14.existsSync(localPath)) {
|
|
1569
1700
|
console.error(pc17.red(`Skill "${id}" not found locally.`));
|
|
1570
1701
|
process.exit(1);
|
|
1571
1702
|
}
|
|
1572
1703
|
console.log(pc17.bold("Comparing with remote...\n"));
|
|
1573
1704
|
try {
|
|
1574
1705
|
const remote = await client.get(id);
|
|
1575
|
-
const localContent =
|
|
1706
|
+
const localContent = fs14.readFileSync(localPath, "utf-8");
|
|
1576
1707
|
if (!remote.content) {
|
|
1577
1708
|
console.log(pc17.yellow("Remote content not available (API key required)."));
|
|
1578
1709
|
return;
|
|
@@ -1603,10 +1734,10 @@ async function diffCommand(target) {
|
|
|
1603
1734
|
if (!remote?.content) continue;
|
|
1604
1735
|
const [pack, file] = id.split("/");
|
|
1605
1736
|
const name = file.replace(".md", "").replace(/[/\\:*?"<>|]/g, "-");
|
|
1606
|
-
const localPath =
|
|
1737
|
+
const localPath = path14.join(cwd, SKILLDB_DIR10, SKILLS_DIR7, pack, `${name}.md`);
|
|
1607
1738
|
let localContent = "";
|
|
1608
1739
|
try {
|
|
1609
|
-
localContent =
|
|
1740
|
+
localContent = fs14.readFileSync(localPath, "utf-8");
|
|
1610
1741
|
} catch {
|
|
1611
1742
|
continue;
|
|
1612
1743
|
}
|
|
@@ -1629,17 +1760,189 @@ ${pc17.green(`${upToDate} up to date`)}` + (changed > 0 ? pc17.yellow(`, ${chang
|
|
|
1629
1760
|
}
|
|
1630
1761
|
}
|
|
1631
1762
|
|
|
1763
|
+
// src/commands/purge.ts
|
|
1764
|
+
import fs15 from "fs";
|
|
1765
|
+
import path15 from "path";
|
|
1766
|
+
import pc18 from "picocolors";
|
|
1767
|
+
import readline3 from "readline";
|
|
1768
|
+
var SKILLDB_DIR11 = ".skilldb";
|
|
1769
|
+
var SKILLS_DIR8 = "skills";
|
|
1770
|
+
var ACTIVE_DIR8 = "active";
|
|
1771
|
+
var SLIM_DIR2 = "slim";
|
|
1772
|
+
function prompt3(question) {
|
|
1773
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
1774
|
+
return new Promise((resolve) => {
|
|
1775
|
+
rl.question(question, (answer) => {
|
|
1776
|
+
rl.close();
|
|
1777
|
+
resolve(answer.trim());
|
|
1778
|
+
});
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
function getDirectorySize(dir) {
|
|
1782
|
+
if (!fs15.existsSync(dir)) return 0;
|
|
1783
|
+
let size = 0;
|
|
1784
|
+
for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
|
|
1785
|
+
const full = path15.join(dir, entry.name);
|
|
1786
|
+
if (entry.isDirectory()) size += getDirectorySize(full);
|
|
1787
|
+
else size += fs15.statSync(full).size;
|
|
1788
|
+
}
|
|
1789
|
+
return size;
|
|
1790
|
+
}
|
|
1791
|
+
function collectAllSkills(dir) {
|
|
1792
|
+
const skills = [];
|
|
1793
|
+
if (!fs15.existsSync(dir)) return skills;
|
|
1794
|
+
for (const pack of fs15.readdirSync(dir, { withFileTypes: true })) {
|
|
1795
|
+
if (!pack.isDirectory()) continue;
|
|
1796
|
+
for (const file of fs15.readdirSync(path15.join(dir, pack.name))) {
|
|
1797
|
+
if (file.endsWith(".md")) skills.push(`${pack.name}/${file}`);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
return skills;
|
|
1801
|
+
}
|
|
1802
|
+
function collectActivePacks(cwd) {
|
|
1803
|
+
const activeDir = path15.join(cwd, SKILLDB_DIR11, ACTIVE_DIR8);
|
|
1804
|
+
const packs = /* @__PURE__ */ new Set();
|
|
1805
|
+
if (!fs15.existsSync(activeDir)) return packs;
|
|
1806
|
+
for (const entry of fs15.readdirSync(activeDir, { withFileTypes: true })) {
|
|
1807
|
+
if (entry.isDirectory()) packs.add(entry.name);
|
|
1808
|
+
}
|
|
1809
|
+
return packs;
|
|
1810
|
+
}
|
|
1811
|
+
async function purgeCommand(options) {
|
|
1812
|
+
const cwd = process.cwd();
|
|
1813
|
+
const skillsDir = path15.join(cwd, SKILLDB_DIR11, SKILLS_DIR8);
|
|
1814
|
+
const activeDir = path15.join(cwd, SKILLDB_DIR11, ACTIVE_DIR8);
|
|
1815
|
+
const slimDir = path15.join(cwd, SKILLDB_DIR11, SLIM_DIR2);
|
|
1816
|
+
if (!fs15.existsSync(path15.join(cwd, SKILLDB_DIR11))) {
|
|
1817
|
+
console.log(pc18.red('No .skilldb/ directory found. Run "skilldb init" first.'));
|
|
1818
|
+
process.exit(1);
|
|
1819
|
+
}
|
|
1820
|
+
const allSkills = collectAllSkills(skillsDir);
|
|
1821
|
+
const activePacks = collectActivePacks(cwd);
|
|
1822
|
+
const activeSkills = /* @__PURE__ */ new Set();
|
|
1823
|
+
if (fs15.existsSync(activeDir)) {
|
|
1824
|
+
for (const pack of fs15.readdirSync(activeDir, { withFileTypes: true })) {
|
|
1825
|
+
if (!pack.isDirectory()) continue;
|
|
1826
|
+
for (const file of fs15.readdirSync(path15.join(activeDir, pack.name))) {
|
|
1827
|
+
if (file.endsWith(".md")) activeSkills.add(`${pack.name}/${file}`);
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
const inactiveSkills = allSkills.filter((s) => !activeSkills.has(s));
|
|
1832
|
+
const slimSkills = collectAllSkills(slimDir);
|
|
1833
|
+
const totalSize = getDirectorySize(path15.join(cwd, SKILLDB_DIR11));
|
|
1834
|
+
const skillsSize = getDirectorySize(skillsDir);
|
|
1835
|
+
const slimSize = getDirectorySize(slimDir);
|
|
1836
|
+
console.log(pc18.bold("\nSkillDB Purge\n"));
|
|
1837
|
+
console.log(` Total skills cached: ${pc18.cyan(String(allSkills.length))}`);
|
|
1838
|
+
console.log(` Active skills: ${pc18.green(String(activeSkills.size))}`);
|
|
1839
|
+
console.log(` Inactive skills: ${pc18.yellow(String(inactiveSkills.length))}`);
|
|
1840
|
+
console.log(` Slim summaries: ${pc18.dim(String(slimSkills.length))}`);
|
|
1841
|
+
console.log(` Total disk usage: ${pc18.cyan((totalSize / 1024).toFixed(0) + " KB")}`);
|
|
1842
|
+
console.log();
|
|
1843
|
+
if (allSkills.length === 0 && slimSkills.length === 0) {
|
|
1844
|
+
console.log(pc18.dim("Nothing to purge."));
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1847
|
+
const stats = { skillsRemoved: 0, packsRemoved: 0, slimRemoved: 0, bytesFreed: 0, activeKept: activeSkills.size };
|
|
1848
|
+
let toPurge = [];
|
|
1849
|
+
let purgeSlim = options?.slim || options?.all || false;
|
|
1850
|
+
if (options?.all) {
|
|
1851
|
+
toPurge = [...allSkills];
|
|
1852
|
+
purgeSlim = true;
|
|
1853
|
+
console.log(pc18.red(`Will remove ALL ${allSkills.length} cached skills + ${slimSkills.length} slim summaries`));
|
|
1854
|
+
} else if (options?.inactive) {
|
|
1855
|
+
toPurge = inactiveSkills;
|
|
1856
|
+
console.log(pc18.yellow(`Will remove ${inactiveSkills.length} inactive skills (keeping ${activeSkills.size} active)`));
|
|
1857
|
+
} else {
|
|
1858
|
+
toPurge = inactiveSkills;
|
|
1859
|
+
purgeSlim = true;
|
|
1860
|
+
console.log(pc18.yellow(`Will remove ${inactiveSkills.length} inactive skills + ${slimSkills.length} slim summaries`));
|
|
1861
|
+
console.log(pc18.green(` Keeping ${activeSkills.size} active skills`));
|
|
1862
|
+
}
|
|
1863
|
+
if (toPurge.length === 0 && !purgeSlim) {
|
|
1864
|
+
console.log(pc18.dim("\nNothing to purge \u2014 all skills are active."));
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
if (!options?.force && !options?.dryRun) {
|
|
1868
|
+
const answer = await prompt3(`
|
|
1869
|
+
Continue? (y/N) `);
|
|
1870
|
+
if (answer.toLowerCase() !== "y") {
|
|
1871
|
+
console.log(pc18.dim("Cancelled."));
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
if (options?.dryRun) {
|
|
1876
|
+
console.log(pc18.dim("\n (Dry run \u2014 no files will be deleted)\n"));
|
|
1877
|
+
for (const skill of toPurge.slice(0, 20)) {
|
|
1878
|
+
console.log(pc18.dim(` would remove: ${skill}`));
|
|
1879
|
+
}
|
|
1880
|
+
if (toPurge.length > 20) console.log(pc18.dim(` ... and ${toPurge.length - 20} more`));
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1883
|
+
const manifest = readManifest(cwd);
|
|
1884
|
+
for (const skillId of toPurge) {
|
|
1885
|
+
const [pack, file] = skillId.split("/");
|
|
1886
|
+
const filePath = path15.join(skillsDir, pack, file);
|
|
1887
|
+
if (fs15.existsSync(filePath)) {
|
|
1888
|
+
stats.bytesFreed += fs15.statSync(filePath).size;
|
|
1889
|
+
fs15.unlinkSync(filePath);
|
|
1890
|
+
stats.skillsRemoved++;
|
|
1891
|
+
}
|
|
1892
|
+
if (manifest.installed[skillId]) {
|
|
1893
|
+
delete manifest.installed[skillId];
|
|
1894
|
+
}
|
|
1895
|
+
const packDir = path15.join(skillsDir, pack);
|
|
1896
|
+
if (fs15.existsSync(packDir) && fs15.readdirSync(packDir).length === 0) {
|
|
1897
|
+
fs15.rmdirSync(packDir);
|
|
1898
|
+
stats.packsRemoved++;
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
if (purgeSlim && fs15.existsSync(slimDir)) {
|
|
1902
|
+
for (const pack of fs15.readdirSync(slimDir, { withFileTypes: true })) {
|
|
1903
|
+
if (!pack.isDirectory()) continue;
|
|
1904
|
+
const packPath = path15.join(slimDir, pack.name);
|
|
1905
|
+
for (const file of fs15.readdirSync(packPath)) {
|
|
1906
|
+
const filePath = path15.join(packPath, file);
|
|
1907
|
+
stats.bytesFreed += fs15.statSync(filePath).size;
|
|
1908
|
+
fs15.unlinkSync(filePath);
|
|
1909
|
+
stats.slimRemoved++;
|
|
1910
|
+
}
|
|
1911
|
+
if (fs15.readdirSync(packPath).length === 0) fs15.rmdirSync(packPath);
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
if (options?.all && fs15.existsSync(activeDir)) {
|
|
1915
|
+
for (const pack of fs15.readdirSync(activeDir, { withFileTypes: true })) {
|
|
1916
|
+
if (!pack.isDirectory()) continue;
|
|
1917
|
+
const packPath = path15.join(activeDir, pack.name);
|
|
1918
|
+
for (const file of fs15.readdirSync(packPath)) {
|
|
1919
|
+
fs15.unlinkSync(path15.join(packPath, file));
|
|
1920
|
+
}
|
|
1921
|
+
fs15.rmdirSync(packPath);
|
|
1922
|
+
}
|
|
1923
|
+
stats.activeKept = 0;
|
|
1924
|
+
}
|
|
1925
|
+
writeManifest(cwd, manifest);
|
|
1926
|
+
console.log(pc18.green(`
|
|
1927
|
+
\u2713 Purge complete`));
|
|
1928
|
+
console.log(` Skills removed: ${stats.skillsRemoved}`);
|
|
1929
|
+
console.log(` Slim removed: ${stats.slimRemoved}`);
|
|
1930
|
+
console.log(` Packs cleaned: ${stats.packsRemoved}`);
|
|
1931
|
+
console.log(` Space freed: ${(stats.bytesFreed / 1024).toFixed(0)} KB`);
|
|
1932
|
+
console.log(` Active kept: ${stats.activeKept}`);
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1632
1935
|
// src/cli.ts
|
|
1633
1936
|
var program = new Command();
|
|
1634
1937
|
program.name("skilldb").description("SkillDB CLI \u2014 discover, install, and manage AI agent skills").version("0.3.0");
|
|
1635
|
-
program.command("init").description("Initialize SkillDB in this project (detect IDE,
|
|
1938
|
+
program.command("init").description("Initialize SkillDB in this project (detect IDE, choose install mode)").option("-t, --target <mode>", "Install mode: mcp, skills-dir, claude-md, hybrid").action((opts) => initCommand(opts));
|
|
1636
1939
|
program.command("login").description("Save your SkillDB API key").action(loginCommand);
|
|
1637
1940
|
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) => {
|
|
1638
1941
|
searchCommand(queryParts.join(" "), options);
|
|
1639
1942
|
});
|
|
1640
1943
|
program.command("list").description("List skills, optionally filtered").option("-c, --category <name>", "Filter by category").option("-p, --pack <name>", "Filter by pack").option("-l, --limit <n>", "Max results", "50").action(listCommand);
|
|
1641
1944
|
program.command("get <id>").description('Download a single skill to .skilldb/skills/ (e.g. "software-skills/code-review")').action(getCommand);
|
|
1642
|
-
program.command("add <pack>").description("Download
|
|
1945
|
+
program.command("add <pack>").description("Download skills (use --target to choose where)").option("-t, --target <mode>", "Where to install: cache (default), skills-dir, hybrid").action((pack, opts) => addCommand(pack, opts));
|
|
1643
1946
|
program.command("info <id>").description('Show metadata and preview for a skill (e.g. "software-skills/code-review")').action(infoCommand);
|
|
1644
1947
|
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) => {
|
|
1645
1948
|
if (!profile && !options.list && !options.current) {
|
|
@@ -1668,5 +1971,8 @@ program.command("stats").description("Show local statistics and coverage").actio
|
|
|
1668
1971
|
program.command("diff [target]").description("Compare local skills with latest remote versions").action((target) => {
|
|
1669
1972
|
diffCommand(target);
|
|
1670
1973
|
});
|
|
1974
|
+
program.command("purge").description("Remove cached skills to free disk space").option("-a, --all", "Remove ALL cached skills (including active)").option("-i, --inactive", "Remove only inactive skills").option("-s, --slim", "Also remove slim summaries").option("-n, --dry-run", "Show what would be removed without deleting").option("-f, --force", "Skip confirmation prompt").action((opts) => {
|
|
1975
|
+
purgeCommand(opts);
|
|
1976
|
+
});
|
|
1671
1977
|
program.parse();
|
|
1672
1978
|
//# sourceMappingURL=cli.js.map
|