@hasna/skills 0.1.13 → 0.1.15
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/bin/index.js +1743 -420
- package/bin/mcp.js +791 -259
- package/dist/index.d.ts +5 -2
- package/dist/index.js +533 -75
- package/dist/lib/config.d.ts +27 -0
- package/dist/lib/config.test.d.ts +1 -0
- package/dist/lib/installer.d.ts +36 -2
- package/dist/lib/registry.d.ts +16 -0
- package/dist/lib/scheduler.d.ts +47 -0
- package/dist/types/api.d.ts +74 -0
- package/package.json +5 -2
- package/skills/_common/index.ts +4 -0
- package/skills/_common/vision.ts +374 -0
- package/skills/skill-academic-journal-matcher/bin/cli.ts +34 -0
- package/skills/skill-action-item-router/bin/cli.ts +34 -0
- package/skills/skill-ad-creative-generator/bin/cli.ts +34 -0
- package/skills/skill-advanced-math/bin/cli.ts +34 -0
- package/skills/skill-analyze-data/bin/cli.ts +19 -0
- package/skills/skill-anomaly-investigator/bin/cli.ts +34 -0
- package/skills/skill-api-test-suite/bin/cli.ts +34 -0
- package/skills/skill-apidocs/bin/cli.ts +87 -0
- package/skills/skill-audio-cleanup-lab/bin/cli.ts +6 -0
- package/skills/skill-audiobook-chapter-proofer/bin/cli.ts +34 -0
- package/skills/skill-banner-ad-suite/bin/cli.ts +34 -0
- package/skills/skill-benchmark-finder/bin/cli.ts +34 -0
- package/skills/skill-bio-sequence-tool/bin/cli.ts +34 -0
- package/skills/skill-blog-topic-cluster/bin/cli.ts +34 -0
- package/skills/skill-brand-style-guide/bin/cli.ts +19 -0
- package/skills/skill-brand-voice-audit/bin/cli.ts +34 -0
- package/skills/skill-budget-variance-analyzer/bin/cli.ts +6 -0
- package/skills/skill-businessactivity/bin/cli.ts +28 -0
- package/skills/skill-calendar-events/bin/cli.ts +34 -0
- package/skills/skill-campaign-metric-brief/bin/cli.ts +34 -0
- package/skills/skill-campaign-moodboard/bin/cli.ts +34 -0
- package/skills/skill-caption-style-stylist/bin/cli.ts +34 -0
- package/skills/skill-chemistry-calculator/bin/cli.ts +34 -0
- package/skills/skill-churn-risk-notifier/bin/cli.ts +34 -0
- package/skills/skill-citation-formatter/bin/cli.ts +34 -0
- package/skills/skill-classroom-newsletter-kit/bin/cli.ts +34 -0
- package/skills/skill-color-palette-harmonizer/bin/cli.ts +34 -0
- package/skills/skill-colorextract/SKILL.md +35 -0
- package/skills/skill-colorextract/bun.lock +102 -0
- package/skills/skill-colorextract/package.json +13 -0
- package/skills/skill-colorextract/src/index.ts +405 -0
- package/skills/skill-competitor-ad-analyzer/bin/cli.ts +34 -0
- package/skills/skill-compliance-copy-check/bin/cli.ts +34 -0
- package/skills/skill-compliance-report-pack/bin/cli.ts +34 -0
- package/skills/skill-compress-video/bin/cli.ts +19 -0
- package/skills/skill-consolelog/bin/cli.ts +884 -0
- package/skills/skill-contract-plainlanguage/bin/cli.ts +34 -0
- package/skills/skill-copytone-translator/bin/cli.ts +34 -0
- package/skills/skill-create-blog-article/bin/cli.ts +34 -0
- package/skills/skill-create-ebook/bin/cli.ts +34 -0
- package/skills/skill-crm-note-enhancer/bin/cli.ts +34 -0
- package/skills/skill-customer-journey-mapper/bin/cli.ts +34 -0
- package/skills/skill-dashboard-builder/bin/cli.ts +34 -0
- package/skills/skill-dashboard-narrator/bin/cli.ts +34 -0
- package/skills/skill-data-anonymizer/bin/cli.ts +34 -0
- package/skills/skill-database-explorer/bin/cli.ts +34 -0
- package/skills/skill-dataset-health-check/bin/cli.ts +34 -0
- package/skills/skill-decision-journal/bin/cli.ts +34 -0
- package/skills/skill-delegation-brief-writer/bin/cli.ts +34 -0
- package/skills/skill-destination-briefing/bin/cli.ts +34 -0
- package/skills/skill-diff-viewer/bin/cli.ts +34 -0
- package/skills/skill-domainpurchase/SKILL.md +46 -0
- package/skills/skill-domainpurchase/bin/cli.ts +683 -0
- package/skills/skill-domainsearch/SKILL.md +41 -0
- package/skills/skill-domainsearch/bin/cli.ts +410 -0
- package/skills/skill-educational-resource-finder/bin/cli.ts +34 -0
- package/skills/skill-email-campaign/bin/cli.ts +34 -0
- package/skills/skill-exam-readiness-check/bin/cli.ts +34 -0
- package/skills/skill-experiment-power-calculator/bin/cli.ts +34 -0
- package/skills/skill-extract-audio/bin/cli.ts +19 -0
- package/skills/skill-extract-frames/bin/cli.ts +34 -0
- package/skills/skill-extract-invoice/bin/cli.ts +34 -0
- package/skills/skill-family-activity-curator/bin/cli.ts +34 -0
- package/skills/skill-faq-packager/bin/cli.ts +34 -0
- package/skills/skill-feedback-survey-designer/bin/cli.ts +34 -0
- package/skills/skill-field-trip-planner/bin/cli.ts +34 -0
- package/skills/skill-file-organizer/bin/cli.ts +34 -0
- package/skills/skill-folder-tree/bin/cli.ts +34 -0
- package/skills/skill-forecast-scenario-lab/bin/cli.ts +34 -0
- package/skills/skill-form-filler/bin/cli.ts +34 -0
- package/skills/skill-generate-api-client/bin/cli.ts +34 -0
- package/skills/skill-generate-book-cover/bin/cli.ts +34 -0
- package/skills/skill-generate-chart/bin/cli.ts +34 -0
- package/skills/skill-generate-diagram/bin/cli.ts +34 -0
- package/skills/skill-generate-dockerfile/bin/cli.ts +34 -0
- package/skills/skill-generate-documentation/bin/cli.ts +34 -0
- package/skills/skill-generate-docx/bin/cli.ts +6 -0
- package/skills/skill-generate-env/bin/cli.ts +34 -0
- package/skills/skill-generate-excel/bin/cli.ts +34 -0
- package/skills/skill-generate-favicon/bin/cli.ts +34 -0
- package/skills/skill-generate-mock-data/bin/cli.ts +34 -0
- package/skills/skill-generate-pdf/bin/cli.ts +6 -0
- package/skills/skill-generate-pr-description/bin/cli.ts +34 -0
- package/skills/skill-generate-presentation/bin/cli.ts +34 -0
- package/skills/skill-generate-qrcode/bin/cli.ts +34 -0
- package/skills/skill-generate-regex/bin/cli.ts +34 -0
- package/skills/skill-generate-resume/bin/cli.ts +34 -0
- package/skills/skill-generate-sitemap/bin/cli.ts +34 -0
- package/skills/skill-generate-social-posts/bin/cli.ts +34 -0
- package/skills/skill-generate-sql/bin/cli.ts +34 -0
- package/skills/skill-gif-maker/bin/cli.ts +34 -0
- package/skills/skill-github-manager/bin/cli.ts +34 -0
- package/skills/skill-gmail/bin/cli.ts +34 -0
- package/skills/skill-goal-quarterly-roadmap/bin/cli.ts +34 -0
- package/skills/skill-grant-application-drafter/bin/cli.ts +34 -0
- package/skills/skill-grocery-basket-optimizer/bin/cli.ts +34 -0
- package/skills/skill-guest-communication-suite/bin/cli.ts +34 -0
- package/skills/skill-habit-reflection-digest/bin/cli.ts +34 -0
- package/skills/skill-highlight-reel-generator/bin/cli.ts +34 -0
- package/skills/skill-homework-feedback-coach/bin/cli.ts +34 -0
- package/skills/skill-hook/bunfig.toml +5 -0
- package/skills/skill-household-maintenance-mgr/bin/cli.ts +34 -0
- package/skills/skill-http-server/bin/cli.ts +34 -0
- package/skills/skill-implementation/bunfig.toml +5 -0
- package/skills/skill-implementation-agent/bin/cli.ts +34 -0
- package/skills/skill-implementation-plan/bin/cli.ts +34 -0
- package/skills/skill-implementation-todo/bin/cli.ts +34 -0
- package/skills/skill-inbox-priority-planner/bin/cli.ts +34 -0
- package/skills/skill-invoice/bin/cli.ts +20 -0
- package/skills/skill-invoice-dispute-helper/bin/cli.ts +34 -0
- package/skills/skill-itinerary-architect/bin/cli.ts +34 -0
- package/skills/skill-jingle-composer/bin/cli.ts +34 -0
- package/skills/skill-kpi-digest-generator/bin/cli.ts +34 -0
- package/skills/skill-lab-notebook-formatter/bin/cli.ts +34 -0
- package/skills/skill-landing-page-copy/bin/cli.ts +34 -0
- package/skills/skill-latex-table-generator/bin/cli.ts +34 -0
- package/skills/skill-learning-style-profiler/bin/cli.ts +34 -0
- package/skills/skill-lesson-plan-customizer/bin/cli.ts +34 -0
- package/skills/skill-livestream-runofshow/bin/cli.ts +34 -0
- package/skills/skill-longform-structurer/bin/cli.ts +34 -0
- package/skills/skill-lorem-generator/bin/cli.ts +34 -0
- package/skills/skill-managehook/bin/cli.ts +241 -0
- package/skills/skill-managemcp/bin/cli.ts +241 -0
- package/skills/skill-manageskill/bin/cli.ts +241 -0
- package/skills/skill-markdown-validator/bin/cli.ts +34 -0
- package/skills/skill-mcp-builder/bin/cli.ts +34 -0
- package/skills/skill-meal-plan-designer/bin/cli.ts +34 -0
- package/skills/skill-meeting-insight-summarizer/bin/cli.ts +34 -0
- package/skills/skill-merge-pdfs/bin/cli.ts +34 -0
- package/skills/skill-microcopy-generator/bin/cli.ts +34 -0
- package/skills/skill-mindfulness-prompt-cache/bin/cli.ts +34 -0
- package/skills/skill-notion-manager/bin/cli.ts +34 -0
- package/skills/skill-onboarding-sequence-builder/bin/cli.ts +34 -0
- package/skills/skill-onsite-ops-checklist/bin/cli.ts +34 -0
- package/skills/skill-outreach-cadence-designer/bin/cli.ts +34 -0
- package/skills/skill-packaging-concept-studio/bin/cli.ts +34 -0
- package/skills/skill-packing-plan-pro/bin/cli.ts +34 -0
- package/skills/skill-parent-teacher-brief/bin/cli.ts +34 -0
- package/skills/skill-partner-kit-assembler/bin/cli.ts +34 -0
- package/skills/skill-payroll-change-prepper/bin/cli.ts +34 -0
- package/skills/skill-persona-based-adwriter/bin/cli.ts +34 -0
- package/skills/skill-persona-generator/bin/cli.ts +34 -0
- package/skills/skill-personal-daily-ops/bin/cli.ts +34 -0
- package/skills/skill-pet-care-scheduler/bin/cli.ts +34 -0
- package/skills/skill-podcast-show-notes/bin/cli.ts +34 -0
- package/skills/skill-presentation-theme-maker/bin/cli.ts +34 -0
- package/skills/skill-press-release-drafter/bin/cli.ts +34 -0
- package/skills/skill-print-collateral-designer/bin/cli.ts +34 -0
- package/skills/skill-procurement-scorecard/bin/cli.ts +34 -0
- package/skills/skill-product-demo-script/bin/cli.ts +34 -0
- package/skills/skill-product-mockup/bin/cli.ts +34 -0
- package/skills/skill-project-retro-companion/bin/cli.ts +34 -0
- package/skills/skill-proposal-redline-advisor/bin/cli.ts +34 -0
- package/skills/skill-regex-tester/bin/cli.ts +34 -0
- package/skills/skill-remove-background/bin/cli.ts +34 -0
- package/skills/skill-risk-disclosure-kit/bin/cli.ts +34 -0
- package/skills/skill-roi-comparison-tool/bin/cli.ts +34 -0
- package/skills/skill-sales-call-recapper/bin/cli.ts +34 -0
- package/skills/skill-salescopy/bin/cli.ts +20 -0
- package/skills/skill-scaffold-project/bin/cli.ts +34 -0
- package/skills/skill-scholarship-tracker/bin/cli.ts +34 -0
- package/skills/skill-scientific-figure-check/bin/cli.ts +34 -0
- package/skills/skill-seating-chart-maker/bin/cli.ts +34 -0
- package/skills/skill-security-audit/bin/cli.ts +34 -0
- package/skills/skill-seo-brief-builder/bin/cli.ts +34 -0
- package/skills/skill-siteanalyze/SKILL.md +25 -0
- package/skills/skill-siteanalyze/package.json +13 -0
- package/skills/skill-siteanalyze/src/index.ts +592 -0
- package/skills/skill-slack-assistant/bin/cli.ts +34 -0
- package/skills/skill-sleep-routine-analyzer/bin/cli.ts +34 -0
- package/skills/skill-social-media-kit/bin/cli.ts +34 -0
- package/skills/skill-split-pdf/bin/cli.ts +34 -0
- package/skills/skill-sponsorship-proposal-lab/bin/cli.ts +34 -0
- package/skills/skill-spreadsheet-cleanroom/bin/cli.ts +34 -0
- package/skills/skill-statistical-test-selector/bin/cli.ts +34 -0
- package/skills/skill-stress-relief-playbook/bin/cli.ts +34 -0
- package/skills/skill-study-guide-builder/bin/cli.ts +34 -0
- package/skills/skill-subscription-spend-watcher/bin/cli.ts +34 -0
- package/skills/skill-subtitle/bin/cli.ts +20 -0
- package/skills/skill-survey-insight-extractor/bin/cli.ts +34 -0
- package/skills/skill-terraform-generator/bin/cli.ts +34 -0
- package/skills/skill-testimonial-graphics/bin/cli.ts +34 -0
- package/skills/skill-timesheet/bin/cli.ts +47 -0
- package/skills/skill-travel-budget-balancer/bin/cli.ts +34 -0
- package/skills/skill-validate-config/bin/cli.ts +34 -0
- package/skills/skill-video-cut-suggester/bin/cli.ts +34 -0
- package/skills/skill-video-downloader/bin/cli.ts +34 -0
- package/skills/skill-video-thumbnail/bin/cli.ts +34 -0
- package/skills/skill-voiceover-casting-assistant/bin/cli.ts +34 -0
- package/skills/skill-watermark/bin/cli.ts +34 -0
- package/skills/skill-webcrawling/bin/cli.ts +21 -0
- package/skills/skill-webinar-script-coach/bin/cli.ts +34 -0
- package/skills/skill-wellness-progress-reporter/bin/cli.ts +34 -0
- package/skills/skill-workout-cycle-planner/bin/cli.ts +34 -0
package/dist/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// src/lib/registry.ts
|
|
3
|
+
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { homedir } from "os";
|
|
3
6
|
var CATEGORIES = [
|
|
4
7
|
"Development Tools",
|
|
5
8
|
"Business & Marketing",
|
|
@@ -1036,6 +1039,20 @@ var SKILLS = [
|
|
|
1036
1039
|
category: "Design & Branding",
|
|
1037
1040
|
tags: ["testimonials", "graphics", "social-proof", "marketing"]
|
|
1038
1041
|
},
|
|
1042
|
+
{
|
|
1043
|
+
name: "colorextract",
|
|
1044
|
+
displayName: "Color Extract",
|
|
1045
|
+
description: "Extract complete color palettes from screenshots and images using Claude Vision. Outputs open-styles compatible profiles.",
|
|
1046
|
+
category: "Design & Branding",
|
|
1047
|
+
tags: ["colors", "palette", "design", "vision", "screenshot", "extract", "open-styles"]
|
|
1048
|
+
},
|
|
1049
|
+
{
|
|
1050
|
+
name: "siteanalyze",
|
|
1051
|
+
displayName: "Site Analyze",
|
|
1052
|
+
description: "Analyze any website's design system \u2014 detects shadcn/ui, Tailwind, extracts colors, typography, and components via Playwright + Claude Vision.",
|
|
1053
|
+
category: "Design & Branding",
|
|
1054
|
+
tags: ["design", "shadcn", "tailwind", "colors", "typography", "playwright", "analysis", "open-styles"]
|
|
1055
|
+
},
|
|
1039
1056
|
{
|
|
1040
1057
|
name: "browse",
|
|
1041
1058
|
displayName: "Browse",
|
|
@@ -1436,8 +1453,91 @@ var SKILLS = [
|
|
|
1436
1453
|
tags: ["seating", "chart", "events", "venues"]
|
|
1437
1454
|
}
|
|
1438
1455
|
];
|
|
1456
|
+
function parseSkillMdFrontmatter(content) {
|
|
1457
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
1458
|
+
if (!match)
|
|
1459
|
+
return null;
|
|
1460
|
+
const result = {};
|
|
1461
|
+
for (const line of match[1].split(`
|
|
1462
|
+
`)) {
|
|
1463
|
+
const colon = line.indexOf(":");
|
|
1464
|
+
if (colon === -1)
|
|
1465
|
+
continue;
|
|
1466
|
+
const key = line.slice(0, colon).trim();
|
|
1467
|
+
const value = line.slice(colon + 1).trim();
|
|
1468
|
+
if (!key || !value)
|
|
1469
|
+
continue;
|
|
1470
|
+
if (key === "name")
|
|
1471
|
+
result.name = value;
|
|
1472
|
+
else if (key === "description")
|
|
1473
|
+
result.description = value;
|
|
1474
|
+
else if (key === "displayName" || key === "display_name")
|
|
1475
|
+
result.displayName = value;
|
|
1476
|
+
else if (key === "category")
|
|
1477
|
+
result.category = value;
|
|
1478
|
+
else if (key === "tags") {
|
|
1479
|
+
result.tags = value.replace(/[\[\]]/g, "").split(",").map((t) => t.trim()).filter(Boolean);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
1483
|
+
}
|
|
1484
|
+
function discoverSkillsInDir(dir) {
|
|
1485
|
+
if (!existsSync(dir))
|
|
1486
|
+
return [];
|
|
1487
|
+
const result = [];
|
|
1488
|
+
try {
|
|
1489
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
1490
|
+
for (const entry of entries) {
|
|
1491
|
+
if (!entry.isDirectory())
|
|
1492
|
+
continue;
|
|
1493
|
+
const skillMdPath = join(dir, entry.name, "SKILL.md");
|
|
1494
|
+
if (!existsSync(skillMdPath))
|
|
1495
|
+
continue;
|
|
1496
|
+
let content;
|
|
1497
|
+
try {
|
|
1498
|
+
content = readFileSync(skillMdPath, "utf-8");
|
|
1499
|
+
} catch {
|
|
1500
|
+
continue;
|
|
1501
|
+
}
|
|
1502
|
+
const fm = parseSkillMdFrontmatter(content);
|
|
1503
|
+
if (!fm?.name)
|
|
1504
|
+
continue;
|
|
1505
|
+
const name = fm.name.replace(/^skill-/, "");
|
|
1506
|
+
result.push({
|
|
1507
|
+
name,
|
|
1508
|
+
displayName: fm.displayName || name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
1509
|
+
description: fm.description || "",
|
|
1510
|
+
category: fm.category || "Development Tools",
|
|
1511
|
+
tags: fm.tags || [],
|
|
1512
|
+
source: "custom"
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
} catch {}
|
|
1516
|
+
return result;
|
|
1517
|
+
}
|
|
1518
|
+
var _registryCache = null;
|
|
1519
|
+
var _registryCacheTime = 0;
|
|
1520
|
+
var REGISTRY_CACHE_TTL = 5000;
|
|
1521
|
+
function loadRegistry(cwd) {
|
|
1522
|
+
const now = Date.now();
|
|
1523
|
+
if (_registryCache && now - _registryCacheTime < REGISTRY_CACHE_TTL) {
|
|
1524
|
+
return _registryCache;
|
|
1525
|
+
}
|
|
1526
|
+
const official = SKILLS.map((s) => ({ ...s, source: "official" }));
|
|
1527
|
+
const globalCustom = discoverSkillsInDir(join(homedir(), ".skills"));
|
|
1528
|
+
const projectCustom = discoverSkillsInDir(join(cwd || process.cwd(), ".skills", "custom-skills"));
|
|
1529
|
+
const customNames = new Set([...globalCustom, ...projectCustom].map((s) => s.name));
|
|
1530
|
+
const filtered = official.filter((s) => !customNames.has(s.name));
|
|
1531
|
+
_registryCache = [...filtered, ...globalCustom, ...projectCustom];
|
|
1532
|
+
_registryCacheTime = now;
|
|
1533
|
+
return _registryCache;
|
|
1534
|
+
}
|
|
1535
|
+
function clearRegistryCache() {
|
|
1536
|
+
_registryCache = null;
|
|
1537
|
+
_registryCacheTime = 0;
|
|
1538
|
+
}
|
|
1439
1539
|
function getSkillsByCategory(category) {
|
|
1440
|
-
return
|
|
1540
|
+
return loadRegistry().filter((s) => s.category === category);
|
|
1441
1541
|
}
|
|
1442
1542
|
function editDistance(a, b) {
|
|
1443
1543
|
if (a === b)
|
|
@@ -1483,7 +1583,7 @@ function searchSkills(query) {
|
|
|
1483
1583
|
if (words.length === 0)
|
|
1484
1584
|
return [];
|
|
1485
1585
|
const scored = [];
|
|
1486
|
-
for (const skill of
|
|
1586
|
+
for (const skill of loadRegistry()) {
|
|
1487
1587
|
const nameLower = skill.name.toLowerCase();
|
|
1488
1588
|
const displayNameLower = skill.displayName.toLowerCase();
|
|
1489
1589
|
const descriptionLower = skill.description.toLowerCase();
|
|
@@ -1519,25 +1619,40 @@ function searchSkills(query) {
|
|
|
1519
1619
|
return scored.map((s) => s.skill);
|
|
1520
1620
|
}
|
|
1521
1621
|
function getSkill(name) {
|
|
1522
|
-
return
|
|
1622
|
+
return loadRegistry().find((s) => s.name === name);
|
|
1523
1623
|
}
|
|
1524
1624
|
function getSkillsByTag(tag) {
|
|
1525
1625
|
const needle = tag.toLowerCase();
|
|
1526
|
-
return
|
|
1626
|
+
return loadRegistry().filter((s) => s.tags.some((t) => t.toLowerCase().includes(needle)));
|
|
1527
1627
|
}
|
|
1528
1628
|
function getAllTags() {
|
|
1529
1629
|
const tagSet = new Set;
|
|
1530
|
-
for (const skill of
|
|
1630
|
+
for (const skill of loadRegistry()) {
|
|
1531
1631
|
for (const tag of skill.tags) {
|
|
1532
1632
|
tagSet.add(tag.toLowerCase());
|
|
1533
1633
|
}
|
|
1534
1634
|
}
|
|
1535
1635
|
return Array.from(tagSet).sort();
|
|
1536
1636
|
}
|
|
1637
|
+
function levenshtein(a, b) {
|
|
1638
|
+
const m = a.length, n = b.length;
|
|
1639
|
+
const dp = Array.from({ length: m + 1 }, (_, i) => Array.from({ length: n + 1 }, (_2, j) => i === 0 ? j : j === 0 ? i : 0));
|
|
1640
|
+
for (let i = 1;i <= m; i++) {
|
|
1641
|
+
for (let j = 1;j <= n; j++) {
|
|
1642
|
+
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
return dp[m][n];
|
|
1646
|
+
}
|
|
1647
|
+
function findSimilarSkills(query, maxResults = 3) {
|
|
1648
|
+
const q = query.toLowerCase();
|
|
1649
|
+
const scored = loadRegistry().map((s) => ({ name: s.name, dist: levenshtein(q, s.name.toLowerCase()) })).filter((s) => s.dist <= Math.max(3, Math.floor(q.length / 2))).sort((a, b) => a.dist - b.dist);
|
|
1650
|
+
return scored.slice(0, maxResults).map((s) => s.name);
|
|
1651
|
+
}
|
|
1537
1652
|
// src/lib/installer.ts
|
|
1538
|
-
import { existsSync, cpSync, mkdirSync, writeFileSync, rmSync, readdirSync, statSync, readFileSync } from "fs";
|
|
1539
|
-
import { join, dirname } from "path";
|
|
1540
|
-
import { homedir } from "os";
|
|
1653
|
+
import { existsSync as existsSync2, cpSync, mkdirSync, writeFileSync, rmSync, readdirSync as readdirSync2, statSync, readFileSync as readFileSync2, accessSync, constants } from "fs";
|
|
1654
|
+
import { join as join2, dirname } from "path";
|
|
1655
|
+
import { homedir as homedir2 } from "os";
|
|
1541
1656
|
import { fileURLToPath } from "url";
|
|
1542
1657
|
|
|
1543
1658
|
// src/lib/utils.ts
|
|
@@ -1550,36 +1665,36 @@ var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
|
1550
1665
|
function findSkillsDir() {
|
|
1551
1666
|
let dir = __dirname2;
|
|
1552
1667
|
for (let i = 0;i < 5; i++) {
|
|
1553
|
-
const candidate =
|
|
1554
|
-
if (
|
|
1668
|
+
const candidate = join2(dir, "skills");
|
|
1669
|
+
if (existsSync2(candidate) && !dir.includes(".skills")) {
|
|
1555
1670
|
return candidate;
|
|
1556
1671
|
}
|
|
1557
1672
|
dir = dirname(dir);
|
|
1558
1673
|
}
|
|
1559
|
-
return
|
|
1674
|
+
return join2(__dirname2, "..", "skills");
|
|
1560
1675
|
}
|
|
1561
1676
|
var SKILLS_DIR = findSkillsDir();
|
|
1562
1677
|
function getSkillPath(name) {
|
|
1563
1678
|
const skillName = normalizeSkillName(name);
|
|
1564
|
-
return
|
|
1679
|
+
return join2(SKILLS_DIR, skillName);
|
|
1565
1680
|
}
|
|
1566
1681
|
function skillExists(name) {
|
|
1567
|
-
return
|
|
1682
|
+
return existsSync2(getSkillPath(name));
|
|
1568
1683
|
}
|
|
1569
1684
|
function installSkill(name, options = {}) {
|
|
1570
1685
|
const { targetDir = process.cwd(), overwrite = false } = options;
|
|
1571
1686
|
const skillName = normalizeSkillName(name);
|
|
1572
1687
|
const sourcePath = getSkillPath(name);
|
|
1573
|
-
const destDir =
|
|
1574
|
-
const destPath =
|
|
1575
|
-
if (!
|
|
1688
|
+
const destDir = join2(targetDir, ".skills");
|
|
1689
|
+
const destPath = join2(destDir, skillName);
|
|
1690
|
+
if (!existsSync2(sourcePath)) {
|
|
1576
1691
|
return {
|
|
1577
1692
|
skill: name,
|
|
1578
1693
|
success: false,
|
|
1579
1694
|
error: `Skill '${name}' not found`
|
|
1580
1695
|
};
|
|
1581
1696
|
}
|
|
1582
|
-
if (
|
|
1697
|
+
if (existsSync2(destPath) && !overwrite) {
|
|
1583
1698
|
return {
|
|
1584
1699
|
skill: name,
|
|
1585
1700
|
success: false,
|
|
@@ -1588,10 +1703,10 @@ function installSkill(name, options = {}) {
|
|
|
1588
1703
|
};
|
|
1589
1704
|
}
|
|
1590
1705
|
try {
|
|
1591
|
-
if (!
|
|
1706
|
+
if (!existsSync2(destDir)) {
|
|
1592
1707
|
mkdirSync(destDir, { recursive: true });
|
|
1593
1708
|
}
|
|
1594
|
-
if (
|
|
1709
|
+
if (existsSync2(destPath) && overwrite) {
|
|
1595
1710
|
rmSync(destPath, { recursive: true, force: true });
|
|
1596
1711
|
}
|
|
1597
1712
|
cpSync(sourcePath, destPath, {
|
|
@@ -1602,6 +1717,7 @@ function installSkill(name, options = {}) {
|
|
|
1602
1717
|
}
|
|
1603
1718
|
});
|
|
1604
1719
|
updateSkillsIndex(destDir);
|
|
1720
|
+
recordInstall(destDir, name);
|
|
1605
1721
|
const meta = getSkill(name);
|
|
1606
1722
|
if (meta?.dependencies && meta.dependencies.length > 0) {
|
|
1607
1723
|
const installed = getInstalledSkills(targetDir);
|
|
@@ -1629,8 +1745,10 @@ function installSkills(names, options = {}) {
|
|
|
1629
1745
|
return names.map((name) => installSkill(name, options));
|
|
1630
1746
|
}
|
|
1631
1747
|
function updateSkillsIndex(skillsDir) {
|
|
1632
|
-
const indexPath =
|
|
1633
|
-
const
|
|
1748
|
+
const indexPath = join2(skillsDir, "index.ts");
|
|
1749
|
+
const meta = loadMeta(skillsDir);
|
|
1750
|
+
const disabledSet = new Set(meta.disabled || []);
|
|
1751
|
+
const skills = readdirSync2(skillsDir).filter((f) => f.startsWith("skill-") && !f.includes(".") && !disabledSet.has(f.replace("skill-", "")));
|
|
1634
1752
|
const exports = skills.map((s) => {
|
|
1635
1753
|
const name = s.replace("skill-", "").replace(/-/g, "_");
|
|
1636
1754
|
return `export * as ${name} from './${s}/src/index.js';`;
|
|
@@ -1645,50 +1763,130 @@ ${exports}
|
|
|
1645
1763
|
`;
|
|
1646
1764
|
writeFileSync(indexPath, content);
|
|
1647
1765
|
}
|
|
1766
|
+
function getMetaPath(skillsDir) {
|
|
1767
|
+
return join2(skillsDir, ".meta.json");
|
|
1768
|
+
}
|
|
1769
|
+
function loadMeta(skillsDir) {
|
|
1770
|
+
const metaPath = getMetaPath(skillsDir);
|
|
1771
|
+
if (existsSync2(metaPath)) {
|
|
1772
|
+
try {
|
|
1773
|
+
return JSON.parse(readFileSync2(metaPath, "utf-8"));
|
|
1774
|
+
} catch {}
|
|
1775
|
+
}
|
|
1776
|
+
return { skills: {} };
|
|
1777
|
+
}
|
|
1778
|
+
function saveMeta(skillsDir, meta) {
|
|
1779
|
+
writeFileSync(getMetaPath(skillsDir), JSON.stringify(meta, null, 2));
|
|
1780
|
+
}
|
|
1781
|
+
function recordInstall(skillsDir, name) {
|
|
1782
|
+
const meta = loadMeta(skillsDir);
|
|
1783
|
+
const skillName = normalizeSkillName(name);
|
|
1784
|
+
let version = "unknown";
|
|
1785
|
+
try {
|
|
1786
|
+
const pkgPath = join2(skillsDir, skillName, "package.json");
|
|
1787
|
+
if (existsSync2(pkgPath)) {
|
|
1788
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
1789
|
+
version = pkg.version || "unknown";
|
|
1790
|
+
}
|
|
1791
|
+
} catch {}
|
|
1792
|
+
meta.skills[name] = { installedAt: new Date().toISOString(), version };
|
|
1793
|
+
saveMeta(skillsDir, meta);
|
|
1794
|
+
}
|
|
1795
|
+
function recordRemove(skillsDir, name) {
|
|
1796
|
+
const meta = loadMeta(skillsDir);
|
|
1797
|
+
delete meta.skills[name];
|
|
1798
|
+
saveMeta(skillsDir, meta);
|
|
1799
|
+
}
|
|
1800
|
+
function getInstallMeta(targetDir = process.cwd()) {
|
|
1801
|
+
return loadMeta(join2(targetDir, ".skills"));
|
|
1802
|
+
}
|
|
1803
|
+
function disableSkill(name, targetDir = process.cwd()) {
|
|
1804
|
+
const skillsDir = join2(targetDir, ".skills");
|
|
1805
|
+
const skillName = normalizeSkillName(name);
|
|
1806
|
+
if (!existsSync2(join2(skillsDir, skillName)))
|
|
1807
|
+
return false;
|
|
1808
|
+
const meta = loadMeta(skillsDir);
|
|
1809
|
+
const disabled = new Set(meta.disabled || []);
|
|
1810
|
+
if (disabled.has(name))
|
|
1811
|
+
return false;
|
|
1812
|
+
disabled.add(name);
|
|
1813
|
+
meta.disabled = [...disabled];
|
|
1814
|
+
saveMeta(skillsDir, meta);
|
|
1815
|
+
updateSkillsIndex(skillsDir);
|
|
1816
|
+
return true;
|
|
1817
|
+
}
|
|
1818
|
+
function enableSkill(name, targetDir = process.cwd()) {
|
|
1819
|
+
const skillsDir = join2(targetDir, ".skills");
|
|
1820
|
+
const meta = loadMeta(skillsDir);
|
|
1821
|
+
const disabled = new Set(meta.disabled || []);
|
|
1822
|
+
if (!disabled.has(name))
|
|
1823
|
+
return false;
|
|
1824
|
+
disabled.delete(name);
|
|
1825
|
+
meta.disabled = [...disabled];
|
|
1826
|
+
saveMeta(skillsDir, meta);
|
|
1827
|
+
updateSkillsIndex(skillsDir);
|
|
1828
|
+
return true;
|
|
1829
|
+
}
|
|
1830
|
+
function getDisabledSkills(targetDir = process.cwd()) {
|
|
1831
|
+
const meta = loadMeta(join2(targetDir, ".skills"));
|
|
1832
|
+
return meta.disabled || [];
|
|
1833
|
+
}
|
|
1648
1834
|
function getInstalledSkills(targetDir = process.cwd()) {
|
|
1649
|
-
const skillsDir =
|
|
1650
|
-
if (!
|
|
1835
|
+
const skillsDir = join2(targetDir, ".skills");
|
|
1836
|
+
if (!existsSync2(skillsDir)) {
|
|
1651
1837
|
return [];
|
|
1652
1838
|
}
|
|
1653
|
-
return
|
|
1654
|
-
const fullPath =
|
|
1839
|
+
return readdirSync2(skillsDir).filter((f) => {
|
|
1840
|
+
const fullPath = join2(skillsDir, f);
|
|
1655
1841
|
return f.startsWith("skill-") && statSync(fullPath).isDirectory();
|
|
1656
1842
|
}).map((f) => f.replace("skill-", ""));
|
|
1657
1843
|
}
|
|
1658
1844
|
function removeSkill(name, targetDir = process.cwd()) {
|
|
1659
1845
|
const skillName = normalizeSkillName(name);
|
|
1660
|
-
const skillsDir =
|
|
1661
|
-
const skillPath =
|
|
1662
|
-
if (!
|
|
1846
|
+
const skillsDir = join2(targetDir, ".skills");
|
|
1847
|
+
const skillPath = join2(skillsDir, skillName);
|
|
1848
|
+
if (!existsSync2(skillPath)) {
|
|
1663
1849
|
return false;
|
|
1664
1850
|
}
|
|
1665
1851
|
rmSync(skillPath, { recursive: true, force: true });
|
|
1666
1852
|
updateSkillsIndex(skillsDir);
|
|
1853
|
+
recordRemove(skillsDir, name);
|
|
1667
1854
|
return true;
|
|
1668
1855
|
}
|
|
1669
|
-
var AGENT_TARGETS = ["claude", "codex", "gemini"];
|
|
1856
|
+
var AGENT_TARGETS = ["claude", "codex", "gemini", "pi", "opencode"];
|
|
1857
|
+
var AGENT_LABELS = {
|
|
1858
|
+
claude: "Claude Code",
|
|
1859
|
+
codex: "Codex CLI",
|
|
1860
|
+
gemini: "Gemini CLI",
|
|
1861
|
+
pi: "pi.dev",
|
|
1862
|
+
opencode: "OpenCode"
|
|
1863
|
+
};
|
|
1670
1864
|
function getAgentSkillsDir(agent, scope = "global", projectDir) {
|
|
1671
|
-
const
|
|
1672
|
-
|
|
1673
|
-
|
|
1865
|
+
const base = projectDir || process.cwd();
|
|
1866
|
+
switch (agent) {
|
|
1867
|
+
case "pi":
|
|
1868
|
+
return scope === "project" ? join2(base, ".pi", "skills") : join2(homedir2(), ".pi", "agent", "skills");
|
|
1869
|
+
case "opencode":
|
|
1870
|
+
return scope === "project" ? join2(base, ".opencode", "skills") : join2(homedir2(), ".opencode", "skills");
|
|
1871
|
+
default:
|
|
1872
|
+
return scope === "project" ? join2(base, `.${agent}`, "skills") : join2(homedir2(), `.${agent}`, "skills");
|
|
1674
1873
|
}
|
|
1675
|
-
return join(homedir(), agentDir, "skills");
|
|
1676
1874
|
}
|
|
1677
1875
|
function getAgentSkillPath(name, agent, scope = "global", projectDir) {
|
|
1678
1876
|
const skillName = normalizeSkillName(name);
|
|
1679
|
-
return
|
|
1877
|
+
return join2(getAgentSkillsDir(agent, scope, projectDir), skillName);
|
|
1680
1878
|
}
|
|
1681
1879
|
function installSkillForAgent(name, options, generateSkillMd) {
|
|
1682
1880
|
const { agent, scope = "global", projectDir } = options;
|
|
1683
1881
|
const skillName = normalizeSkillName(name);
|
|
1684
1882
|
const sourcePath = getSkillPath(name);
|
|
1685
|
-
if (!
|
|
1883
|
+
if (!existsSync2(sourcePath)) {
|
|
1686
1884
|
return { skill: name, success: false, error: `Skill '${name}' not found` };
|
|
1687
1885
|
}
|
|
1688
1886
|
let skillMdContent = null;
|
|
1689
|
-
const skillMdPath =
|
|
1690
|
-
if (
|
|
1691
|
-
skillMdContent =
|
|
1887
|
+
const skillMdPath = join2(sourcePath, "SKILL.md");
|
|
1888
|
+
if (existsSync2(skillMdPath)) {
|
|
1889
|
+
skillMdContent = readFileSync2(skillMdPath, "utf-8");
|
|
1692
1890
|
} else if (generateSkillMd) {
|
|
1693
1891
|
skillMdContent = generateSkillMd(name);
|
|
1694
1892
|
}
|
|
@@ -1696,9 +1894,28 @@ function installSkillForAgent(name, options, generateSkillMd) {
|
|
|
1696
1894
|
return { skill: name, success: false, error: `No SKILL.md found and could not generate one for '${name}'` };
|
|
1697
1895
|
}
|
|
1698
1896
|
const destDir = getAgentSkillPath(name, agent, scope, projectDir);
|
|
1897
|
+
if (scope === "global") {
|
|
1898
|
+
const agentBaseDir = agent === "pi" ? join2(homedir2(), ".pi", "agent") : join2(homedir2(), `.${agent}`);
|
|
1899
|
+
if (!existsSync2(agentBaseDir)) {
|
|
1900
|
+
return {
|
|
1901
|
+
skill: name,
|
|
1902
|
+
success: false,
|
|
1903
|
+
error: `Agent directory ${agentBaseDir} does not exist. Is ${AGENT_LABELS[agent]} installed?`
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
try {
|
|
1907
|
+
accessSync(agentBaseDir, constants.W_OK);
|
|
1908
|
+
} catch {
|
|
1909
|
+
return {
|
|
1910
|
+
skill: name,
|
|
1911
|
+
success: false,
|
|
1912
|
+
error: `Agent directory ${agentBaseDir} is not writable. Check permissions.`
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1699
1916
|
try {
|
|
1700
1917
|
mkdirSync(destDir, { recursive: true });
|
|
1701
|
-
writeFileSync(
|
|
1918
|
+
writeFileSync(join2(destDir, "SKILL.md"), skillMdContent);
|
|
1702
1919
|
return { skill: name, success: true, path: destDir };
|
|
1703
1920
|
} catch (error) {
|
|
1704
1921
|
return {
|
|
@@ -1711,23 +1928,23 @@ function installSkillForAgent(name, options, generateSkillMd) {
|
|
|
1711
1928
|
function removeSkillForAgent(name, options) {
|
|
1712
1929
|
const { agent, scope = "global", projectDir } = options;
|
|
1713
1930
|
const destDir = getAgentSkillPath(name, agent, scope, projectDir);
|
|
1714
|
-
if (!
|
|
1931
|
+
if (!existsSync2(destDir)) {
|
|
1715
1932
|
return false;
|
|
1716
1933
|
}
|
|
1717
1934
|
rmSync(destDir, { recursive: true, force: true });
|
|
1718
1935
|
return true;
|
|
1719
1936
|
}
|
|
1720
1937
|
// src/lib/skillinfo.ts
|
|
1721
|
-
import { existsSync as
|
|
1722
|
-
import { join as
|
|
1938
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync3 } from "fs";
|
|
1939
|
+
import { join as join3 } from "path";
|
|
1723
1940
|
function getSkillDocs(name) {
|
|
1724
1941
|
const skillPath = getSkillPath(name);
|
|
1725
|
-
if (!
|
|
1942
|
+
if (!existsSync3(skillPath))
|
|
1726
1943
|
return null;
|
|
1727
1944
|
return {
|
|
1728
|
-
skillMd: readIfExists(
|
|
1729
|
-
readme: readIfExists(
|
|
1730
|
-
claudeMd: readIfExists(
|
|
1945
|
+
skillMd: readIfExists(join3(skillPath, "SKILL.md")),
|
|
1946
|
+
readme: readIfExists(join3(skillPath, "README.md")),
|
|
1947
|
+
claudeMd: readIfExists(join3(skillPath, "CLAUDE.md"))
|
|
1731
1948
|
};
|
|
1732
1949
|
}
|
|
1733
1950
|
function getSkillBestDoc(name) {
|
|
@@ -1738,11 +1955,11 @@ function getSkillBestDoc(name) {
|
|
|
1738
1955
|
}
|
|
1739
1956
|
function getSkillRequirements(name) {
|
|
1740
1957
|
const skillPath = getSkillPath(name);
|
|
1741
|
-
if (!
|
|
1958
|
+
if (!existsSync3(skillPath))
|
|
1742
1959
|
return null;
|
|
1743
1960
|
const texts = [];
|
|
1744
1961
|
for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
|
|
1745
|
-
const content = readIfExists(
|
|
1962
|
+
const content = readIfExists(join3(skillPath, file));
|
|
1746
1963
|
if (content)
|
|
1747
1964
|
texts.push(content);
|
|
1748
1965
|
}
|
|
@@ -1769,10 +1986,10 @@ function getSkillRequirements(name) {
|
|
|
1769
1986
|
}
|
|
1770
1987
|
let cliCommand = null;
|
|
1771
1988
|
let dependencies = {};
|
|
1772
|
-
const pkgPath =
|
|
1773
|
-
if (
|
|
1989
|
+
const pkgPath = join3(skillPath, "package.json");
|
|
1990
|
+
if (existsSync3(pkgPath)) {
|
|
1774
1991
|
try {
|
|
1775
|
-
const pkg = JSON.parse(
|
|
1992
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
1776
1993
|
if (pkg.bin) {
|
|
1777
1994
|
const binKeys = Object.keys(pkg.bin);
|
|
1778
1995
|
if (binKeys.length > 0)
|
|
@@ -1792,25 +2009,25 @@ async function runSkill(name, args, options = {}) {
|
|
|
1792
2009
|
const skillName = normalizeSkillName(name);
|
|
1793
2010
|
let skillPath;
|
|
1794
2011
|
if (options.installed) {
|
|
1795
|
-
skillPath =
|
|
2012
|
+
skillPath = join3(process.cwd(), ".skills", skillName);
|
|
1796
2013
|
} else {
|
|
1797
|
-
const installedPath =
|
|
1798
|
-
if (
|
|
2014
|
+
const installedPath = join3(process.cwd(), ".skills", skillName);
|
|
2015
|
+
if (existsSync3(installedPath)) {
|
|
1799
2016
|
skillPath = installedPath;
|
|
1800
2017
|
} else {
|
|
1801
2018
|
skillPath = getSkillPath(name);
|
|
1802
2019
|
}
|
|
1803
2020
|
}
|
|
1804
|
-
if (!
|
|
2021
|
+
if (!existsSync3(skillPath)) {
|
|
1805
2022
|
return { exitCode: 1, error: `Skill '${name}' not found` };
|
|
1806
2023
|
}
|
|
1807
|
-
const pkgPath =
|
|
1808
|
-
if (!
|
|
2024
|
+
const pkgPath = join3(skillPath, "package.json");
|
|
2025
|
+
if (!existsSync3(pkgPath)) {
|
|
1809
2026
|
return { exitCode: 1, error: `No package.json in skill '${name}'` };
|
|
1810
2027
|
}
|
|
1811
2028
|
let entryPoint;
|
|
1812
2029
|
try {
|
|
1813
|
-
const pkg = JSON.parse(
|
|
2030
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
1814
2031
|
if (pkg.bin) {
|
|
1815
2032
|
const binValues = Object.values(pkg.bin);
|
|
1816
2033
|
entryPoint = binValues[0];
|
|
@@ -1824,12 +2041,12 @@ async function runSkill(name, args, options = {}) {
|
|
|
1824
2041
|
} catch {
|
|
1825
2042
|
return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
|
|
1826
2043
|
}
|
|
1827
|
-
const entryPath =
|
|
1828
|
-
if (!
|
|
2044
|
+
const entryPath = join3(skillPath, entryPoint);
|
|
2045
|
+
if (!existsSync3(entryPath)) {
|
|
1829
2046
|
return { exitCode: 1, error: `Entry point '${entryPoint}' not found in skill '${name}'` };
|
|
1830
2047
|
}
|
|
1831
|
-
const nodeModules =
|
|
1832
|
-
if (!
|
|
2048
|
+
const nodeModules = join3(skillPath, "node_modules");
|
|
2049
|
+
if (!existsSync3(nodeModules)) {
|
|
1833
2050
|
const install = Bun.spawn(["bun", "install", "--no-save"], {
|
|
1834
2051
|
cwd: skillPath,
|
|
1835
2052
|
stdout: "pipe",
|
|
@@ -1847,17 +2064,17 @@ async function runSkill(name, args, options = {}) {
|
|
|
1847
2064
|
return { exitCode };
|
|
1848
2065
|
}
|
|
1849
2066
|
function generateEnvExample(targetDir = process.cwd()) {
|
|
1850
|
-
const skillsDir =
|
|
1851
|
-
if (!
|
|
2067
|
+
const skillsDir = join3(targetDir, ".skills");
|
|
2068
|
+
if (!existsSync3(skillsDir))
|
|
1852
2069
|
return "";
|
|
1853
|
-
const dirs =
|
|
2070
|
+
const dirs = readdirSync3(skillsDir).filter((f) => f.startsWith("skill-") && existsSync3(join3(skillsDir, f, "package.json")));
|
|
1854
2071
|
const envMap = new Map;
|
|
1855
2072
|
for (const dir of dirs) {
|
|
1856
2073
|
const skillName = dir.replace("skill-", "");
|
|
1857
|
-
const skillPath =
|
|
2074
|
+
const skillPath = join3(skillsDir, dir);
|
|
1858
2075
|
const texts = [];
|
|
1859
2076
|
for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example"]) {
|
|
1860
|
-
const content = readIfExists(
|
|
2077
|
+
const content = readIfExists(join3(skillPath, file));
|
|
1861
2078
|
if (content)
|
|
1862
2079
|
texts.push(content);
|
|
1863
2080
|
}
|
|
@@ -1902,7 +2119,7 @@ function generateSkillMd(name) {
|
|
|
1902
2119
|
if (!meta)
|
|
1903
2120
|
return null;
|
|
1904
2121
|
const skillPath = getSkillPath(name);
|
|
1905
|
-
if (!
|
|
2122
|
+
if (!existsSync3(skillPath))
|
|
1906
2123
|
return null;
|
|
1907
2124
|
const frontmatter = [
|
|
1908
2125
|
"---",
|
|
@@ -1911,13 +2128,13 @@ function generateSkillMd(name) {
|
|
|
1911
2128
|
"---"
|
|
1912
2129
|
].join(`
|
|
1913
2130
|
`);
|
|
1914
|
-
const readme = readIfExists(
|
|
1915
|
-
const claudeMd = readIfExists(
|
|
2131
|
+
const readme = readIfExists(join3(skillPath, "README.md"));
|
|
2132
|
+
const claudeMd = readIfExists(join3(skillPath, "CLAUDE.md"));
|
|
1916
2133
|
let cliCommand = null;
|
|
1917
|
-
const pkgPath =
|
|
1918
|
-
if (
|
|
2134
|
+
const pkgPath = join3(skillPath, "package.json");
|
|
2135
|
+
if (existsSync3(pkgPath)) {
|
|
1919
2136
|
try {
|
|
1920
|
-
const pkg = JSON.parse(
|
|
2137
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
1921
2138
|
if (pkg.bin) {
|
|
1922
2139
|
const binKeys = Object.keys(pkg.bin);
|
|
1923
2140
|
if (binKeys.length > 0)
|
|
@@ -1992,18 +2209,249 @@ function extractEnvVars(text) {
|
|
|
1992
2209
|
}
|
|
1993
2210
|
function readIfExists(path) {
|
|
1994
2211
|
try {
|
|
1995
|
-
if (
|
|
1996
|
-
return
|
|
2212
|
+
if (existsSync3(path)) {
|
|
2213
|
+
return readFileSync3(path, "utf-8");
|
|
1997
2214
|
}
|
|
1998
2215
|
} catch {}
|
|
1999
2216
|
return null;
|
|
2000
2217
|
}
|
|
2218
|
+
// src/lib/config.ts
|
|
2219
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
2220
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
2221
|
+
import { homedir as homedir3 } from "os";
|
|
2222
|
+
var VALID_KEYS = {
|
|
2223
|
+
defaultAgent: ["claude", "codex", "gemini", "all"],
|
|
2224
|
+
defaultScope: ["global", "project"],
|
|
2225
|
+
format: ["compact", "json", "csv"]
|
|
2226
|
+
};
|
|
2227
|
+
function getConfigPath(scope) {
|
|
2228
|
+
if (scope === "global") {
|
|
2229
|
+
return join4(homedir3(), ".skillsrc");
|
|
2230
|
+
}
|
|
2231
|
+
return join4(process.cwd(), "skills.config.json");
|
|
2232
|
+
}
|
|
2233
|
+
function readConfigFile(path) {
|
|
2234
|
+
if (!existsSync4(path))
|
|
2235
|
+
return {};
|
|
2236
|
+
try {
|
|
2237
|
+
const raw = readFileSync4(path, "utf-8");
|
|
2238
|
+
const parsed = JSON.parse(raw);
|
|
2239
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
2240
|
+
return {};
|
|
2241
|
+
const config = {};
|
|
2242
|
+
for (const [key, allowed] of Object.entries(VALID_KEYS)) {
|
|
2243
|
+
const val = parsed[key];
|
|
2244
|
+
if (typeof val === "string" && allowed.includes(val)) {
|
|
2245
|
+
config[key] = val;
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
return config;
|
|
2249
|
+
} catch {
|
|
2250
|
+
return {};
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
function loadConfig() {
|
|
2254
|
+
const globalConfig = readConfigFile(getConfigPath("global"));
|
|
2255
|
+
const projectConfig = readConfigFile(getConfigPath("project"));
|
|
2256
|
+
return { ...globalConfig, ...projectConfig };
|
|
2257
|
+
}
|
|
2258
|
+
function saveConfig(key, value, scope = "project") {
|
|
2259
|
+
if (!(key in VALID_KEYS)) {
|
|
2260
|
+
throw new Error(`Unknown config key: ${key}. Valid keys: ${Object.keys(VALID_KEYS).join(", ")}`);
|
|
2261
|
+
}
|
|
2262
|
+
const allowed = VALID_KEYS[key];
|
|
2263
|
+
if (!allowed.includes(value)) {
|
|
2264
|
+
throw new Error(`Invalid value '${value}' for ${key}. Allowed: ${allowed.join(", ")}`);
|
|
2265
|
+
}
|
|
2266
|
+
const filePath = getConfigPath(scope);
|
|
2267
|
+
let existing = {};
|
|
2268
|
+
if (existsSync4(filePath)) {
|
|
2269
|
+
try {
|
|
2270
|
+
existing = JSON.parse(readFileSync4(filePath, "utf-8"));
|
|
2271
|
+
if (typeof existing !== "object" || existing === null || Array.isArray(existing)) {
|
|
2272
|
+
existing = {};
|
|
2273
|
+
}
|
|
2274
|
+
} catch {
|
|
2275
|
+
existing = {};
|
|
2276
|
+
}
|
|
2277
|
+
} else {
|
|
2278
|
+
const dir = dirname2(filePath);
|
|
2279
|
+
if (!existsSync4(dir)) {
|
|
2280
|
+
mkdirSync2(dir, { recursive: true });
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
existing[key] = value;
|
|
2284
|
+
writeFileSync2(filePath, JSON.stringify(existing, null, 2) + `
|
|
2285
|
+
`);
|
|
2286
|
+
}
|
|
2287
|
+
// src/lib/scheduler.ts
|
|
2288
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
2289
|
+
import { join as join5 } from "path";
|
|
2290
|
+
function getSchedulesPath(targetDir = process.cwd()) {
|
|
2291
|
+
return join5(targetDir, ".skills", "schedules.json");
|
|
2292
|
+
}
|
|
2293
|
+
function loadSchedules(targetDir = process.cwd()) {
|
|
2294
|
+
const path = getSchedulesPath(targetDir);
|
|
2295
|
+
if (existsSync5(path)) {
|
|
2296
|
+
try {
|
|
2297
|
+
return JSON.parse(readFileSync5(path, "utf-8"));
|
|
2298
|
+
} catch {}
|
|
2299
|
+
}
|
|
2300
|
+
return { version: 1, schedules: [] };
|
|
2301
|
+
}
|
|
2302
|
+
function saveSchedules(data, targetDir = process.cwd()) {
|
|
2303
|
+
const path = getSchedulesPath(targetDir);
|
|
2304
|
+
const dir = join5(targetDir, ".skills");
|
|
2305
|
+
if (!existsSync5(dir))
|
|
2306
|
+
mkdirSync3(dir, { recursive: true });
|
|
2307
|
+
writeFileSync3(path, JSON.stringify(data, null, 2));
|
|
2308
|
+
}
|
|
2309
|
+
function validateCron(expr) {
|
|
2310
|
+
const fields = expr.trim().split(/\s+/);
|
|
2311
|
+
if (fields.length !== 5) {
|
|
2312
|
+
return { valid: false, error: `Expected 5 fields, got ${fields.length}. Format: "minute hour day-of-month month day-of-week"` };
|
|
2313
|
+
}
|
|
2314
|
+
return { valid: true };
|
|
2315
|
+
}
|
|
2316
|
+
function getNextRun(cron, from = new Date) {
|
|
2317
|
+
const { valid } = validateCron(cron);
|
|
2318
|
+
if (!valid)
|
|
2319
|
+
return null;
|
|
2320
|
+
const [minuteF, hourF, domF, monthF, dowF] = cron.trim().split(/\s+/);
|
|
2321
|
+
function parseField(f, min, max) {
|
|
2322
|
+
if (f === "*")
|
|
2323
|
+
return Array.from({ length: max - min + 1 }, (_, i) => i + min);
|
|
2324
|
+
if (f.startsWith("*/")) {
|
|
2325
|
+
const step = parseInt(f.slice(2));
|
|
2326
|
+
if (isNaN(step))
|
|
2327
|
+
return [];
|
|
2328
|
+
const vals = [];
|
|
2329
|
+
for (let i = min;i <= max; i += step)
|
|
2330
|
+
vals.push(i);
|
|
2331
|
+
return vals;
|
|
2332
|
+
}
|
|
2333
|
+
return f.split(",").flatMap((part) => {
|
|
2334
|
+
if (part.includes("-")) {
|
|
2335
|
+
const [lo, hi] = part.split("-").map(Number);
|
|
2336
|
+
return Array.from({ length: hi - lo + 1 }, (_, i) => i + lo);
|
|
2337
|
+
}
|
|
2338
|
+
const n = parseInt(part);
|
|
2339
|
+
return isNaN(n) ? [] : [n];
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
const minutes = parseField(minuteF, 0, 59);
|
|
2343
|
+
const hours = parseField(hourF, 0, 23);
|
|
2344
|
+
const doms = parseField(domF, 1, 31);
|
|
2345
|
+
const months = parseField(monthF, 1, 12);
|
|
2346
|
+
const dows = parseField(dowF, 0, 6);
|
|
2347
|
+
const candidate = new Date(from);
|
|
2348
|
+
candidate.setSeconds(0, 0);
|
|
2349
|
+
candidate.setMinutes(candidate.getMinutes() + 1);
|
|
2350
|
+
const limit = new Date(from);
|
|
2351
|
+
limit.setFullYear(limit.getFullYear() + 1);
|
|
2352
|
+
while (candidate < limit) {
|
|
2353
|
+
const month = candidate.getMonth() + 1;
|
|
2354
|
+
const dom = candidate.getDate();
|
|
2355
|
+
const dow = candidate.getDay();
|
|
2356
|
+
const hour = candidate.getHours();
|
|
2357
|
+
const minute = candidate.getMinutes();
|
|
2358
|
+
if (!months.includes(month)) {
|
|
2359
|
+
candidate.setMonth(candidate.getMonth() + 1, 1);
|
|
2360
|
+
candidate.setHours(0, 0, 0, 0);
|
|
2361
|
+
continue;
|
|
2362
|
+
}
|
|
2363
|
+
if (!doms.includes(dom) || !dows.includes(dow)) {
|
|
2364
|
+
candidate.setDate(candidate.getDate() + 1);
|
|
2365
|
+
candidate.setHours(0, 0, 0, 0);
|
|
2366
|
+
continue;
|
|
2367
|
+
}
|
|
2368
|
+
if (!hours.includes(hour)) {
|
|
2369
|
+
candidate.setHours(candidate.getHours() + 1, 0, 0, 0);
|
|
2370
|
+
continue;
|
|
2371
|
+
}
|
|
2372
|
+
if (!minutes.includes(minute)) {
|
|
2373
|
+
candidate.setMinutes(candidate.getMinutes() + 1, 0, 0);
|
|
2374
|
+
continue;
|
|
2375
|
+
}
|
|
2376
|
+
return new Date(candidate);
|
|
2377
|
+
}
|
|
2378
|
+
return null;
|
|
2379
|
+
}
|
|
2380
|
+
function addSchedule(skill, cron, options = {}) {
|
|
2381
|
+
const { valid, error } = validateCron(cron);
|
|
2382
|
+
if (!valid)
|
|
2383
|
+
return { schedule: null, error };
|
|
2384
|
+
const data = loadSchedules(options.targetDir);
|
|
2385
|
+
const id = `${skill}-${Date.now()}`;
|
|
2386
|
+
const now = new Date;
|
|
2387
|
+
const nextRun = getNextRun(cron, now);
|
|
2388
|
+
const schedule = {
|
|
2389
|
+
id,
|
|
2390
|
+
name: options.name || `${skill} (${cron})`,
|
|
2391
|
+
skill,
|
|
2392
|
+
cron,
|
|
2393
|
+
args: options.args,
|
|
2394
|
+
enabled: true,
|
|
2395
|
+
createdAt: now.toISOString(),
|
|
2396
|
+
nextRun: nextRun?.toISOString()
|
|
2397
|
+
};
|
|
2398
|
+
data.schedules.push(schedule);
|
|
2399
|
+
saveSchedules(data, options.targetDir);
|
|
2400
|
+
return { schedule };
|
|
2401
|
+
}
|
|
2402
|
+
function listSchedules(targetDir) {
|
|
2403
|
+
return loadSchedules(targetDir).schedules;
|
|
2404
|
+
}
|
|
2405
|
+
function removeSchedule(idOrName, targetDir) {
|
|
2406
|
+
const data = loadSchedules(targetDir);
|
|
2407
|
+
const before = data.schedules.length;
|
|
2408
|
+
data.schedules = data.schedules.filter((s) => s.id !== idOrName && s.name !== idOrName);
|
|
2409
|
+
if (data.schedules.length === before)
|
|
2410
|
+
return false;
|
|
2411
|
+
saveSchedules(data, targetDir);
|
|
2412
|
+
return true;
|
|
2413
|
+
}
|
|
2414
|
+
function setScheduleEnabled(idOrName, enabled, targetDir) {
|
|
2415
|
+
const data = loadSchedules(targetDir);
|
|
2416
|
+
const schedule = data.schedules.find((s) => s.id === idOrName || s.name === idOrName);
|
|
2417
|
+
if (!schedule)
|
|
2418
|
+
return false;
|
|
2419
|
+
schedule.enabled = enabled;
|
|
2420
|
+
if (enabled) {
|
|
2421
|
+
schedule.nextRun = getNextRun(schedule.cron)?.toISOString();
|
|
2422
|
+
}
|
|
2423
|
+
saveSchedules(data, targetDir);
|
|
2424
|
+
return true;
|
|
2425
|
+
}
|
|
2426
|
+
function getDueSchedules(targetDir) {
|
|
2427
|
+
const now = new Date;
|
|
2428
|
+
return listSchedules(targetDir).filter((s) => s.enabled && s.nextRun && new Date(s.nextRun) <= now);
|
|
2429
|
+
}
|
|
2430
|
+
function recordScheduleRun(id, status, targetDir) {
|
|
2431
|
+
const data = loadSchedules(targetDir);
|
|
2432
|
+
const schedule = data.schedules.find((s) => s.id === id);
|
|
2433
|
+
if (!schedule)
|
|
2434
|
+
return;
|
|
2435
|
+
const now = new Date;
|
|
2436
|
+
schedule.lastRun = now.toISOString();
|
|
2437
|
+
schedule.lastRunStatus = status;
|
|
2438
|
+
schedule.nextRun = getNextRun(schedule.cron, now)?.toISOString();
|
|
2439
|
+
saveSchedules(data, targetDir);
|
|
2440
|
+
}
|
|
2001
2441
|
export {
|
|
2442
|
+
validateCron,
|
|
2002
2443
|
skillExists,
|
|
2444
|
+
setScheduleEnabled,
|
|
2003
2445
|
searchSkills,
|
|
2446
|
+
saveConfig,
|
|
2004
2447
|
runSkill,
|
|
2005
2448
|
removeSkillForAgent,
|
|
2006
2449
|
removeSkill,
|
|
2450
|
+
removeSchedule,
|
|
2451
|
+
recordScheduleRun,
|
|
2452
|
+
loadRegistry,
|
|
2453
|
+
loadConfig,
|
|
2454
|
+
listSchedules,
|
|
2007
2455
|
installSkills,
|
|
2008
2456
|
installSkillForAgent,
|
|
2009
2457
|
installSkill,
|
|
@@ -2014,12 +2462,22 @@ export {
|
|
|
2014
2462
|
getSkillDocs,
|
|
2015
2463
|
getSkillBestDoc,
|
|
2016
2464
|
getSkill,
|
|
2465
|
+
getNextRun,
|
|
2017
2466
|
getInstalledSkills,
|
|
2467
|
+
getInstallMeta,
|
|
2468
|
+
getDueSchedules,
|
|
2469
|
+
getDisabledSkills,
|
|
2470
|
+
getConfigPath,
|
|
2018
2471
|
getAllTags,
|
|
2019
2472
|
getAgentSkillsDir,
|
|
2020
2473
|
getAgentSkillPath,
|
|
2021
2474
|
generateSkillMd,
|
|
2022
2475
|
generateEnvExample,
|
|
2476
|
+
findSimilarSkills,
|
|
2477
|
+
enableSkill,
|
|
2478
|
+
disableSkill,
|
|
2479
|
+
clearRegistryCache,
|
|
2480
|
+
addSchedule,
|
|
2023
2481
|
SKILLS,
|
|
2024
2482
|
CATEGORIES,
|
|
2025
2483
|
AGENT_TARGETS
|