@emmraan/ai-skills 0.0.1 → 0.0.2

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.
@@ -1,66 +1,66 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import * as downloader from '../../core/downloader';
3
-
4
- vi.mock('../../utils/retry', () => ({
5
- fetchWithRetry: vi.fn(),
6
- }));
7
-
8
- describe('downloader', () => {
9
- beforeEach(() => {
10
- vi.clearAllMocks();
11
- });
12
-
13
- it('should fetch registry index', async () => {
14
- const { fetchWithRetry } = await import('../../utils/retry');
15
- const mockIndex = {
16
- skills: {
17
- react: {
18
- version: '18.2.0',
19
- domains: ['frontend', 'ui'],
20
- lastGenerated: '2026-02-06T00:00:00Z',
21
- },
22
- },
23
- };
24
-
25
- vi.mocked(fetchWithRetry).mockResolvedValue(JSON.stringify(mockIndex));
26
-
27
- const result = await downloader.fetchRegistryIndex();
28
- expect(result.skills.react).toBeDefined();
29
- expect(result.skills.react.version).toBe('18.2.0');
30
- });
31
-
32
- it('should fetch skill markdown', async () => {
33
- const { fetchWithRetry } = await import('../../utils/retry');
34
- const mockContent = '# React Skill\n\nSome content';
35
-
36
- vi.mocked(fetchWithRetry).mockResolvedValue(mockContent);
37
-
38
- const result = await downloader.fetchSkill('react');
39
- expect(result).toBe(mockContent);
40
- });
41
-
42
- it('should get available skills', async () => {
43
- const { fetchWithRetry } = await import('../../utils/retry');
44
- const mockIndex = {
45
- skills: {
46
- react: {
47
- version: '18.2.0',
48
- domains: ['frontend'],
49
- lastGenerated: '2026-02-06T00:00:00Z',
50
- },
51
- vue: {
52
- version: '3.4.0',
53
- domains: ['frontend'],
54
- lastGenerated: '2026-02-06T00:00:00Z',
55
- },
56
- },
57
- };
58
-
59
- vi.mocked(fetchWithRetry).mockResolvedValue(JSON.stringify(mockIndex));
60
-
61
- const result = await downloader.getAvailableSkills();
62
- expect(result).toContain('react');
63
- expect(result).toContain('vue');
64
- expect(result).toHaveLength(2);
65
- });
66
- });
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import * as downloader from '../../core/downloader';
3
+
4
+ vi.mock('../../utils/retry', () => ({
5
+ fetchWithRetry: vi.fn(),
6
+ }));
7
+
8
+ describe('downloader', () => {
9
+ beforeEach(() => {
10
+ vi.clearAllMocks();
11
+ });
12
+
13
+ it('should fetch registry index', async () => {
14
+ const { fetchWithRetry } = await import('../../utils/retry');
15
+ const mockIndex = {
16
+ skills: {
17
+ react: {
18
+ version: '18.2.0',
19
+ domains: ['frontend', 'ui'],
20
+ lastGenerated: '2026-02-06T00:00:00Z',
21
+ },
22
+ },
23
+ };
24
+
25
+ vi.mocked(fetchWithRetry).mockResolvedValue(JSON.stringify(mockIndex));
26
+
27
+ const result = await downloader.fetchRegistryIndex();
28
+ expect(result.skills.react).toBeDefined();
29
+ expect(result.skills.react.version).toBe('18.2.0');
30
+ });
31
+
32
+ it('should fetch skill markdown', async () => {
33
+ const { fetchWithRetry } = await import('../../utils/retry');
34
+ const mockContent = '# React Skill\n\nSome content';
35
+
36
+ vi.mocked(fetchWithRetry).mockResolvedValue(mockContent);
37
+
38
+ const result = await downloader.fetchSkill('react');
39
+ expect(result).toBe(mockContent);
40
+ });
41
+
42
+ it('should get available skills', async () => {
43
+ const { fetchWithRetry } = await import('../../utils/retry');
44
+ const mockIndex = {
45
+ skills: {
46
+ react: {
47
+ version: '18.2.0',
48
+ domains: ['frontend'],
49
+ lastGenerated: '2026-02-06T00:00:00Z',
50
+ },
51
+ vue: {
52
+ version: '3.4.0',
53
+ domains: ['frontend'],
54
+ lastGenerated: '2026-02-06T00:00:00Z',
55
+ },
56
+ },
57
+ };
58
+
59
+ vi.mocked(fetchWithRetry).mockResolvedValue(JSON.stringify(mockIndex));
60
+
61
+ const result = await downloader.getAvailableSkills();
62
+ expect(result).toContain('react');
63
+ expect(result).toContain('vue');
64
+ expect(result).toHaveLength(2);
65
+ });
66
+ });
@@ -1,90 +1,90 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
- import { rm } from 'fs/promises';
3
- import { tmpdir } from 'os';
4
- import { join } from 'path';
5
- import * as lockfile from '../../core/lockfile';
6
-
7
- const lockfilePath = join(tmpdir(), `test-skills-lock-${Date.now()}.json`);
8
-
9
- // Mock the config to use a temp directory
10
- vi.mock('../../core/config', () => ({
11
- getLockfilePath: () => lockfilePath,
12
- }));
13
-
14
- describe('lockfile', () => {
15
- beforeEach(async () => {
16
- // Clear the lockfile before each test
17
- try {
18
- await rm(lockfilePath, { force: true });
19
- } catch {
20
- // File doesn't exist, that's fine
21
- }
22
- });
23
-
24
- afterEach(async () => {
25
- // Clean up after tests
26
- try {
27
- await rm(lockfilePath, { force: true });
28
- } catch {
29
- // File doesn't exist, that's fine
30
- }
31
- });
32
-
33
- it('should read an empty lockfile as initial state', async () => {
34
- const result = await lockfile.readLockfile();
35
- expect(result.version).toBe(1);
36
- expect(result.installedSkills).toEqual({});
37
- });
38
-
39
- it('should add a skill to lockfile', async () => {
40
- const skillName = 'react';
41
- const version = '18.2.0';
42
- const hash = 'abc123';
43
- const paths = ['/home/user/.agents/skills/react/SKILLS.md'];
44
-
45
- await lockfile.addSkillToLockfile(skillName, version, hash, paths);
46
-
47
- const result = await lockfile.readLockfile();
48
- expect(result.installedSkills[skillName]).toBeDefined();
49
- expect(result.installedSkills[skillName].version).toBe(version);
50
- expect(result.installedSkills[skillName].hash).toBe(hash);
51
- });
52
-
53
- it('should remove a skill from lockfile', async () => {
54
- const skillName = 'react';
55
- await lockfile.addSkillToLockfile(skillName, '18.2.0', 'hash1', []);
56
-
57
- let result = await lockfile.readLockfile();
58
- expect(result.installedSkills[skillName]).toBeDefined();
59
-
60
- await lockfile.removeSkillFromLockfile(skillName);
61
-
62
- result = await lockfile.readLockfile();
63
- expect(result.installedSkills[skillName]).toBeUndefined();
64
- });
65
-
66
- it('should get installed skills', async () => {
67
- await lockfile.addSkillToLockfile('react', '18.2.0', 'hash1', []);
68
- await lockfile.addSkillToLockfile('vue', '3.4.0', 'hash2', []);
69
-
70
- const installed = await lockfile.getInstalledSkills();
71
- expect(installed).toHaveLength(2);
72
- expect(installed.map((s) => s.name)).toContain('react');
73
- expect(installed.map((s) => s.name)).toContain('vue');
74
- });
75
-
76
- it('should get a specific skill entry', async () => {
77
- const skillName = 'react';
78
- const version = '18.2.0';
79
- const hash = 'abc123';
80
- const paths = ['/home/user/.agents/skills/react/SKILLS.md'];
81
-
82
- await lockfile.addSkillToLockfile(skillName, version, hash, paths);
83
-
84
- const entry = await lockfile.getSkillEntry(skillName);
85
- expect(entry).not.toBeNull();
86
- expect(entry?.name).toBe(skillName);
87
- expect(entry?.version).toBe(version);
88
- expect(entry?.hash).toBe(hash);
89
- });
90
- });
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { rm } from 'fs/promises';
3
+ import { tmpdir } from 'os';
4
+ import { join } from 'path';
5
+ import * as lockfile from '../../core/lockfile';
6
+
7
+ const lockfilePath = join(tmpdir(), `test-skills-lock-${Date.now()}.json`);
8
+
9
+ // Mock the config to use a temp directory
10
+ vi.mock('../../core/config', () => ({
11
+ getLockfilePath: () => lockfilePath,
12
+ }));
13
+
14
+ describe('lockfile', () => {
15
+ beforeEach(async () => {
16
+ // Clear the lockfile before each test
17
+ try {
18
+ await rm(lockfilePath, { force: true });
19
+ } catch {
20
+ // File doesn't exist, that's fine
21
+ }
22
+ });
23
+
24
+ afterEach(async () => {
25
+ // Clean up after tests
26
+ try {
27
+ await rm(lockfilePath, { force: true });
28
+ } catch {
29
+ // File doesn't exist, that's fine
30
+ }
31
+ });
32
+
33
+ it('should read an empty lockfile as initial state', async () => {
34
+ const result = await lockfile.readLockfile();
35
+ expect(result.version).toBe(1);
36
+ expect(result.installedSkills).toEqual({});
37
+ });
38
+
39
+ it('should add a skill to lockfile', async () => {
40
+ const skillName = 'react';
41
+ const version = '18.2.0';
42
+ const hash = 'abc123';
43
+ const paths = ['/home/user/.agents/skills/react/SKILLS.md'];
44
+
45
+ await lockfile.addSkillToLockfile(skillName, version, hash, paths);
46
+
47
+ const result = await lockfile.readLockfile();
48
+ expect(result.installedSkills[skillName]).toBeDefined();
49
+ expect(result.installedSkills[skillName].version).toBe(version);
50
+ expect(result.installedSkills[skillName].hash).toBe(hash);
51
+ });
52
+
53
+ it('should remove a skill from lockfile', async () => {
54
+ const skillName = 'react';
55
+ await lockfile.addSkillToLockfile(skillName, '18.2.0', 'hash1', []);
56
+
57
+ let result = await lockfile.readLockfile();
58
+ expect(result.installedSkills[skillName]).toBeDefined();
59
+
60
+ await lockfile.removeSkillFromLockfile(skillName);
61
+
62
+ result = await lockfile.readLockfile();
63
+ expect(result.installedSkills[skillName]).toBeUndefined();
64
+ });
65
+
66
+ it('should get installed skills', async () => {
67
+ await lockfile.addSkillToLockfile('react', '18.2.0', 'hash1', []);
68
+ await lockfile.addSkillToLockfile('vue', '3.4.0', 'hash2', []);
69
+
70
+ const installed = await lockfile.getInstalledSkills();
71
+ expect(installed).toHaveLength(2);
72
+ expect(installed.map((s) => s.name)).toContain('react');
73
+ expect(installed.map((s) => s.name)).toContain('vue');
74
+ });
75
+
76
+ it('should get a specific skill entry', async () => {
77
+ const skillName = 'react';
78
+ const version = '18.2.0';
79
+ const hash = 'abc123';
80
+ const paths = ['/home/user/.agents/skills/react/SKILLS.md'];
81
+
82
+ await lockfile.addSkillToLockfile(skillName, version, hash, paths);
83
+
84
+ const entry = await lockfile.getSkillEntry(skillName);
85
+ expect(entry).not.toBeNull();
86
+ expect(entry?.name).toBe(skillName);
87
+ expect(entry?.version).toBe(version);
88
+ expect(entry?.hash).toBe(hash);
89
+ });
90
+ });
@@ -1,29 +1,29 @@
1
- import { homedir, platform } from 'os';
2
- import { join } from 'path';
3
-
4
- export const REGISTRY_URL =
5
- 'https://raw.githubusercontent.com/Emmraan/ai-skills/main/packages/skills-registry';
6
-
7
- export const REGISTRY_INDEX_URL = `${REGISTRY_URL}/skills/.index.json`;
8
-
9
- export const AGENT_PLATFORMS = ['.claude', '.gemini', '.vscode', '.opencode', '.codex', '.agents'];
10
-
11
- export function getAgentPlatformDirs(): string[] {
12
- const home = homedir();
13
- return AGENT_PLATFORMS.map((p) => join(home, p, 'skills'));
14
- }
15
-
16
- export function getLockfilePath(): string {
17
- const home = homedir();
18
- return join(home, '.ai-skills', '.skill-lock.json');
19
- }
20
-
21
- export function getSkillInstallPaths(skillName: string): string[] {
22
- return getAgentPlatformDirs().map((dir) => join(dir, skillName, 'SKILLS.md'));
23
- }
24
-
25
- export function getSkillMetadataPaths(skillName: string): string[] {
26
- return getAgentPlatformDirs().map((dir) => join(dir, skillName, '.skill-metadata.json'));
27
- }
28
-
29
- export const IS_WINDOWS = platform() === 'win32';
1
+ import { homedir, platform } from 'os';
2
+ import { join } from 'path';
3
+
4
+ export const REGISTRY_URL =
5
+ 'https://raw.githubusercontent.com/Emmraan/ai-skills/main/packages/skills-registry';
6
+
7
+ export const REGISTRY_INDEX_URL = `${REGISTRY_URL}/skills/.index.json`;
8
+
9
+ export const AGENT_PLATFORMS = ['.claude', '.gemini', '.vscode', '.opencode', '.codex', '.agents'];
10
+
11
+ export function getAgentPlatformDirs(): string[] {
12
+ const home = homedir();
13
+ return AGENT_PLATFORMS.map((p) => join(home, p, 'skills'));
14
+ }
15
+
16
+ export function getLockfilePath(): string {
17
+ const home = homedir();
18
+ return join(home, '.ai-skills', '.skill-lock.json');
19
+ }
20
+
21
+ export function getSkillInstallPaths(skillName: string): string[] {
22
+ return getAgentPlatformDirs().map((dir) => join(dir, skillName, 'SKILLS.md'));
23
+ }
24
+
25
+ export function getSkillMetadataPaths(skillName: string): string[] {
26
+ return getAgentPlatformDirs().map((dir) => join(dir, skillName, '.skill-metadata.json'));
27
+ }
28
+
29
+ export const IS_WINDOWS = platform() === 'win32';
@@ -1,27 +1,27 @@
1
- import { REGISTRY_INDEX_URL, REGISTRY_URL } from './config.js';
2
- import { fetchWithRetry } from '../utils/retry.js';
3
-
4
- export interface SkillIndexEntry {
5
- version: string;
6
- domains: string[];
7
- lastGenerated: string;
8
- }
9
-
10
- export interface SkillIndex {
11
- skills: Record<string, SkillIndexEntry>;
12
- }
13
-
14
- export async function fetchRegistryIndex(): Promise<SkillIndex> {
15
- const content = await fetchWithRetry(REGISTRY_INDEX_URL);
16
- return JSON.parse(content);
17
- }
18
-
19
- export async function fetchSkill(skillName: string): Promise<string> {
20
- const url = `${REGISTRY_URL}/skills/${skillName}/SKILLS.md`;
21
- return fetchWithRetry(url);
22
- }
23
-
24
- export async function getAvailableSkills(): Promise<string[]> {
25
- const index = await fetchRegistryIndex();
26
- return Object.keys(index.skills);
27
- }
1
+ import { REGISTRY_INDEX_URL, REGISTRY_URL } from './config.js';
2
+ import { fetchWithRetry } from '../utils/retry.js';
3
+
4
+ export interface SkillIndexEntry {
5
+ version: string;
6
+ domains: string[];
7
+ lastGenerated: string;
8
+ }
9
+
10
+ export interface SkillIndex {
11
+ skills: Record<string, SkillIndexEntry>;
12
+ }
13
+
14
+ export async function fetchRegistryIndex(): Promise<SkillIndex> {
15
+ const content = await fetchWithRetry(REGISTRY_INDEX_URL);
16
+ return JSON.parse(content);
17
+ }
18
+
19
+ export async function fetchSkill(skillName: string): Promise<string> {
20
+ const url = `${REGISTRY_URL}/skills/${skillName}/SKILLS.md`;
21
+ return fetchWithRetry(url);
22
+ }
23
+
24
+ export async function getAvailableSkills(): Promise<string[]> {
25
+ const index = await fetchRegistryIndex();
26
+ return Object.keys(index.skills);
27
+ }
@@ -1,101 +1,101 @@
1
- import { writeFile, mkdir, readFile, rm } from 'fs/promises';
2
- import { dirname } from 'path';
3
- import { getSkillInstallPaths, getSkillMetadataPaths } from './config.js';
4
- import { sha256 } from '../utils/hash.js';
5
- import { info, warn } from '../utils/logger.js';
6
-
7
- export async function installSkill(skillName: string, skillContent: string): Promise<string[]> {
8
- const installPaths = getSkillInstallPaths(skillName);
9
- const metadataPaths = getSkillMetadataPaths(skillName);
10
- const installedPaths: string[] = [];
11
- const installedMetadataPaths: string[] = [];
12
- const hash = sha256(skillContent);
13
- const installedAt = new Date().toISOString();
14
-
15
- for (let i = 0; i < installPaths.length; i++) {
16
- const skillPath = installPaths[i];
17
- const metadataPath = metadataPaths[i];
18
- try {
19
- const dir = dirname(skillPath);
20
- await mkdir(dir, { recursive: true });
21
- await writeFile(skillPath, skillContent, 'utf-8');
22
- await writeFile(
23
- metadataPath,
24
- JSON.stringify(
25
- {
26
- skill: skillName,
27
- hash,
28
- installedAt,
29
- file: 'SKILLS.md',
30
- },
31
- null,
32
- 2
33
- ),
34
- 'utf-8'
35
- );
36
- installedPaths.push(skillPath);
37
- installedMetadataPaths.push(metadataPath);
38
- info(`Installed to ${skillPath}`);
39
- } catch (err) {
40
- warn(
41
- `Failed to install to ${skillPath}: ${err instanceof Error ? err.message : String(err)}`
42
- );
43
- }
44
- }
45
-
46
- if (installedMetadataPaths.length > 0) {
47
- info(`Wrote metadata to ${installedMetadataPaths.length} location(s)`);
48
- }
49
-
50
- return installedPaths;
51
- }
52
-
53
- export async function removeSkill(skillName: string): Promise<string[]> {
54
- const installPaths = getSkillInstallPaths(skillName);
55
- const metadataPaths = getSkillMetadataPaths(skillName);
56
- const removedPaths: string[] = [];
57
-
58
- for (let i = 0; i < installPaths.length; i++) {
59
- const skillPath = installPaths[i];
60
- const metadataPath = metadataPaths[i];
61
- try {
62
- await rm(skillPath, { force: true });
63
- await rm(metadataPath, { force: true });
64
- removedPaths.push(skillPath);
65
- info(`Removed ${skillPath}`);
66
- } catch (err) {
67
- warn(`Failed to remove ${skillPath}: ${err instanceof Error ? err.message : String(err)}`);
68
- }
69
- }
70
-
71
- return removedPaths;
72
- }
73
-
74
- export async function skillExists(skillName: string): Promise<boolean> {
75
- const installPaths = getSkillInstallPaths(skillName);
76
-
77
- for (const skillPath of installPaths) {
78
- try {
79
- await readFile(skillPath, 'utf-8');
80
- return true;
81
- } catch {
82
- // Continue checking other paths
83
- }
84
- }
85
-
86
- return false;
87
- }
88
-
89
- export async function readSkill(skillName: string): Promise<string | null> {
90
- const installPaths = getSkillInstallPaths(skillName);
91
-
92
- for (const skillPath of installPaths) {
93
- try {
94
- return await readFile(skillPath, 'utf-8');
95
- } catch {
96
- // Continue checking other paths
97
- }
98
- }
99
-
100
- return null;
101
- }
1
+ import { writeFile, mkdir, readFile, rm } from 'fs/promises';
2
+ import { dirname } from 'path';
3
+ import { getSkillInstallPaths, getSkillMetadataPaths } from './config.js';
4
+ import { sha256 } from '../utils/hash.js';
5
+ import { info, warn } from '../utils/logger.js';
6
+
7
+ export async function installSkill(skillName: string, skillContent: string): Promise<string[]> {
8
+ const installPaths = getSkillInstallPaths(skillName);
9
+ const metadataPaths = getSkillMetadataPaths(skillName);
10
+ const installedPaths: string[] = [];
11
+ const installedMetadataPaths: string[] = [];
12
+ const hash = sha256(skillContent);
13
+ const installedAt = new Date().toISOString();
14
+
15
+ for (let i = 0; i < installPaths.length; i++) {
16
+ const skillPath = installPaths[i];
17
+ const metadataPath = metadataPaths[i];
18
+ try {
19
+ const dir = dirname(skillPath);
20
+ await mkdir(dir, { recursive: true });
21
+ await writeFile(skillPath, skillContent, 'utf-8');
22
+ await writeFile(
23
+ metadataPath,
24
+ JSON.stringify(
25
+ {
26
+ skill: skillName,
27
+ hash,
28
+ installedAt,
29
+ file: 'SKILLS.md',
30
+ },
31
+ null,
32
+ 2
33
+ ),
34
+ 'utf-8'
35
+ );
36
+ installedPaths.push(skillPath);
37
+ installedMetadataPaths.push(metadataPath);
38
+ info(`Installed to ${skillPath}`);
39
+ } catch (err) {
40
+ warn(
41
+ `Failed to install to ${skillPath}: ${err instanceof Error ? err.message : String(err)}`
42
+ );
43
+ }
44
+ }
45
+
46
+ if (installedMetadataPaths.length > 0) {
47
+ info(`Wrote metadata to ${installedMetadataPaths.length} location(s)`);
48
+ }
49
+
50
+ return installedPaths;
51
+ }
52
+
53
+ export async function removeSkill(skillName: string): Promise<string[]> {
54
+ const installPaths = getSkillInstallPaths(skillName);
55
+ const metadataPaths = getSkillMetadataPaths(skillName);
56
+ const removedPaths: string[] = [];
57
+
58
+ for (let i = 0; i < installPaths.length; i++) {
59
+ const skillPath = installPaths[i];
60
+ const metadataPath = metadataPaths[i];
61
+ try {
62
+ await rm(skillPath, { force: true });
63
+ await rm(metadataPath, { force: true });
64
+ removedPaths.push(skillPath);
65
+ info(`Removed ${skillPath}`);
66
+ } catch (err) {
67
+ warn(`Failed to remove ${skillPath}: ${err instanceof Error ? err.message : String(err)}`);
68
+ }
69
+ }
70
+
71
+ return removedPaths;
72
+ }
73
+
74
+ export async function skillExists(skillName: string): Promise<boolean> {
75
+ const installPaths = getSkillInstallPaths(skillName);
76
+
77
+ for (const skillPath of installPaths) {
78
+ try {
79
+ await readFile(skillPath, 'utf-8');
80
+ return true;
81
+ } catch {
82
+ // Continue checking other paths
83
+ }
84
+ }
85
+
86
+ return false;
87
+ }
88
+
89
+ export async function readSkill(skillName: string): Promise<string | null> {
90
+ const installPaths = getSkillInstallPaths(skillName);
91
+
92
+ for (const skillPath of installPaths) {
93
+ try {
94
+ return await readFile(skillPath, 'utf-8');
95
+ } catch {
96
+ // Continue checking other paths
97
+ }
98
+ }
99
+
100
+ return null;
101
+ }