@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
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Config file support for Open Skills
3
+ *
4
+ * Loads configuration from:
5
+ * 1. Project-local: ./skills.config.json (highest priority)
6
+ * 2. Global: ~/.skillsrc (JSON format, lowest priority)
7
+ *
8
+ * Values from the project config override global config.
9
+ */
10
+ export interface SkillsConfig {
11
+ defaultAgent?: "claude" | "codex" | "gemini" | "all";
12
+ defaultScope?: "global" | "project";
13
+ format?: "compact" | "json" | "csv";
14
+ }
15
+ export type ConfigScope = "global" | "project";
16
+ /**
17
+ * Get the config file path for a given scope
18
+ */
19
+ export declare function getConfigPath(scope: ConfigScope): string;
20
+ /**
21
+ * Load merged config: project-local overrides global
22
+ */
23
+ export declare function loadConfig(): SkillsConfig;
24
+ /**
25
+ * Save a single config key-value pair to the specified scope
26
+ */
27
+ export declare function saveConfig(key: string, value: string, scope?: ConfigScope): void;
@@ -0,0 +1 @@
1
+ export {};
@@ -27,6 +27,30 @@ export declare function installSkill(name: string, options?: InstallOptions): In
27
27
  * Install multiple skills
28
28
  */
29
29
  export declare function installSkills(names: string[], options?: InstallOptions): InstallResult[];
30
+ interface SkillMeta {
31
+ installedAt: string;
32
+ version: string;
33
+ }
34
+ interface MetaFile {
35
+ skills: Record<string, SkillMeta>;
36
+ disabled?: string[];
37
+ }
38
+ /**
39
+ * Get installation metadata for installed skills
40
+ */
41
+ export declare function getInstallMeta(targetDir?: string): MetaFile;
42
+ /**
43
+ * Disable a skill (exclude from .skills/index.ts without removing files)
44
+ */
45
+ export declare function disableSkill(name: string, targetDir?: string): boolean;
46
+ /**
47
+ * Enable a previously disabled skill (re-add to .skills/index.ts)
48
+ */
49
+ export declare function enableSkill(name: string, targetDir?: string): boolean;
50
+ /**
51
+ * Get list of disabled skills
52
+ */
53
+ export declare function getDisabledSkills(targetDir?: string): string[];
30
54
  /**
31
55
  * Get list of installed skills in a directory
32
56
  */
@@ -35,9 +59,11 @@ export declare function getInstalledSkills(targetDir?: string): string[];
35
59
  * Remove an installed skill
36
60
  */
37
61
  export declare function removeSkill(name: string, targetDir?: string): boolean;
38
- export type AgentTarget = "claude" | "codex" | "gemini";
62
+ export type AgentTarget = "claude" | "codex" | "gemini" | "pi" | "opencode";
39
63
  export type AgentScope = "global" | "project";
40
64
  export declare const AGENT_TARGETS: AgentTarget[];
65
+ /** Human-readable labels for each agent */
66
+ export declare const AGENT_LABELS: Record<AgentTarget, string>;
41
67
  /**
42
68
  * Resolve an agent argument ("all" or a specific agent name) to a list of AgentTarget values.
43
69
  * Throws if the agent name is not recognized.
@@ -49,7 +75,14 @@ export interface AgentInstallOptions {
49
75
  projectDir?: string;
50
76
  }
51
77
  /**
52
- * Get the skills directory for a given agent and scope
78
+ * Get the skills directory for a given agent and scope.
79
+ *
80
+ * Agent config dir conventions:
81
+ * claude — ~/.claude/skills/ | .claude/skills/
82
+ * codex — ~/.codex/skills/ | .codex/skills/
83
+ * gemini — ~/.gemini/skills/ | .gemini/skills/
84
+ * pi — ~/.pi/agent/skills/ | .pi/skills/
85
+ * opencode — ~/.opencode/skills/ | .opencode/skills/
53
86
  */
54
87
  export declare function getAgentSkillsDir(agent: AgentTarget, scope?: AgentScope, projectDir?: string): string;
55
88
  /**
@@ -65,3 +98,4 @@ export declare function installSkillForAgent(name: string, options: AgentInstall
65
98
  * Remove a skill from an agent's skill directory
66
99
  */
67
100
  export declare function removeSkillForAgent(name: string, options: AgentInstallOptions): boolean;
101
+ export {};
@@ -8,10 +8,22 @@ export interface SkillMeta {
8
8
  category: string;
9
9
  tags: string[];
10
10
  dependencies?: string[];
11
+ source?: "official" | "custom";
11
12
  }
12
13
  export declare const CATEGORIES: readonly ["Development Tools", "Business & Marketing", "Productivity & Organization", "Project Management", "Content Generation", "Finance & Compliance", "Data & Analysis", "Media Processing", "Design & Branding", "Web & Browser", "Research & Writing", "Science & Academic", "Education & Learning", "Communication", "Health & Wellness", "Travel & Lifestyle", "Event Management"];
13
14
  export type Category = (typeof CATEGORIES)[number];
14
15
  export declare const SKILLS: SkillMeta[];
16
+ /**
17
+ * Load the full registry: official skills merged with custom skills from:
18
+ * - ~/.skills/ (global custom)
19
+ * - ./.custom-skills/ (project-level custom, relative to cwd)
20
+ *
21
+ * Custom skills with the same name as official skills take precedence.
22
+ * Results are cached for 5 seconds.
23
+ */
24
+ export declare function loadRegistry(cwd?: string): SkillMeta[];
25
+ /** Invalidate the registry cache (e.g. after installing a custom skill). */
26
+ export declare function clearRegistryCache(): void;
15
27
  export declare function getSkillsByCategory(category: Category): SkillMeta[];
16
28
  export declare function searchSkills(query: string): SkillMeta[];
17
29
  export declare function getSkill(name: string): SkillMeta | undefined;
@@ -23,3 +35,7 @@ export declare function getSkillsByTag(tag: string): SkillMeta[];
23
35
  * Return all unique tags across every skill, sorted alphabetically.
24
36
  */
25
37
  export declare function getAllTags(): string[];
38
+ /**
39
+ * Find skills with names similar to the given query (for "did you mean?" suggestions)
40
+ */
41
+ export declare function findSimilarSkills(query: string, maxResults?: number): string[];
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Skill scheduler — cron-based scheduling for skills
3
+ *
4
+ * Schedules are stored in .skills/schedules.json in the project directory.
5
+ * Each schedule entry defines a skill to run on a cron expression.
6
+ *
7
+ * Cron format: standard 5-field (minute hour dom month dow)
8
+ * e.g. "0 9 * * *" = every day at 9am
9
+ */
10
+ export interface SkillSchedule {
11
+ id: string;
12
+ name: string;
13
+ skill: string;
14
+ cron: string;
15
+ args?: string[];
16
+ enabled: boolean;
17
+ createdAt: string;
18
+ lastRun?: string;
19
+ lastRunStatus?: "success" | "error";
20
+ nextRun?: string;
21
+ }
22
+ /** Validate a 5-field cron expression (basic syntax check). */
23
+ export declare function validateCron(expr: string): {
24
+ valid: boolean;
25
+ error?: string;
26
+ };
27
+ /** Compute the next run time for a cron expression relative to a given date. */
28
+ export declare function getNextRun(cron: string, from?: Date): Date | null;
29
+ /** Add a new schedule. Returns the created schedule. */
30
+ export declare function addSchedule(skill: string, cron: string, options?: {
31
+ name?: string;
32
+ args?: string[];
33
+ targetDir?: string;
34
+ }): {
35
+ schedule: SkillSchedule | null;
36
+ error?: string;
37
+ };
38
+ /** List all schedules. */
39
+ export declare function listSchedules(targetDir?: string): SkillSchedule[];
40
+ /** Remove a schedule by id or name. Returns true if removed. */
41
+ export declare function removeSchedule(idOrName: string, targetDir?: string): boolean;
42
+ /** Enable or disable a schedule by id or name. */
43
+ export declare function setScheduleEnabled(idOrName: string, enabled: boolean, targetDir?: string): boolean;
44
+ /** Get all schedules that are due now (nextRun <= now and enabled). */
45
+ export declare function getDueSchedules(targetDir?: string): SkillSchedule[];
46
+ /** Mark a schedule as having just run. Updates lastRun and nextRun. */
47
+ export declare function recordScheduleRun(id: string, status: "success" | "error", targetDir?: string): void;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Shared API response types used by both the HTTP server and the dashboard.
3
+ * Import from "@hasna/skills" to get type-safe API responses.
4
+ */
5
+ export interface SkillResponse {
6
+ name: string;
7
+ displayName: string;
8
+ description: string;
9
+ category: string;
10
+ tags: string[];
11
+ installed: boolean;
12
+ envVars: string[];
13
+ envVarsSet: string[];
14
+ systemDeps: string[];
15
+ cliCommand: string | null;
16
+ }
17
+ export interface SkillDetailResponse extends SkillResponse {
18
+ docs: string | null;
19
+ }
20
+ export interface CategoryResponse {
21
+ name: string;
22
+ count: number;
23
+ }
24
+ export interface TagResponse {
25
+ name: string;
26
+ count: number;
27
+ }
28
+ export interface InstallResponse {
29
+ skill: string;
30
+ success: boolean;
31
+ error?: string;
32
+ results?: Array<{
33
+ skill: string;
34
+ success: boolean;
35
+ error?: string;
36
+ }>;
37
+ }
38
+ export interface RemoveResponse {
39
+ skill: string;
40
+ success: boolean;
41
+ }
42
+ export interface VersionResponse {
43
+ version: string;
44
+ name: string;
45
+ }
46
+ export interface ExportResponse {
47
+ version: number;
48
+ skills: string[];
49
+ timestamp: string;
50
+ }
51
+ export interface ImportResponse {
52
+ imported: number;
53
+ total: number;
54
+ results: Array<{
55
+ skill: string;
56
+ success: boolean;
57
+ error?: string;
58
+ }>;
59
+ }
60
+ export interface SearchResponse extends SkillResponse {
61
+ }
62
+ export interface CategoryInstallResponse {
63
+ category: string;
64
+ count: number;
65
+ success: boolean;
66
+ results: Array<{
67
+ skill: string;
68
+ success: boolean;
69
+ error?: string;
70
+ }>;
71
+ }
72
+ export interface ErrorResponse {
73
+ error: string;
74
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,9 +23,12 @@
23
23
  "main": "./dist/index.js",
24
24
  "types": "./dist/index.d.ts",
25
25
  "scripts": {
26
- "build": "bun build ./src/cli/index.tsx --outdir ./bin --target bun --external ink --external react --external chalk && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/index.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
26
+ "clean": "rm -rf bin/ dist/",
27
+ "build": "bun run clean && bun build ./src/cli/index.tsx --outdir ./bin --target bun --external ink --external react --external chalk && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/index.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
27
28
  "test": "bun test",
28
29
  "dev": "bun run ./src/cli/index.tsx",
30
+ "dev:watch": "bun --watch run ./src/cli/index.tsx",
31
+ "dev:mcp": "bun --watch run ./src/mcp/index.ts",
29
32
  "typecheck": "tsc --noEmit",
30
33
  "prepublishOnly": "bun run build",
31
34
  "dashboard:dev": "cd dashboard && bun run dev",
@@ -21,3 +21,7 @@ export {
21
21
  type AssistantType,
22
22
  } from './installer';
23
23
  export { handleInstallCommand } from './skill-install';
24
+
25
+ // Vision client — multi-provider (anthropic/openai/xai/gemini)
26
+ // import { analyzeImage, detectProvider } from './vision.js'
27
+ export * from './vision.js';
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Unified vision client for AI providers.
3
+ * Follows @hasna/connectors pattern — auto-detects from env vars.
4
+ * Providers: anthropic, openai, xai, gemini
5
+ *
6
+ * Uses built-in fetch only — no external SDK dependencies.
7
+ */
8
+
9
+ export type VisionProvider = "anthropic" | "openai" | "xai" | "gemini";
10
+
11
+ export interface VisionOptions {
12
+ provider?: VisionProvider;
13
+ model?: string;
14
+ maxTokens?: number;
15
+ systemPrompt?: string;
16
+ jsonMode?: boolean; // wrap prompt to request JSON response
17
+ }
18
+
19
+ export interface VisionResult {
20
+ text: string;
21
+ provider: VisionProvider;
22
+ model: string;
23
+ inputTokens?: number;
24
+ outputTokens?: number;
25
+ }
26
+
27
+ // Default models per provider (vision-capable)
28
+ export const DEFAULT_MODELS: Record<VisionProvider, string> = {
29
+ anthropic: "claude-sonnet-4-6",
30
+ openai: "gpt-4o",
31
+ xai: "grok-2-vision-1212",
32
+ gemini: "gemini-2.0-flash",
33
+ };
34
+
35
+ // API key env vars per provider
36
+ export const API_KEY_VARS: Record<VisionProvider, string> = {
37
+ anthropic: "ANTHROPIC_API_KEY",
38
+ openai: "OPENAI_API_KEY",
39
+ xai: "XAI_API_KEY",
40
+ gemini: "GEMINI_API_KEY",
41
+ };
42
+
43
+ // Base URLs
44
+ const BASE_URLS: Record<VisionProvider, string> = {
45
+ anthropic: "https://api.anthropic.com/v1/messages",
46
+ openai: "https://api.openai.com/v1/chat/completions",
47
+ xai: "https://api.x.ai/v1/chat/completions",
48
+ gemini: "https://generativelanguage.googleapis.com/v1beta/models",
49
+ };
50
+
51
+ /**
52
+ * Auto-detect provider from env vars.
53
+ * Priority: ANTHROPIC_API_KEY → OPENAI_API_KEY → XAI_API_KEY → GEMINI_API_KEY
54
+ */
55
+ export function detectProvider(): VisionProvider | null {
56
+ const order: VisionProvider[] = ["anthropic", "openai", "xai", "gemini"];
57
+ for (const provider of order) {
58
+ if (process.env[API_KEY_VARS[provider]]) {
59
+ return provider;
60
+ }
61
+ }
62
+ return null;
63
+ }
64
+
65
+ /**
66
+ * Get API key for a provider. Throws if not set.
67
+ */
68
+ export function getApiKey(provider: VisionProvider): string {
69
+ const key = process.env[API_KEY_VARS[provider]];
70
+ if (!key) {
71
+ throw new Error(
72
+ `${API_KEY_VARS[provider]} is not set. Run: connectors setup ${provider}`
73
+ );
74
+ }
75
+ return key;
76
+ }
77
+
78
+ /**
79
+ * Strip markdown code fences and JSON.parse.
80
+ * Throws with raw text on failure.
81
+ */
82
+ export function parseJsonResponse(text: string): unknown {
83
+ let str = text.trim();
84
+ if (str.startsWith("```")) {
85
+ str = str
86
+ .replace(/^```(?:json)?\n?/, "")
87
+ .replace(/\n?```$/, "")
88
+ .trim();
89
+ }
90
+ try {
91
+ return JSON.parse(str);
92
+ } catch {
93
+ throw new Error(
94
+ `Failed to parse response as JSON.\nRaw response:\n${text}`
95
+ );
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Returns all providers that have an API key set.
101
+ */
102
+ export function listAvailableProviders(): VisionProvider[] {
103
+ return (["anthropic", "openai", "xai", "gemini"] as VisionProvider[]).filter(
104
+ (p) => !!process.env[API_KEY_VARS[p]]
105
+ );
106
+ }
107
+
108
+ /**
109
+ * Returns availability status for all providers.
110
+ */
111
+ export function getProviderInfo(): Record<
112
+ VisionProvider,
113
+ { available: boolean; model: string; keyVar: string }
114
+ > {
115
+ return {
116
+ anthropic: {
117
+ available: !!process.env[API_KEY_VARS.anthropic],
118
+ model: DEFAULT_MODELS.anthropic,
119
+ keyVar: API_KEY_VARS.anthropic,
120
+ },
121
+ openai: {
122
+ available: !!process.env[API_KEY_VARS.openai],
123
+ model: DEFAULT_MODELS.openai,
124
+ keyVar: API_KEY_VARS.openai,
125
+ },
126
+ xai: {
127
+ available: !!process.env[API_KEY_VARS.xai],
128
+ model: DEFAULT_MODELS.xai,
129
+ keyVar: API_KEY_VARS.xai,
130
+ },
131
+ gemini: {
132
+ available: !!process.env[API_KEY_VARS.gemini],
133
+ model: DEFAULT_MODELS.gemini,
134
+ keyVar: API_KEY_VARS.gemini,
135
+ },
136
+ };
137
+ }
138
+
139
+ // ============================================================================
140
+ // Provider implementations
141
+ // ============================================================================
142
+
143
+ async function analyzeWithAnthropic(
144
+ imageBase64: string,
145
+ mediaType: string,
146
+ prompt: string,
147
+ model: string,
148
+ options: VisionOptions
149
+ ): Promise<VisionResult> {
150
+ const apiKey = getApiKey("anthropic");
151
+ const body: Record<string, unknown> = {
152
+ model,
153
+ max_tokens: options.maxTokens ?? 1024,
154
+ messages: [
155
+ {
156
+ role: "user",
157
+ content: [
158
+ {
159
+ type: "image",
160
+ source: { type: "base64", media_type: mediaType, data: imageBase64 },
161
+ },
162
+ { type: "text", text: prompt },
163
+ ],
164
+ },
165
+ ],
166
+ };
167
+ if (options.systemPrompt) {
168
+ body.system = options.systemPrompt;
169
+ }
170
+
171
+ const response = await fetch(BASE_URLS.anthropic, {
172
+ method: "POST",
173
+ headers: {
174
+ "x-api-key": apiKey,
175
+ "anthropic-version": "2023-06-01",
176
+ "content-type": "application/json",
177
+ },
178
+ body: JSON.stringify(body),
179
+ });
180
+
181
+ if (!response.ok) {
182
+ const err = await response.text();
183
+ throw new Error(`Anthropic API error ${response.status}: ${err}`);
184
+ }
185
+
186
+ const data = (await response.json()) as {
187
+ content: Array<{ type: string; text: string }>;
188
+ usage: { input_tokens: number; output_tokens: number };
189
+ };
190
+
191
+ return {
192
+ text: data.content[0].text,
193
+ provider: "anthropic",
194
+ model,
195
+ inputTokens: data.usage?.input_tokens,
196
+ outputTokens: data.usage?.output_tokens,
197
+ };
198
+ }
199
+
200
+ async function analyzeWithOpenAICompat(
201
+ provider: "openai" | "xai",
202
+ imageBase64: string,
203
+ mediaType: string,
204
+ prompt: string,
205
+ model: string,
206
+ options: VisionOptions
207
+ ): Promise<VisionResult> {
208
+ const apiKey = getApiKey(provider);
209
+ const messages: unknown[] = [];
210
+
211
+ if (options.systemPrompt) {
212
+ messages.push({ role: "system", content: options.systemPrompt });
213
+ }
214
+
215
+ messages.push({
216
+ role: "user",
217
+ content: [
218
+ {
219
+ type: "image_url",
220
+ image_url: {
221
+ url: `data:${mediaType};base64,${imageBase64}`,
222
+ detail: "high",
223
+ },
224
+ },
225
+ { type: "text", text: prompt },
226
+ ],
227
+ });
228
+
229
+ const body = {
230
+ model,
231
+ max_tokens: options.maxTokens ?? 1024,
232
+ messages,
233
+ };
234
+
235
+ const response = await fetch(BASE_URLS[provider], {
236
+ method: "POST",
237
+ headers: {
238
+ Authorization: `Bearer ${apiKey}`,
239
+ "content-type": "application/json",
240
+ },
241
+ body: JSON.stringify(body),
242
+ });
243
+
244
+ if (!response.ok) {
245
+ const err = await response.text();
246
+ throw new Error(
247
+ `${provider.toUpperCase()} API error ${response.status}: ${err}`
248
+ );
249
+ }
250
+
251
+ const data = (await response.json()) as {
252
+ choices: Array<{ message: { content: string } }>;
253
+ usage: { prompt_tokens: number; completion_tokens: number };
254
+ };
255
+
256
+ return {
257
+ text: data.choices[0].message.content,
258
+ provider,
259
+ model,
260
+ inputTokens: data.usage?.prompt_tokens,
261
+ outputTokens: data.usage?.completion_tokens,
262
+ };
263
+ }
264
+
265
+ async function analyzeWithGemini(
266
+ imageBase64: string,
267
+ mediaType: string,
268
+ prompt: string,
269
+ model: string,
270
+ options: VisionOptions
271
+ ): Promise<VisionResult> {
272
+ const apiKey = getApiKey("gemini");
273
+ const url = `${BASE_URLS.gemini}/${model}:generateContent?key=${apiKey}`;
274
+
275
+ const body: Record<string, unknown> = {
276
+ contents: [
277
+ {
278
+ parts: [
279
+ { inlineData: { mimeType: mediaType, data: imageBase64 } },
280
+ { text: prompt },
281
+ ],
282
+ },
283
+ ],
284
+ generationConfig: {
285
+ maxOutputTokens: options.maxTokens ?? 1024,
286
+ ...(options.jsonMode ? { responseMimeType: "application/json" } : {}),
287
+ },
288
+ };
289
+
290
+ if (options.systemPrompt) {
291
+ body.systemInstruction = { parts: [{ text: options.systemPrompt }] };
292
+ }
293
+
294
+ const response = await fetch(url, {
295
+ method: "POST",
296
+ headers: { "content-type": "application/json" },
297
+ body: JSON.stringify(body),
298
+ });
299
+
300
+ if (!response.ok) {
301
+ const err = await response.text();
302
+ throw new Error(`Gemini API error ${response.status}: ${err}`);
303
+ }
304
+
305
+ const data = (await response.json()) as {
306
+ candidates: Array<{
307
+ content: { parts: Array<{ text: string }> };
308
+ }>;
309
+ usageMetadata?: { promptTokenCount: number; candidatesTokenCount: number };
310
+ };
311
+
312
+ return {
313
+ text: data.candidates[0].content.parts[0].text,
314
+ provider: "gemini",
315
+ model,
316
+ inputTokens: data.usageMetadata?.promptTokenCount,
317
+ outputTokens: data.usageMetadata?.candidatesTokenCount,
318
+ };
319
+ }
320
+
321
+ // ============================================================================
322
+ // Main entry point
323
+ // ============================================================================
324
+
325
+ /**
326
+ * Analyze an image with a vision-capable model.
327
+ * Auto-detects provider from env vars unless options.provider is specified.
328
+ */
329
+ export async function analyzeImage(
330
+ imageBase64: string,
331
+ mediaType: string,
332
+ prompt: string,
333
+ options: VisionOptions = {}
334
+ ): Promise<VisionResult> {
335
+ const provider = options.provider ?? detectProvider();
336
+ if (!provider) {
337
+ throw new Error(
338
+ "No AI provider API key found. Set one of: ANTHROPIC_API_KEY, OPENAI_API_KEY, XAI_API_KEY, GEMINI_API_KEY"
339
+ );
340
+ }
341
+
342
+ const model = options.model ?? DEFAULT_MODELS[provider];
343
+ const jsonPrompt =
344
+ options.jsonMode
345
+ ? `${prompt}\n\nIMPORTANT: Respond ONLY with valid JSON. No markdown, no code fences, no commentary.`
346
+ : prompt;
347
+
348
+ switch (provider) {
349
+ case "anthropic":
350
+ return analyzeWithAnthropic(
351
+ imageBase64,
352
+ mediaType,
353
+ jsonPrompt,
354
+ model,
355
+ options
356
+ );
357
+ case "openai":
358
+ case "xai":
359
+ return analyzeWithOpenAICompat(
360
+ provider,
361
+ imageBase64,
362
+ mediaType,
363
+ jsonPrompt,
364
+ model,
365
+ options
366
+ );
367
+ case "gemini":
368
+ return analyzeWithGemini(imageBase64, mediaType, jsonPrompt, model, options);
369
+ default: {
370
+ const _exhaustive: never = provider;
371
+ throw new Error(`Unknown provider: ${_exhaustive}`);
372
+ }
373
+ }
374
+ }
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from "commander";
4
+ import { homedir } from "os";
5
+ import { join } from "path";
6
+
7
+ function getDataDir(): string {
8
+ return process.env.DATA_DIR || join(homedir(), ".skill", "skill-academic-journal-matcher");
9
+ }
10
+
11
+ const program = new Command();
12
+
13
+ program
14
+ .name("skill-academic-journal-matcher")
15
+ .description("Academic Journal Matcher skill")
16
+ .version("0.1.0");
17
+
18
+ program
19
+ .command("run")
20
+ .description("Run the skill")
21
+ .option("-o, --output <path>", "Output directory", join(getDataDir(), "output"))
22
+ .allowUnknownOption(true)
23
+ .action(async (options, command) => {
24
+ try {
25
+ const args = command.args;
26
+ process.env.DATA_DIR = process.env.DATA_DIR || getDataDir();
27
+ await import("../src/index.js");
28
+ } catch (err) {
29
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
30
+ process.exit(1);
31
+ }
32
+ });
33
+
34
+ program.parse();