@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,592 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * skill-siteanalyze — Analyze any website's design system via Playwright + AI Vision
4
+ * Supports multiple providers: anthropic, openai, xai, gemini
5
+ */
6
+ import { writeFileSync, existsSync, mkdirSync } from "fs";
7
+ import { tmpdir } from "os";
8
+ import { join } from "path";
9
+ import {
10
+ analyzeImage,
11
+ detectProvider,
12
+ listAvailableProviders,
13
+ parseJsonResponse,
14
+ type VisionProvider,
15
+ } from "../../_common/vision.js";
16
+
17
+ // ============================================================================
18
+ // Types
19
+ // ============================================================================
20
+
21
+ export interface DesignColors {
22
+ primary?: string;
23
+ secondary?: string;
24
+ accent?: string;
25
+ background?: string;
26
+ text?: string;
27
+ border?: string;
28
+ muted?: string;
29
+ [key: string]: string | undefined;
30
+ }
31
+
32
+ export interface TypographyScale {
33
+ fontFamilies: string[];
34
+ sizes: string[];
35
+ weights: string[];
36
+ lineHeights?: string[];
37
+ }
38
+
39
+ export interface ComponentPattern {
40
+ name: string;
41
+ description: string;
42
+ }
43
+
44
+ export interface SiteAnalyzeResult {
45
+ url: string;
46
+ framework: string | null;
47
+ hasShadcn: boolean;
48
+ hasTailwind: boolean;
49
+ colors: DesignColors;
50
+ typography: TypographyScale;
51
+ components: ComponentPattern[];
52
+ provider: VisionProvider | null;
53
+ model: string | null;
54
+ openStylesProfile: {
55
+ name: string;
56
+ displayName: string;
57
+ category: string;
58
+ description: string;
59
+ colors: Record<string, string>;
60
+ typography: TypographyScale;
61
+ framework: string | null;
62
+ tags: string[];
63
+ };
64
+ screenshotPath?: string;
65
+ rawAnalysis?: string;
66
+ }
67
+
68
+ // ============================================================================
69
+ // HTML/CSS analysis (quick mode — no browser needed)
70
+ // ============================================================================
71
+
72
+ async function fetchPageSource(url: string): Promise<string> {
73
+ const response = await fetch(url, {
74
+ headers: {
75
+ "User-Agent":
76
+ "Mozilla/5.0 (compatible; skill-siteanalyze/1.0; +https://github.com/hasna/skills)",
77
+ },
78
+ });
79
+ if (!response.ok) {
80
+ throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
81
+ }
82
+ return response.text();
83
+ }
84
+
85
+ function detectFrameworkFromHtml(html: string): {
86
+ hasShadcn: boolean;
87
+ hasTailwind: boolean;
88
+ framework: string | null;
89
+ } {
90
+ const lower = html.toLowerCase();
91
+ const hasShadcn =
92
+ lower.includes("shadcn") ||
93
+ lower.includes("@radix-ui") ||
94
+ lower.includes("radix-ui") ||
95
+ lower.includes("cmdk") ||
96
+ lower.includes("data-radix");
97
+ const hasTailwind =
98
+ lower.includes("tailwind") ||
99
+ lower.includes("tw-") ||
100
+ /class="[^"]*\b(flex|grid|p-\d|m-\d|text-\w+|bg-\w+|border-\w+)\b/.test(html);
101
+
102
+ let framework: string | null = null;
103
+ if (hasShadcn) {
104
+ framework = "shadcn/ui";
105
+ } else if (lower.includes("mui") || lower.includes("material-ui")) {
106
+ framework = "Material UI";
107
+ } else if (lower.includes("chakra")) {
108
+ framework = "Chakra UI";
109
+ } else if (lower.includes("mantine")) {
110
+ framework = "Mantine";
111
+ } else if (lower.includes("antd") || lower.includes("ant-design")) {
112
+ framework = "Ant Design";
113
+ } else if (lower.includes("bootstrap")) {
114
+ framework = "Bootstrap";
115
+ } else if (hasTailwind) {
116
+ framework = "Tailwind CSS";
117
+ }
118
+
119
+ return { hasShadcn, hasTailwind, framework };
120
+ }
121
+
122
+ function extractColorsFromCss(html: string): DesignColors {
123
+ const colors: DesignColors = {};
124
+ const hexPattern = /#([0-9A-Fa-f]{3,8})\b/g;
125
+ const cssVarPattern = /--[\w-]+:\s*(#[0-9A-Fa-f]{3,8}|rgba?\([^)]+\)|hsla?\([^)]+\))/g;
126
+
127
+ // Try to extract CSS custom properties (most reliable)
128
+ const varMatches = [...html.matchAll(cssVarPattern)].slice(0, 20);
129
+ if (varMatches.length > 0) {
130
+ for (const match of varMatches) {
131
+ const varName = match[0].split(":")[0].trim();
132
+ const value = match[1].trim();
133
+ if (varName.includes("primary")) colors.primary = value;
134
+ else if (varName.includes("secondary")) colors.secondary = value;
135
+ else if (varName.includes("accent")) colors.accent = value;
136
+ else if (varName.includes("background") || varName.includes("bg")) colors.background = value;
137
+ else if (varName.includes("foreground") || varName.includes("text")) colors.text = value;
138
+ else if (varName.includes("border")) colors.border = value;
139
+ else if (varName.includes("muted")) colors.muted = value;
140
+ }
141
+ }
142
+
143
+ // Fall back to raw hex extraction from inline styles
144
+ if (Object.keys(colors).length === 0) {
145
+ const hexMatches = [...html.matchAll(hexPattern)].map((m) => m[0]);
146
+ const unique = [...new Set(hexMatches)].slice(0, 5);
147
+ if (unique[0]) colors.primary = unique[0];
148
+ if (unique[1]) colors.secondary = unique[1];
149
+ if (unique[2]) colors.accent = unique[2];
150
+ }
151
+
152
+ return colors;
153
+ }
154
+
155
+ // ============================================================================
156
+ // Vision-based analysis
157
+ // ============================================================================
158
+
159
+ async function captureScreenshot(url: string): Promise<string | null> {
160
+ // Try to use playwright if available
161
+ try {
162
+ // Dynamic import so the skill works without playwright installed
163
+ const { chromium } = await import("playwright" as never) as {
164
+ chromium: {
165
+ launch: (opts?: Record<string, unknown>) => Promise<{
166
+ newPage: () => Promise<{
167
+ goto: (url: string, opts?: Record<string, unknown>) => Promise<unknown>;
168
+ screenshot: (opts?: Record<string, unknown>) => Promise<Buffer>;
169
+ close: () => Promise<void>;
170
+ }>;
171
+ close: () => Promise<void>;
172
+ }>;
173
+ };
174
+ };
175
+
176
+ const tmpDir = join(tmpdir(), "skill-siteanalyze");
177
+ if (!existsSync(tmpDir)) {
178
+ mkdirSync(tmpDir, { recursive: true });
179
+ }
180
+ const screenshotPath = join(tmpDir, `screenshot-${Date.now()}.png`);
181
+
182
+ const browser = await chromium.launch({ headless: true });
183
+ const page = await browser.newPage();
184
+ await page.goto(url, { waitUntil: "networkidle", timeout: 30000 });
185
+ const buffer = await page.screenshot({ path: screenshotPath, fullPage: false });
186
+ await page.close();
187
+ await browser.close();
188
+
189
+ return screenshotPath;
190
+ } catch {
191
+ return null;
192
+ }
193
+ }
194
+
195
+ async function analyzeScreenshot(
196
+ screenshotPath: string,
197
+ url: string,
198
+ options: { provider?: VisionProvider; model?: string }
199
+ ): Promise<{
200
+ colors: DesignColors;
201
+ typography: TypographyScale;
202
+ components: ComponentPattern[];
203
+ styleCategory: string;
204
+ description: string;
205
+ rawAnalysis: string;
206
+ provider: VisionProvider;
207
+ model: string;
208
+ }> {
209
+ const { readFileSync } = await import("fs");
210
+ const imageBase64 = readFileSync(screenshotPath).toString("base64");
211
+
212
+ const prompt = `Analyze this website screenshot from ${url}.
213
+
214
+ Extract:
215
+ 1. Color palette — primary, secondary, accent, background, text, border, muted colors (hex values)
216
+ 2. Typography — font families used, size scale, font weights
217
+ 3. UI components — list the main component patterns you see (buttons, cards, nav, etc.)
218
+ 4. Design style category (minimalist/corporate/startup/editorial/material/glassmorphism/brutalist/neubrutalism)
219
+ 5. Brief description of the overall design system
220
+
221
+ Respond ONLY with valid JSON:
222
+ {
223
+ "colors": {
224
+ "primary": "#...",
225
+ "secondary": "#...",
226
+ "accent": "#...",
227
+ "background": "#...",
228
+ "text": "#...",
229
+ "border": "#...",
230
+ "muted": "#..."
231
+ },
232
+ "typography": {
233
+ "fontFamilies": ["..."],
234
+ "sizes": ["xs", "sm", "base", "lg", "xl", "2xl"],
235
+ "weights": ["400", "500", "600", "700"]
236
+ },
237
+ "components": [
238
+ { "name": "Button", "description": "Rounded pill buttons with primary color fill" }
239
+ ],
240
+ "styleCategory": "minimalist",
241
+ "description": "..."
242
+ }`;
243
+
244
+ const result = await analyzeImage(imageBase64, "image/png", prompt, {
245
+ provider: options.provider,
246
+ model: options.model,
247
+ jsonMode: true,
248
+ maxTokens: 2048,
249
+ });
250
+
251
+ const parsed = parseJsonResponse(result.text) as {
252
+ colors: DesignColors;
253
+ typography: TypographyScale;
254
+ components: ComponentPattern[];
255
+ styleCategory: string;
256
+ description: string;
257
+ };
258
+
259
+ return {
260
+ colors: parsed.colors ?? {},
261
+ typography: parsed.typography ?? { fontFamilies: [], sizes: [], weights: [] },
262
+ components: parsed.components ?? [],
263
+ styleCategory: parsed.styleCategory ?? "unknown",
264
+ description: parsed.description ?? "",
265
+ rawAnalysis: result.text,
266
+ provider: result.provider,
267
+ model: result.model,
268
+ };
269
+ }
270
+
271
+ // ============================================================================
272
+ // Core analysis function
273
+ // ============================================================================
274
+
275
+ export async function analyzeSite(
276
+ url: string,
277
+ options?: {
278
+ provider?: VisionProvider;
279
+ model?: string;
280
+ quickMode?: boolean; // skip screenshot/vision — HTML analysis only
281
+ }
282
+ ): Promise<SiteAnalyzeResult> {
283
+ // Fetch HTML for framework/color detection
284
+ const html = await fetchPageSource(url);
285
+ const { hasShadcn, hasTailwind, framework } = detectFrameworkFromHtml(html);
286
+ const quickColors = extractColorsFromCss(html);
287
+
288
+ const availableProviders = listAvailableProviders();
289
+ const useVision = !options?.quickMode && availableProviders.length > 0;
290
+
291
+ if (!useVision) {
292
+ // Quick mode or no provider available — return HTML analysis only
293
+ return {
294
+ url,
295
+ framework,
296
+ hasShadcn,
297
+ hasTailwind,
298
+ colors: quickColors,
299
+ typography: { fontFamilies: [], sizes: [], weights: [] },
300
+ components: [],
301
+ provider: null,
302
+ model: null,
303
+ openStylesProfile: {
304
+ name: `site-${Date.now()}`,
305
+ displayName: url,
306
+ category: framework ?? "Unknown",
307
+ description: `Design system extracted from ${url} (HTML analysis only)`,
308
+ colors: quickColors as Record<string, string>,
309
+ typography: { fontFamilies: [], sizes: [], weights: [] },
310
+ framework,
311
+ tags: [
312
+ "extracted",
313
+ "auto-generated",
314
+ ...(hasShadcn ? ["shadcn"] : []),
315
+ ...(hasTailwind ? ["tailwind"] : []),
316
+ ],
317
+ },
318
+ };
319
+ }
320
+
321
+ // Vision-based analysis
322
+ const screenshotPath = await captureScreenshot(url);
323
+ let visionResult: Awaited<ReturnType<typeof analyzeScreenshot>> | null = null;
324
+
325
+ if (screenshotPath) {
326
+ visionResult = await analyzeScreenshot(screenshotPath, url, {
327
+ provider: options?.provider,
328
+ model: options?.model,
329
+ });
330
+ }
331
+
332
+ const colors = visionResult?.colors ?? quickColors;
333
+ const typography = visionResult?.typography ?? { fontFamilies: [], sizes: [], weights: [] };
334
+ const components = visionResult?.components ?? [];
335
+ const styleCategory = visionResult?.styleCategory ?? framework ?? "Unknown";
336
+ const description =
337
+ visionResult?.description ??
338
+ `Design system extracted from ${url}`;
339
+
340
+ return {
341
+ url,
342
+ framework,
343
+ hasShadcn,
344
+ hasTailwind,
345
+ colors,
346
+ typography,
347
+ components,
348
+ provider: visionResult?.provider ?? null,
349
+ model: visionResult?.model ?? null,
350
+ screenshotPath: screenshotPath ?? undefined,
351
+ rawAnalysis: visionResult?.rawAnalysis,
352
+ openStylesProfile: {
353
+ name: `site-${Date.now()}`,
354
+ displayName: url,
355
+ category: styleCategory,
356
+ description,
357
+ colors: colors as Record<string, string>,
358
+ typography,
359
+ framework,
360
+ tags: [
361
+ "extracted",
362
+ "auto-generated",
363
+ ...(hasShadcn ? ["shadcn"] : []),
364
+ ...(hasTailwind ? ["tailwind"] : []),
365
+ ],
366
+ },
367
+ };
368
+ }
369
+
370
+ // ============================================================================
371
+ // CLI
372
+ // ============================================================================
373
+
374
+ type OutputFormat = "profile" | "colors" | "full";
375
+
376
+ interface CliOptions {
377
+ url: string | null;
378
+ format: OutputFormat;
379
+ output: string | null;
380
+ provider: VisionProvider | null;
381
+ model: string | null;
382
+ quickMode: boolean;
383
+ }
384
+
385
+ function parseArgs(argv: string[]): { command: string; options: CliOptions } {
386
+ const args = argv.slice(2);
387
+
388
+ let command = "help";
389
+ const options: CliOptions = {
390
+ url: null,
391
+ format: "full",
392
+ output: null,
393
+ provider: null,
394
+ model: null,
395
+ quickMode: false,
396
+ };
397
+
398
+ if (args.length === 0) {
399
+ return { command: "help", options };
400
+ }
401
+
402
+ const firstArg = args[0];
403
+ if (firstArg === "analyze") {
404
+ command = "analyze";
405
+ } else if (
406
+ firstArg === "help" ||
407
+ firstArg === "--help" ||
408
+ firstArg === "-h"
409
+ ) {
410
+ command = "help";
411
+ } else if (firstArg.startsWith("http://") || firstArg.startsWith("https://")) {
412
+ // Allow `skill-siteanalyze <url>` shorthand
413
+ command = "analyze";
414
+ options.url = firstArg;
415
+ args.splice(0, 1);
416
+ // Re-parse remaining flags below (shift args[0] already used)
417
+ for (let i = 0; i < args.length; i++) {
418
+ const arg = args[i];
419
+ if ((arg === "--format" || arg === "-f") && args[i + 1]) {
420
+ const fmt = args[++i];
421
+ if (fmt === "profile" || fmt === "colors" || fmt === "full") {
422
+ options.format = fmt;
423
+ }
424
+ } else if ((arg === "--output" || arg === "-o") && args[i + 1]) {
425
+ options.output = args[++i];
426
+ } else if (arg === "--provider" && args[i + 1]) {
427
+ const p = args[++i] as VisionProvider;
428
+ if (["anthropic", "openai", "xai", "gemini"].includes(p)) options.provider = p;
429
+ } else if (arg === "--model" && args[i + 1]) {
430
+ options.model = args[++i];
431
+ } else if (arg === "--quick") {
432
+ options.quickMode = true;
433
+ }
434
+ }
435
+ return { command, options };
436
+ } else {
437
+ command = "help";
438
+ }
439
+
440
+ for (let i = 1; i < args.length; i++) {
441
+ const arg = args[i];
442
+ if ((arg === "--url" || arg === "-u") && args[i + 1]) {
443
+ options.url = args[++i];
444
+ } else if ((arg === "--format" || arg === "-f") && args[i + 1]) {
445
+ const fmt = args[++i];
446
+ if (fmt === "profile" || fmt === "colors" || fmt === "full") {
447
+ options.format = fmt;
448
+ } else {
449
+ console.error(`Invalid format: ${fmt}. Use: profile, colors, full`);
450
+ process.exit(1);
451
+ }
452
+ } else if ((arg === "--output" || arg === "-o") && args[i + 1]) {
453
+ options.output = args[++i];
454
+ } else if (arg === "--provider" && args[i + 1]) {
455
+ const p = args[++i] as VisionProvider;
456
+ if (["anthropic", "openai", "xai", "gemini"].includes(p)) {
457
+ options.provider = p;
458
+ } else {
459
+ console.error(`Invalid provider: ${p}. Use: anthropic, openai, xai, gemini`);
460
+ process.exit(1);
461
+ }
462
+ } else if (arg === "--model" && args[i + 1]) {
463
+ options.model = args[++i];
464
+ } else if (arg === "--quick") {
465
+ options.quickMode = true;
466
+ }
467
+ }
468
+
469
+ return { command, options };
470
+ }
471
+
472
+ function printHelp(): void {
473
+ const available = listAvailableProviders();
474
+ console.log(`
475
+ skill-siteanalyze — Analyze any website's design system via Playwright + AI Vision
476
+
477
+ USAGE
478
+ skill-siteanalyze <url> [options]
479
+ skill-siteanalyze analyze --url <url> [options]
480
+ skill-siteanalyze help
481
+
482
+ COMMANDS
483
+ analyze Analyze a website's design system (default when URL is given)
484
+ help Show this help message
485
+
486
+ OPTIONS
487
+ --url, -u <url> Website URL to analyze (required)
488
+ --format, -f <format> Output format: profile | colors | full (default: full)
489
+ --output, -o <file> Write JSON result to file instead of stdout
490
+ --provider <name> AI provider: anthropic | openai | xai | gemini (auto-detected)
491
+ --model <name> Model override (uses provider default if not set)
492
+ --quick Skip AI vision analysis — HTML/CSS analysis only
493
+
494
+ FORMATS
495
+ profile Print only the open-styles compatible profile object
496
+ colors Print only the extracted colors
497
+ full Print the complete analysis result (default)
498
+
499
+ EXAMPLES
500
+ skill-siteanalyze https://vercel.com
501
+ skill-siteanalyze https://stripe.com --format profile
502
+ skill-siteanalyze https://tailwindcss.com --provider openai
503
+ skill-siteanalyze https://shadcn.com --quick
504
+ skill-siteanalyze analyze --url https://example.com --output ./profile.json
505
+
506
+ ENVIRONMENT
507
+ ANTHROPIC_API_KEY Claude API key (anthropic provider)
508
+ OPENAI_API_KEY OpenAI API key (openai provider)
509
+ XAI_API_KEY xAI API key (xai provider)
510
+ GEMINI_API_KEY Google Gemini API key (gemini provider)
511
+
512
+ AVAILABLE PROVIDERS
513
+ ${available.length > 0 ? available.join(", ") : "(none — set an API key for vision analysis, or use --quick)"}
514
+ `);
515
+ }
516
+
517
+ async function main(): Promise<void> {
518
+ const { command, options } = parseArgs(process.argv);
519
+
520
+ if (command === "help") {
521
+ printHelp();
522
+ process.exit(0);
523
+ }
524
+
525
+ if (command === "analyze") {
526
+ if (!options.url) {
527
+ console.error("Error: URL is required.");
528
+ console.error(
529
+ "Run `skill-siteanalyze help` for usage."
530
+ );
531
+ process.exit(1);
532
+ }
533
+
534
+ const available = listAvailableProviders();
535
+ if (!options.quickMode && available.length === 0) {
536
+ console.warn(
537
+ "Warning: No AI provider API key found — running in quick mode (HTML analysis only)."
538
+ );
539
+ console.warn(
540
+ "Set ANTHROPIC_API_KEY, OPENAI_API_KEY, XAI_API_KEY, or GEMINI_API_KEY for visual analysis."
541
+ );
542
+ options.quickMode = true;
543
+ }
544
+
545
+ try {
546
+ console.error(`Analyzing ${options.url}...`);
547
+
548
+ const result = await analyzeSite(options.url, {
549
+ provider: options.provider ?? undefined,
550
+ model: options.model ?? undefined,
551
+ quickMode: options.quickMode,
552
+ });
553
+
554
+ let output: unknown;
555
+ if (options.format === "colors") {
556
+ output = result.colors;
557
+ } else if (options.format === "profile") {
558
+ output = result.openStylesProfile;
559
+ } else {
560
+ output = {
561
+ url: result.url,
562
+ framework: result.framework,
563
+ hasShadcn: result.hasShadcn,
564
+ hasTailwind: result.hasTailwind,
565
+ colors: result.colors,
566
+ typography: result.typography,
567
+ components: result.components,
568
+ provider: result.provider,
569
+ model: result.model,
570
+ openStylesProfile: result.openStylesProfile,
571
+ };
572
+ }
573
+
574
+ const json = JSON.stringify(output, null, 2);
575
+
576
+ if (options.output) {
577
+ writeFileSync(options.output, json, "utf-8");
578
+ console.error(`Result written to: ${options.output}`);
579
+ } else {
580
+ console.log(json);
581
+ }
582
+ } catch (error) {
583
+ console.error(
584
+ "Error:",
585
+ error instanceof Error ? error.message : String(error)
586
+ );
587
+ process.exit(1);
588
+ }
589
+ }
590
+ }
591
+
592
+ main();
@@ -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-slack-assistant");
9
+ }
10
+
11
+ const program = new Command();
12
+
13
+ program
14
+ .name("skill-slack-assistant")
15
+ .description("Slack Assistant 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();
@@ -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-sleep-routine-analyzer");
9
+ }
10
+
11
+ const program = new Command();
12
+
13
+ program
14
+ .name("skill-sleep-routine-analyzer")
15
+ .description("Sleep Routine Analyzer 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();
@@ -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-social-media-kit");
9
+ }
10
+
11
+ const program = new Command();
12
+
13
+ program
14
+ .name("skill-social-media-kit")
15
+ .description("Social Media Kit 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();