@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.
Files changed (207) hide show
  1. package/bin/index.js +1743 -420
  2. package/bin/mcp.js +791 -259
  3. package/dist/index.d.ts +5 -2
  4. package/dist/index.js +533 -75
  5. package/dist/lib/config.d.ts +27 -0
  6. package/dist/lib/config.test.d.ts +1 -0
  7. package/dist/lib/installer.d.ts +36 -2
  8. package/dist/lib/registry.d.ts +16 -0
  9. package/dist/lib/scheduler.d.ts +47 -0
  10. package/dist/types/api.d.ts +74 -0
  11. package/package.json +5 -2
  12. package/skills/_common/index.ts +4 -0
  13. package/skills/_common/vision.ts +374 -0
  14. package/skills/skill-academic-journal-matcher/bin/cli.ts +34 -0
  15. package/skills/skill-action-item-router/bin/cli.ts +34 -0
  16. package/skills/skill-ad-creative-generator/bin/cli.ts +34 -0
  17. package/skills/skill-advanced-math/bin/cli.ts +34 -0
  18. package/skills/skill-analyze-data/bin/cli.ts +19 -0
  19. package/skills/skill-anomaly-investigator/bin/cli.ts +34 -0
  20. package/skills/skill-api-test-suite/bin/cli.ts +34 -0
  21. package/skills/skill-apidocs/bin/cli.ts +87 -0
  22. package/skills/skill-audio-cleanup-lab/bin/cli.ts +6 -0
  23. package/skills/skill-audiobook-chapter-proofer/bin/cli.ts +34 -0
  24. package/skills/skill-banner-ad-suite/bin/cli.ts +34 -0
  25. package/skills/skill-benchmark-finder/bin/cli.ts +34 -0
  26. package/skills/skill-bio-sequence-tool/bin/cli.ts +34 -0
  27. package/skills/skill-blog-topic-cluster/bin/cli.ts +34 -0
  28. package/skills/skill-brand-style-guide/bin/cli.ts +19 -0
  29. package/skills/skill-brand-voice-audit/bin/cli.ts +34 -0
  30. package/skills/skill-budget-variance-analyzer/bin/cli.ts +6 -0
  31. package/skills/skill-businessactivity/bin/cli.ts +28 -0
  32. package/skills/skill-calendar-events/bin/cli.ts +34 -0
  33. package/skills/skill-campaign-metric-brief/bin/cli.ts +34 -0
  34. package/skills/skill-campaign-moodboard/bin/cli.ts +34 -0
  35. package/skills/skill-caption-style-stylist/bin/cli.ts +34 -0
  36. package/skills/skill-chemistry-calculator/bin/cli.ts +34 -0
  37. package/skills/skill-churn-risk-notifier/bin/cli.ts +34 -0
  38. package/skills/skill-citation-formatter/bin/cli.ts +34 -0
  39. package/skills/skill-classroom-newsletter-kit/bin/cli.ts +34 -0
  40. package/skills/skill-color-palette-harmonizer/bin/cli.ts +34 -0
  41. package/skills/skill-colorextract/SKILL.md +35 -0
  42. package/skills/skill-colorextract/bun.lock +102 -0
  43. package/skills/skill-colorextract/package.json +13 -0
  44. package/skills/skill-colorextract/src/index.ts +405 -0
  45. package/skills/skill-competitor-ad-analyzer/bin/cli.ts +34 -0
  46. package/skills/skill-compliance-copy-check/bin/cli.ts +34 -0
  47. package/skills/skill-compliance-report-pack/bin/cli.ts +34 -0
  48. package/skills/skill-compress-video/bin/cli.ts +19 -0
  49. package/skills/skill-consolelog/bin/cli.ts +884 -0
  50. package/skills/skill-contract-plainlanguage/bin/cli.ts +34 -0
  51. package/skills/skill-copytone-translator/bin/cli.ts +34 -0
  52. package/skills/skill-create-blog-article/bin/cli.ts +34 -0
  53. package/skills/skill-create-ebook/bin/cli.ts +34 -0
  54. package/skills/skill-crm-note-enhancer/bin/cli.ts +34 -0
  55. package/skills/skill-customer-journey-mapper/bin/cli.ts +34 -0
  56. package/skills/skill-dashboard-builder/bin/cli.ts +34 -0
  57. package/skills/skill-dashboard-narrator/bin/cli.ts +34 -0
  58. package/skills/skill-data-anonymizer/bin/cli.ts +34 -0
  59. package/skills/skill-database-explorer/bin/cli.ts +34 -0
  60. package/skills/skill-dataset-health-check/bin/cli.ts +34 -0
  61. package/skills/skill-decision-journal/bin/cli.ts +34 -0
  62. package/skills/skill-delegation-brief-writer/bin/cli.ts +34 -0
  63. package/skills/skill-destination-briefing/bin/cli.ts +34 -0
  64. package/skills/skill-diff-viewer/bin/cli.ts +34 -0
  65. package/skills/skill-domainpurchase/SKILL.md +46 -0
  66. package/skills/skill-domainpurchase/bin/cli.ts +683 -0
  67. package/skills/skill-domainsearch/SKILL.md +41 -0
  68. package/skills/skill-domainsearch/bin/cli.ts +410 -0
  69. package/skills/skill-educational-resource-finder/bin/cli.ts +34 -0
  70. package/skills/skill-email-campaign/bin/cli.ts +34 -0
  71. package/skills/skill-exam-readiness-check/bin/cli.ts +34 -0
  72. package/skills/skill-experiment-power-calculator/bin/cli.ts +34 -0
  73. package/skills/skill-extract-audio/bin/cli.ts +19 -0
  74. package/skills/skill-extract-frames/bin/cli.ts +34 -0
  75. package/skills/skill-extract-invoice/bin/cli.ts +34 -0
  76. package/skills/skill-family-activity-curator/bin/cli.ts +34 -0
  77. package/skills/skill-faq-packager/bin/cli.ts +34 -0
  78. package/skills/skill-feedback-survey-designer/bin/cli.ts +34 -0
  79. package/skills/skill-field-trip-planner/bin/cli.ts +34 -0
  80. package/skills/skill-file-organizer/bin/cli.ts +34 -0
  81. package/skills/skill-folder-tree/bin/cli.ts +34 -0
  82. package/skills/skill-forecast-scenario-lab/bin/cli.ts +34 -0
  83. package/skills/skill-form-filler/bin/cli.ts +34 -0
  84. package/skills/skill-generate-api-client/bin/cli.ts +34 -0
  85. package/skills/skill-generate-book-cover/bin/cli.ts +34 -0
  86. package/skills/skill-generate-chart/bin/cli.ts +34 -0
  87. package/skills/skill-generate-diagram/bin/cli.ts +34 -0
  88. package/skills/skill-generate-dockerfile/bin/cli.ts +34 -0
  89. package/skills/skill-generate-documentation/bin/cli.ts +34 -0
  90. package/skills/skill-generate-docx/bin/cli.ts +6 -0
  91. package/skills/skill-generate-env/bin/cli.ts +34 -0
  92. package/skills/skill-generate-excel/bin/cli.ts +34 -0
  93. package/skills/skill-generate-favicon/bin/cli.ts +34 -0
  94. package/skills/skill-generate-mock-data/bin/cli.ts +34 -0
  95. package/skills/skill-generate-pdf/bin/cli.ts +6 -0
  96. package/skills/skill-generate-pr-description/bin/cli.ts +34 -0
  97. package/skills/skill-generate-presentation/bin/cli.ts +34 -0
  98. package/skills/skill-generate-qrcode/bin/cli.ts +34 -0
  99. package/skills/skill-generate-regex/bin/cli.ts +34 -0
  100. package/skills/skill-generate-resume/bin/cli.ts +34 -0
  101. package/skills/skill-generate-sitemap/bin/cli.ts +34 -0
  102. package/skills/skill-generate-social-posts/bin/cli.ts +34 -0
  103. package/skills/skill-generate-sql/bin/cli.ts +34 -0
  104. package/skills/skill-gif-maker/bin/cli.ts +34 -0
  105. package/skills/skill-github-manager/bin/cli.ts +34 -0
  106. package/skills/skill-gmail/bin/cli.ts +34 -0
  107. package/skills/skill-goal-quarterly-roadmap/bin/cli.ts +34 -0
  108. package/skills/skill-grant-application-drafter/bin/cli.ts +34 -0
  109. package/skills/skill-grocery-basket-optimizer/bin/cli.ts +34 -0
  110. package/skills/skill-guest-communication-suite/bin/cli.ts +34 -0
  111. package/skills/skill-habit-reflection-digest/bin/cli.ts +34 -0
  112. package/skills/skill-highlight-reel-generator/bin/cli.ts +34 -0
  113. package/skills/skill-homework-feedback-coach/bin/cli.ts +34 -0
  114. package/skills/skill-hook/bunfig.toml +5 -0
  115. package/skills/skill-household-maintenance-mgr/bin/cli.ts +34 -0
  116. package/skills/skill-http-server/bin/cli.ts +34 -0
  117. package/skills/skill-implementation/bunfig.toml +5 -0
  118. package/skills/skill-implementation-agent/bin/cli.ts +34 -0
  119. package/skills/skill-implementation-plan/bin/cli.ts +34 -0
  120. package/skills/skill-implementation-todo/bin/cli.ts +34 -0
  121. package/skills/skill-inbox-priority-planner/bin/cli.ts +34 -0
  122. package/skills/skill-invoice/bin/cli.ts +20 -0
  123. package/skills/skill-invoice-dispute-helper/bin/cli.ts +34 -0
  124. package/skills/skill-itinerary-architect/bin/cli.ts +34 -0
  125. package/skills/skill-jingle-composer/bin/cli.ts +34 -0
  126. package/skills/skill-kpi-digest-generator/bin/cli.ts +34 -0
  127. package/skills/skill-lab-notebook-formatter/bin/cli.ts +34 -0
  128. package/skills/skill-landing-page-copy/bin/cli.ts +34 -0
  129. package/skills/skill-latex-table-generator/bin/cli.ts +34 -0
  130. package/skills/skill-learning-style-profiler/bin/cli.ts +34 -0
  131. package/skills/skill-lesson-plan-customizer/bin/cli.ts +34 -0
  132. package/skills/skill-livestream-runofshow/bin/cli.ts +34 -0
  133. package/skills/skill-longform-structurer/bin/cli.ts +34 -0
  134. package/skills/skill-lorem-generator/bin/cli.ts +34 -0
  135. package/skills/skill-managehook/bin/cli.ts +241 -0
  136. package/skills/skill-managemcp/bin/cli.ts +241 -0
  137. package/skills/skill-manageskill/bin/cli.ts +241 -0
  138. package/skills/skill-markdown-validator/bin/cli.ts +34 -0
  139. package/skills/skill-mcp-builder/bin/cli.ts +34 -0
  140. package/skills/skill-meal-plan-designer/bin/cli.ts +34 -0
  141. package/skills/skill-meeting-insight-summarizer/bin/cli.ts +34 -0
  142. package/skills/skill-merge-pdfs/bin/cli.ts +34 -0
  143. package/skills/skill-microcopy-generator/bin/cli.ts +34 -0
  144. package/skills/skill-mindfulness-prompt-cache/bin/cli.ts +34 -0
  145. package/skills/skill-notion-manager/bin/cli.ts +34 -0
  146. package/skills/skill-onboarding-sequence-builder/bin/cli.ts +34 -0
  147. package/skills/skill-onsite-ops-checklist/bin/cli.ts +34 -0
  148. package/skills/skill-outreach-cadence-designer/bin/cli.ts +34 -0
  149. package/skills/skill-packaging-concept-studio/bin/cli.ts +34 -0
  150. package/skills/skill-packing-plan-pro/bin/cli.ts +34 -0
  151. package/skills/skill-parent-teacher-brief/bin/cli.ts +34 -0
  152. package/skills/skill-partner-kit-assembler/bin/cli.ts +34 -0
  153. package/skills/skill-payroll-change-prepper/bin/cli.ts +34 -0
  154. package/skills/skill-persona-based-adwriter/bin/cli.ts +34 -0
  155. package/skills/skill-persona-generator/bin/cli.ts +34 -0
  156. package/skills/skill-personal-daily-ops/bin/cli.ts +34 -0
  157. package/skills/skill-pet-care-scheduler/bin/cli.ts +34 -0
  158. package/skills/skill-podcast-show-notes/bin/cli.ts +34 -0
  159. package/skills/skill-presentation-theme-maker/bin/cli.ts +34 -0
  160. package/skills/skill-press-release-drafter/bin/cli.ts +34 -0
  161. package/skills/skill-print-collateral-designer/bin/cli.ts +34 -0
  162. package/skills/skill-procurement-scorecard/bin/cli.ts +34 -0
  163. package/skills/skill-product-demo-script/bin/cli.ts +34 -0
  164. package/skills/skill-product-mockup/bin/cli.ts +34 -0
  165. package/skills/skill-project-retro-companion/bin/cli.ts +34 -0
  166. package/skills/skill-proposal-redline-advisor/bin/cli.ts +34 -0
  167. package/skills/skill-regex-tester/bin/cli.ts +34 -0
  168. package/skills/skill-remove-background/bin/cli.ts +34 -0
  169. package/skills/skill-risk-disclosure-kit/bin/cli.ts +34 -0
  170. package/skills/skill-roi-comparison-tool/bin/cli.ts +34 -0
  171. package/skills/skill-sales-call-recapper/bin/cli.ts +34 -0
  172. package/skills/skill-salescopy/bin/cli.ts +20 -0
  173. package/skills/skill-scaffold-project/bin/cli.ts +34 -0
  174. package/skills/skill-scholarship-tracker/bin/cli.ts +34 -0
  175. package/skills/skill-scientific-figure-check/bin/cli.ts +34 -0
  176. package/skills/skill-seating-chart-maker/bin/cli.ts +34 -0
  177. package/skills/skill-security-audit/bin/cli.ts +34 -0
  178. package/skills/skill-seo-brief-builder/bin/cli.ts +34 -0
  179. package/skills/skill-siteanalyze/SKILL.md +25 -0
  180. package/skills/skill-siteanalyze/package.json +13 -0
  181. package/skills/skill-siteanalyze/src/index.ts +592 -0
  182. package/skills/skill-slack-assistant/bin/cli.ts +34 -0
  183. package/skills/skill-sleep-routine-analyzer/bin/cli.ts +34 -0
  184. package/skills/skill-social-media-kit/bin/cli.ts +34 -0
  185. package/skills/skill-split-pdf/bin/cli.ts +34 -0
  186. package/skills/skill-sponsorship-proposal-lab/bin/cli.ts +34 -0
  187. package/skills/skill-spreadsheet-cleanroom/bin/cli.ts +34 -0
  188. package/skills/skill-statistical-test-selector/bin/cli.ts +34 -0
  189. package/skills/skill-stress-relief-playbook/bin/cli.ts +34 -0
  190. package/skills/skill-study-guide-builder/bin/cli.ts +34 -0
  191. package/skills/skill-subscription-spend-watcher/bin/cli.ts +34 -0
  192. package/skills/skill-subtitle/bin/cli.ts +20 -0
  193. package/skills/skill-survey-insight-extractor/bin/cli.ts +34 -0
  194. package/skills/skill-terraform-generator/bin/cli.ts +34 -0
  195. package/skills/skill-testimonial-graphics/bin/cli.ts +34 -0
  196. package/skills/skill-timesheet/bin/cli.ts +47 -0
  197. package/skills/skill-travel-budget-balancer/bin/cli.ts +34 -0
  198. package/skills/skill-validate-config/bin/cli.ts +34 -0
  199. package/skills/skill-video-cut-suggester/bin/cli.ts +34 -0
  200. package/skills/skill-video-downloader/bin/cli.ts +34 -0
  201. package/skills/skill-video-thumbnail/bin/cli.ts +34 -0
  202. package/skills/skill-voiceover-casting-assistant/bin/cli.ts +34 -0
  203. package/skills/skill-watermark/bin/cli.ts +34 -0
  204. package/skills/skill-webcrawling/bin/cli.ts +21 -0
  205. package/skills/skill-webinar-script-coach/bin/cli.ts +34 -0
  206. package/skills/skill-wellness-progress-reporter/bin/cli.ts +34 -0
  207. 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 SKILLS.filter((s) => s.category === category);
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 SKILLS) {
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 SKILLS.find((s) => s.name === name);
1622
+ return loadRegistry().find((s) => s.name === name);
1523
1623
  }
1524
1624
  function getSkillsByTag(tag) {
1525
1625
  const needle = tag.toLowerCase();
1526
- return SKILLS.filter((s) => s.tags.some((t) => t.toLowerCase().includes(needle)));
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 SKILLS) {
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 = join(dir, "skills");
1554
- if (existsSync(candidate)) {
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 join(__dirname2, "..", "skills");
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 join(SKILLS_DIR, skillName);
1679
+ return join2(SKILLS_DIR, skillName);
1565
1680
  }
1566
1681
  function skillExists(name) {
1567
- return existsSync(getSkillPath(name));
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 = join(targetDir, ".skills");
1574
- const destPath = join(destDir, skillName);
1575
- if (!existsSync(sourcePath)) {
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 (existsSync(destPath) && !overwrite) {
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 (!existsSync(destDir)) {
1706
+ if (!existsSync2(destDir)) {
1592
1707
  mkdirSync(destDir, { recursive: true });
1593
1708
  }
1594
- if (existsSync(destPath) && overwrite) {
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 = join(skillsDir, "index.ts");
1633
- const skills = readdirSync(skillsDir).filter((f) => f.startsWith("skill-") && !f.includes("."));
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 = join(targetDir, ".skills");
1650
- if (!existsSync(skillsDir)) {
1835
+ const skillsDir = join2(targetDir, ".skills");
1836
+ if (!existsSync2(skillsDir)) {
1651
1837
  return [];
1652
1838
  }
1653
- return readdirSync(skillsDir).filter((f) => {
1654
- const fullPath = join(skillsDir, f);
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 = join(targetDir, ".skills");
1661
- const skillPath = join(skillsDir, skillName);
1662
- if (!existsSync(skillPath)) {
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 agentDir = `.${agent}`;
1672
- if (scope === "project") {
1673
- return join(projectDir || process.cwd(), agentDir, "skills");
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 join(getAgentSkillsDir(agent, scope, projectDir), skillName);
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 (!existsSync(sourcePath)) {
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 = join(sourcePath, "SKILL.md");
1690
- if (existsSync(skillMdPath)) {
1691
- skillMdContent = readFileSync(skillMdPath, "utf-8");
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(join(destDir, "SKILL.md"), skillMdContent);
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 (!existsSync(destDir)) {
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 existsSync2, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
1722
- import { join as join2 } from "path";
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 (!existsSync2(skillPath))
1942
+ if (!existsSync3(skillPath))
1726
1943
  return null;
1727
1944
  return {
1728
- skillMd: readIfExists(join2(skillPath, "SKILL.md")),
1729
- readme: readIfExists(join2(skillPath, "README.md")),
1730
- claudeMd: readIfExists(join2(skillPath, "CLAUDE.md"))
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 (!existsSync2(skillPath))
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(join2(skillPath, file));
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 = join2(skillPath, "package.json");
1773
- if (existsSync2(pkgPath)) {
1989
+ const pkgPath = join3(skillPath, "package.json");
1990
+ if (existsSync3(pkgPath)) {
1774
1991
  try {
1775
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
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 = join2(process.cwd(), ".skills", skillName);
2012
+ skillPath = join3(process.cwd(), ".skills", skillName);
1796
2013
  } else {
1797
- const installedPath = join2(process.cwd(), ".skills", skillName);
1798
- if (existsSync2(installedPath)) {
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 (!existsSync2(skillPath)) {
2021
+ if (!existsSync3(skillPath)) {
1805
2022
  return { exitCode: 1, error: `Skill '${name}' not found` };
1806
2023
  }
1807
- const pkgPath = join2(skillPath, "package.json");
1808
- if (!existsSync2(pkgPath)) {
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(readFileSync2(pkgPath, "utf-8"));
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 = join2(skillPath, entryPoint);
1828
- if (!existsSync2(entryPath)) {
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 = join2(skillPath, "node_modules");
1832
- if (!existsSync2(nodeModules)) {
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 = join2(targetDir, ".skills");
1851
- if (!existsSync2(skillsDir))
2067
+ const skillsDir = join3(targetDir, ".skills");
2068
+ if (!existsSync3(skillsDir))
1852
2069
  return "";
1853
- const dirs = readdirSync2(skillsDir).filter((f) => f.startsWith("skill-") && existsSync2(join2(skillsDir, f, "package.json")));
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 = join2(skillsDir, dir);
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(join2(skillPath, file));
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 (!existsSync2(skillPath))
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(join2(skillPath, "README.md"));
1915
- const claudeMd = readIfExists(join2(skillPath, "CLAUDE.md"));
2131
+ const readme = readIfExists(join3(skillPath, "README.md"));
2132
+ const claudeMd = readIfExists(join3(skillPath, "CLAUDE.md"));
1916
2133
  let cliCommand = null;
1917
- const pkgPath = join2(skillPath, "package.json");
1918
- if (existsSync2(pkgPath)) {
2134
+ const pkgPath = join3(skillPath, "package.json");
2135
+ if (existsSync3(pkgPath)) {
1919
2136
  try {
1920
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
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 (existsSync2(path)) {
1996
- return readFileSync2(path, "utf-8");
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