@hasna/skills 0.1.14 → 0.1.16

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.
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Loads configuration from:
5
5
  * 1. Project-local: ./skills.config.json (highest priority)
6
- * 2. Global: ~/.skillsrc (JSON format, lowest priority)
6
+ * 2. Global: ~/.hasna/skills/config.json (JSON format, lowest priority)
7
+ * (backward compat: also checks ~/.skillsrc)
7
8
  *
8
9
  * Values from the project config override global config.
9
10
  */
@@ -13,6 +14,12 @@ export interface SkillsConfig {
13
14
  format?: "compact" | "json" | "csv";
14
15
  }
15
16
  export type ConfigScope = "global" | "project";
17
+ /**
18
+ * Get the data directory for skills global config/data.
19
+ * New default: ~/.hasna/skills/
20
+ * Auto-migrates from ~/.skillsrc if the new config doesn't exist yet.
21
+ */
22
+ export declare function getDataDir(): string;
16
23
  /**
17
24
  * Get the config file path for a given scope
18
25
  */
@@ -59,9 +59,11 @@ export declare function getInstalledSkills(targetDir?: string): string[];
59
59
  * Remove an installed skill
60
60
  */
61
61
  export declare function removeSkill(name: string, targetDir?: string): boolean;
62
- export type AgentTarget = "claude" | "codex" | "gemini";
62
+ export type AgentTarget = "claude" | "codex" | "gemini" | "pi" | "opencode";
63
63
  export type AgentScope = "global" | "project";
64
64
  export declare const AGENT_TARGETS: AgentTarget[];
65
+ /** Human-readable labels for each agent */
66
+ export declare const AGENT_LABELS: Record<AgentTarget, string>;
65
67
  /**
66
68
  * Resolve an agent argument ("all" or a specific agent name) to a list of AgentTarget values.
67
69
  * Throws if the agent name is not recognized.
@@ -73,7 +75,14 @@ export interface AgentInstallOptions {
73
75
  projectDir?: string;
74
76
  }
75
77
  /**
76
- * 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/
77
86
  */
78
87
  export declare function getAgentSkillsDir(agent: AgentTarget, scope?: AgentScope, projectDir?: string): string;
79
88
  /**
@@ -8,10 +8,23 @@ 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
+ * - ~/.hasna/skills/custom/ (global custom, new path)
19
+ * - ~/.skills/ (global custom, legacy path)
20
+ * - ./.custom-skills/ (project-level custom, relative to cwd)
21
+ *
22
+ * Custom skills with the same name as official skills take precedence.
23
+ * Results are cached for 5 seconds.
24
+ */
25
+ export declare function loadRegistry(cwd?: string): SkillMeta[];
26
+ /** Invalidate the registry cache (e.g. after installing a custom skill). */
27
+ export declare function clearRegistryCache(): void;
15
28
  export declare function getSkillsByCategory(category: Category): SkillMeta[];
16
29
  export declare function searchSkills(query: string): SkillMeta[];
17
30
  export declare function getSkill(name: string): SkillMeta | undefined;
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -60,6 +60,7 @@
60
60
  "typescript": "^5"
61
61
  },
62
62
  "dependencies": {
63
+ "@hasna/cloud": "^0.1.0",
63
64
  "@modelcontextprotocol/sdk": "^1.26.0",
64
65
  "chalk": "^5.3.0",
65
66
  "commander": "^12.1.0",
@@ -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,35 @@
1
+ ---
2
+ name: colorextract
3
+ description: Extract color palettes from screenshots and images using Claude Vision. Outputs open-styles-compatible profiles.
4
+ ---
5
+
6
+ # Color Extract Skill
7
+
8
+ Analyze any screenshot or image and extract a complete color palette — hex values, usage context, and an open-styles compatible profile.
9
+
10
+ ## Usage
11
+
12
+ ```bash
13
+ # Extract from local screenshot
14
+ skill-colorextract extract --image ./screenshot.png
15
+
16
+ # Extract from image URL
17
+ skill-colorextract extract --image https://example.com/screenshot.png
18
+
19
+ # Output as open-styles profile JSON
20
+ skill-colorextract extract --image ./screenshot.png --format profile
21
+
22
+ # Save result to file
23
+ skill-colorextract extract --image ./screenshot.png --output ./colors.json
24
+ ```
25
+
26
+ ## Environment Variables
27
+
28
+ - `ANTHROPIC_API_KEY` — required for Claude Vision analysis
29
+
30
+ ## Output
31
+
32
+ Returns a JSON object with:
33
+ - `colors`: all extracted colors with hex, name, usage context, frequency estimate
34
+ - `palette`: categorized palette (primary, secondary, accent, neutral, background, text)
35
+ - `openStylesProfile`: ready-to-import open-styles profile
@@ -0,0 +1,102 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "skill-colorextract",
7
+ "dependencies": {
8
+ "@anthropic-ai/sdk": "^0.39.0",
9
+ },
10
+ "devDependencies": {
11
+ "@types/bun": "latest",
12
+ },
13
+ },
14
+ },
15
+ "packages": {
16
+ "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.39.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-eMyDIPRZbt1CCLErRCi3exlAvNkBtRe+kW5vvJyef93PmNr/clstYgHhtvmkxN82nlKgzyGPCyGxrm0JQ1ZIdg=="],
17
+
18
+ "@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="],
19
+
20
+ "@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
21
+
22
+ "@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
23
+
24
+ "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
25
+
26
+ "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
27
+
28
+ "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
29
+
30
+ "bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="],
31
+
32
+ "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
33
+
34
+ "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
35
+
36
+ "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
37
+
38
+ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
39
+
40
+ "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
41
+
42
+ "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
43
+
44
+ "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
45
+
46
+ "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
47
+
48
+ "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
49
+
50
+ "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
51
+
52
+ "form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="],
53
+
54
+ "formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
55
+
56
+ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
57
+
58
+ "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
59
+
60
+ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
61
+
62
+ "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
63
+
64
+ "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
65
+
66
+ "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
67
+
68
+ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
69
+
70
+ "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="],
71
+
72
+ "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
73
+
74
+ "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
75
+
76
+ "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
77
+
78
+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
79
+
80
+ "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
81
+
82
+ "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
83
+
84
+ "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
85
+
86
+ "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
87
+
88
+ "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="],
89
+
90
+ "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
91
+
92
+ "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
93
+
94
+ "@types/node-fetch/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
95
+
96
+ "bun-types/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
97
+
98
+ "@types/node-fetch/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
99
+
100
+ "bun-types/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
101
+ }
102
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "skill-colorextract",
3
+ "version": "1.0.0",
4
+ "description": "Extract color palettes from screenshots and images using AI Vision (anthropic/openai/xai/gemini)",
5
+ "main": "src/index.ts",
6
+ "type": "module",
7
+ "bin": { "skill-colorextract": "src/index.ts" },
8
+ "scripts": { "start": "bun run src/index.ts" },
9
+ "keywords": ["colors", "palette", "design", "vision", "screenshot", "extract"],
10
+ "license": "Apache-2.0",
11
+ "dependencies": {},
12
+ "devDependencies": { "@types/bun": "latest" }
13
+ }