@hasna/skills 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +1743 -420
- package/bin/mcp.js +791 -259
- package/dist/index.d.ts +5 -2
- package/dist/index.js +533 -75
- package/dist/lib/config.d.ts +27 -0
- package/dist/lib/config.test.d.ts +1 -0
- package/dist/lib/installer.d.ts +36 -2
- package/dist/lib/registry.d.ts +16 -0
- package/dist/lib/scheduler.d.ts +47 -0
- package/dist/types/api.d.ts +74 -0
- package/package.json +5 -2
- package/skills/_common/index.ts +4 -0
- package/skills/_common/vision.ts +374 -0
- package/skills/skill-academic-journal-matcher/bin/cli.ts +34 -0
- package/skills/skill-action-item-router/bin/cli.ts +34 -0
- package/skills/skill-ad-creative-generator/bin/cli.ts +34 -0
- package/skills/skill-advanced-math/bin/cli.ts +34 -0
- package/skills/skill-analyze-data/bin/cli.ts +19 -0
- package/skills/skill-anomaly-investigator/bin/cli.ts +34 -0
- package/skills/skill-api-test-suite/bin/cli.ts +34 -0
- package/skills/skill-apidocs/bin/cli.ts +87 -0
- package/skills/skill-audio-cleanup-lab/bin/cli.ts +6 -0
- package/skills/skill-audiobook-chapter-proofer/bin/cli.ts +34 -0
- package/skills/skill-banner-ad-suite/bin/cli.ts +34 -0
- package/skills/skill-benchmark-finder/bin/cli.ts +34 -0
- package/skills/skill-bio-sequence-tool/bin/cli.ts +34 -0
- package/skills/skill-blog-topic-cluster/bin/cli.ts +34 -0
- package/skills/skill-brand-style-guide/bin/cli.ts +19 -0
- package/skills/skill-brand-voice-audit/bin/cli.ts +34 -0
- package/skills/skill-budget-variance-analyzer/bin/cli.ts +6 -0
- package/skills/skill-businessactivity/bin/cli.ts +28 -0
- package/skills/skill-calendar-events/bin/cli.ts +34 -0
- package/skills/skill-campaign-metric-brief/bin/cli.ts +34 -0
- package/skills/skill-campaign-moodboard/bin/cli.ts +34 -0
- package/skills/skill-caption-style-stylist/bin/cli.ts +34 -0
- package/skills/skill-chemistry-calculator/bin/cli.ts +34 -0
- package/skills/skill-churn-risk-notifier/bin/cli.ts +34 -0
- package/skills/skill-citation-formatter/bin/cli.ts +34 -0
- package/skills/skill-classroom-newsletter-kit/bin/cli.ts +34 -0
- package/skills/skill-color-palette-harmonizer/bin/cli.ts +34 -0
- package/skills/skill-colorextract/SKILL.md +35 -0
- package/skills/skill-colorextract/bun.lock +102 -0
- package/skills/skill-colorextract/package.json +13 -0
- package/skills/skill-colorextract/src/index.ts +405 -0
- package/skills/skill-competitor-ad-analyzer/bin/cli.ts +34 -0
- package/skills/skill-compliance-copy-check/bin/cli.ts +34 -0
- package/skills/skill-compliance-report-pack/bin/cli.ts +34 -0
- package/skills/skill-compress-video/bin/cli.ts +19 -0
- package/skills/skill-consolelog/bin/cli.ts +884 -0
- package/skills/skill-contract-plainlanguage/bin/cli.ts +34 -0
- package/skills/skill-copytone-translator/bin/cli.ts +34 -0
- package/skills/skill-create-blog-article/bin/cli.ts +34 -0
- package/skills/skill-create-ebook/bin/cli.ts +34 -0
- package/skills/skill-crm-note-enhancer/bin/cli.ts +34 -0
- package/skills/skill-customer-journey-mapper/bin/cli.ts +34 -0
- package/skills/skill-dashboard-builder/bin/cli.ts +34 -0
- package/skills/skill-dashboard-narrator/bin/cli.ts +34 -0
- package/skills/skill-data-anonymizer/bin/cli.ts +34 -0
- package/skills/skill-database-explorer/bin/cli.ts +34 -0
- package/skills/skill-dataset-health-check/bin/cli.ts +34 -0
- package/skills/skill-decision-journal/bin/cli.ts +34 -0
- package/skills/skill-delegation-brief-writer/bin/cli.ts +34 -0
- package/skills/skill-destination-briefing/bin/cli.ts +34 -0
- package/skills/skill-diff-viewer/bin/cli.ts +34 -0
- package/skills/skill-domainpurchase/SKILL.md +46 -0
- package/skills/skill-domainpurchase/bin/cli.ts +683 -0
- package/skills/skill-domainsearch/SKILL.md +41 -0
- package/skills/skill-domainsearch/bin/cli.ts +410 -0
- package/skills/skill-educational-resource-finder/bin/cli.ts +34 -0
- package/skills/skill-email-campaign/bin/cli.ts +34 -0
- package/skills/skill-exam-readiness-check/bin/cli.ts +34 -0
- package/skills/skill-experiment-power-calculator/bin/cli.ts +34 -0
- package/skills/skill-extract-audio/bin/cli.ts +19 -0
- package/skills/skill-extract-frames/bin/cli.ts +34 -0
- package/skills/skill-extract-invoice/bin/cli.ts +34 -0
- package/skills/skill-family-activity-curator/bin/cli.ts +34 -0
- package/skills/skill-faq-packager/bin/cli.ts +34 -0
- package/skills/skill-feedback-survey-designer/bin/cli.ts +34 -0
- package/skills/skill-field-trip-planner/bin/cli.ts +34 -0
- package/skills/skill-file-organizer/bin/cli.ts +34 -0
- package/skills/skill-folder-tree/bin/cli.ts +34 -0
- package/skills/skill-forecast-scenario-lab/bin/cli.ts +34 -0
- package/skills/skill-form-filler/bin/cli.ts +34 -0
- package/skills/skill-generate-api-client/bin/cli.ts +34 -0
- package/skills/skill-generate-book-cover/bin/cli.ts +34 -0
- package/skills/skill-generate-chart/bin/cli.ts +34 -0
- package/skills/skill-generate-diagram/bin/cli.ts +34 -0
- package/skills/skill-generate-dockerfile/bin/cli.ts +34 -0
- package/skills/skill-generate-documentation/bin/cli.ts +34 -0
- package/skills/skill-generate-docx/bin/cli.ts +6 -0
- package/skills/skill-generate-env/bin/cli.ts +34 -0
- package/skills/skill-generate-excel/bin/cli.ts +34 -0
- package/skills/skill-generate-favicon/bin/cli.ts +34 -0
- package/skills/skill-generate-mock-data/bin/cli.ts +34 -0
- package/skills/skill-generate-pdf/bin/cli.ts +6 -0
- package/skills/skill-generate-pr-description/bin/cli.ts +34 -0
- package/skills/skill-generate-presentation/bin/cli.ts +34 -0
- package/skills/skill-generate-qrcode/bin/cli.ts +34 -0
- package/skills/skill-generate-regex/bin/cli.ts +34 -0
- package/skills/skill-generate-resume/bin/cli.ts +34 -0
- package/skills/skill-generate-sitemap/bin/cli.ts +34 -0
- package/skills/skill-generate-social-posts/bin/cli.ts +34 -0
- package/skills/skill-generate-sql/bin/cli.ts +34 -0
- package/skills/skill-gif-maker/bin/cli.ts +34 -0
- package/skills/skill-github-manager/bin/cli.ts +34 -0
- package/skills/skill-gmail/bin/cli.ts +34 -0
- package/skills/skill-goal-quarterly-roadmap/bin/cli.ts +34 -0
- package/skills/skill-grant-application-drafter/bin/cli.ts +34 -0
- package/skills/skill-grocery-basket-optimizer/bin/cli.ts +34 -0
- package/skills/skill-guest-communication-suite/bin/cli.ts +34 -0
- package/skills/skill-habit-reflection-digest/bin/cli.ts +34 -0
- package/skills/skill-highlight-reel-generator/bin/cli.ts +34 -0
- package/skills/skill-homework-feedback-coach/bin/cli.ts +34 -0
- package/skills/skill-hook/bunfig.toml +5 -0
- package/skills/skill-household-maintenance-mgr/bin/cli.ts +34 -0
- package/skills/skill-http-server/bin/cli.ts +34 -0
- package/skills/skill-implementation/bunfig.toml +5 -0
- package/skills/skill-implementation-agent/bin/cli.ts +34 -0
- package/skills/skill-implementation-plan/bin/cli.ts +34 -0
- package/skills/skill-implementation-todo/bin/cli.ts +34 -0
- package/skills/skill-inbox-priority-planner/bin/cli.ts +34 -0
- package/skills/skill-invoice/bin/cli.ts +20 -0
- package/skills/skill-invoice-dispute-helper/bin/cli.ts +34 -0
- package/skills/skill-itinerary-architect/bin/cli.ts +34 -0
- package/skills/skill-jingle-composer/bin/cli.ts +34 -0
- package/skills/skill-kpi-digest-generator/bin/cli.ts +34 -0
- package/skills/skill-lab-notebook-formatter/bin/cli.ts +34 -0
- package/skills/skill-landing-page-copy/bin/cli.ts +34 -0
- package/skills/skill-latex-table-generator/bin/cli.ts +34 -0
- package/skills/skill-learning-style-profiler/bin/cli.ts +34 -0
- package/skills/skill-lesson-plan-customizer/bin/cli.ts +34 -0
- package/skills/skill-livestream-runofshow/bin/cli.ts +34 -0
- package/skills/skill-longform-structurer/bin/cli.ts +34 -0
- package/skills/skill-lorem-generator/bin/cli.ts +34 -0
- package/skills/skill-managehook/bin/cli.ts +241 -0
- package/skills/skill-managemcp/bin/cli.ts +241 -0
- package/skills/skill-manageskill/bin/cli.ts +241 -0
- package/skills/skill-markdown-validator/bin/cli.ts +34 -0
- package/skills/skill-mcp-builder/bin/cli.ts +34 -0
- package/skills/skill-meal-plan-designer/bin/cli.ts +34 -0
- package/skills/skill-meeting-insight-summarizer/bin/cli.ts +34 -0
- package/skills/skill-merge-pdfs/bin/cli.ts +34 -0
- package/skills/skill-microcopy-generator/bin/cli.ts +34 -0
- package/skills/skill-mindfulness-prompt-cache/bin/cli.ts +34 -0
- package/skills/skill-notion-manager/bin/cli.ts +34 -0
- package/skills/skill-onboarding-sequence-builder/bin/cli.ts +34 -0
- package/skills/skill-onsite-ops-checklist/bin/cli.ts +34 -0
- package/skills/skill-outreach-cadence-designer/bin/cli.ts +34 -0
- package/skills/skill-packaging-concept-studio/bin/cli.ts +34 -0
- package/skills/skill-packing-plan-pro/bin/cli.ts +34 -0
- package/skills/skill-parent-teacher-brief/bin/cli.ts +34 -0
- package/skills/skill-partner-kit-assembler/bin/cli.ts +34 -0
- package/skills/skill-payroll-change-prepper/bin/cli.ts +34 -0
- package/skills/skill-persona-based-adwriter/bin/cli.ts +34 -0
- package/skills/skill-persona-generator/bin/cli.ts +34 -0
- package/skills/skill-personal-daily-ops/bin/cli.ts +34 -0
- package/skills/skill-pet-care-scheduler/bin/cli.ts +34 -0
- package/skills/skill-podcast-show-notes/bin/cli.ts +34 -0
- package/skills/skill-presentation-theme-maker/bin/cli.ts +34 -0
- package/skills/skill-press-release-drafter/bin/cli.ts +34 -0
- package/skills/skill-print-collateral-designer/bin/cli.ts +34 -0
- package/skills/skill-procurement-scorecard/bin/cli.ts +34 -0
- package/skills/skill-product-demo-script/bin/cli.ts +34 -0
- package/skills/skill-product-mockup/bin/cli.ts +34 -0
- package/skills/skill-project-retro-companion/bin/cli.ts +34 -0
- package/skills/skill-proposal-redline-advisor/bin/cli.ts +34 -0
- package/skills/skill-regex-tester/bin/cli.ts +34 -0
- package/skills/skill-remove-background/bin/cli.ts +34 -0
- package/skills/skill-risk-disclosure-kit/bin/cli.ts +34 -0
- package/skills/skill-roi-comparison-tool/bin/cli.ts +34 -0
- package/skills/skill-sales-call-recapper/bin/cli.ts +34 -0
- package/skills/skill-salescopy/bin/cli.ts +20 -0
- package/skills/skill-scaffold-project/bin/cli.ts +34 -0
- package/skills/skill-scholarship-tracker/bin/cli.ts +34 -0
- package/skills/skill-scientific-figure-check/bin/cli.ts +34 -0
- package/skills/skill-seating-chart-maker/bin/cli.ts +34 -0
- package/skills/skill-security-audit/bin/cli.ts +34 -0
- package/skills/skill-seo-brief-builder/bin/cli.ts +34 -0
- package/skills/skill-siteanalyze/SKILL.md +25 -0
- package/skills/skill-siteanalyze/package.json +13 -0
- package/skills/skill-siteanalyze/src/index.ts +592 -0
- package/skills/skill-slack-assistant/bin/cli.ts +34 -0
- package/skills/skill-sleep-routine-analyzer/bin/cli.ts +34 -0
- package/skills/skill-social-media-kit/bin/cli.ts +34 -0
- package/skills/skill-split-pdf/bin/cli.ts +34 -0
- package/skills/skill-sponsorship-proposal-lab/bin/cli.ts +34 -0
- package/skills/skill-spreadsheet-cleanroom/bin/cli.ts +34 -0
- package/skills/skill-statistical-test-selector/bin/cli.ts +34 -0
- package/skills/skill-stress-relief-playbook/bin/cli.ts +34 -0
- package/skills/skill-study-guide-builder/bin/cli.ts +34 -0
- package/skills/skill-subscription-spend-watcher/bin/cli.ts +34 -0
- package/skills/skill-subtitle/bin/cli.ts +20 -0
- package/skills/skill-survey-insight-extractor/bin/cli.ts +34 -0
- package/skills/skill-terraform-generator/bin/cli.ts +34 -0
- package/skills/skill-testimonial-graphics/bin/cli.ts +34 -0
- package/skills/skill-timesheet/bin/cli.ts +47 -0
- package/skills/skill-travel-budget-balancer/bin/cli.ts +34 -0
- package/skills/skill-validate-config/bin/cli.ts +34 -0
- package/skills/skill-video-cut-suggester/bin/cli.ts +34 -0
- package/skills/skill-video-downloader/bin/cli.ts +34 -0
- package/skills/skill-video-thumbnail/bin/cli.ts +34 -0
- package/skills/skill-voiceover-casting-assistant/bin/cli.ts +34 -0
- package/skills/skill-watermark/bin/cli.ts +34 -0
- package/skills/skill-webcrawling/bin/cli.ts +21 -0
- package/skills/skill-webinar-script-coach/bin/cli.ts +34 -0
- package/skills/skill-wellness-progress-reporter/bin/cli.ts +34 -0
- package/skills/skill-workout-cycle-planner/bin/cli.ts +34 -0
|
@@ -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();
|