@emmraan/ai-skills 0.0.1 → 0.0.3

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,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
+ }
@@ -1,70 +1,70 @@
1
- import { readFile, writeFile, mkdir } from 'fs/promises';
2
- import { dirname } from 'path';
3
- import { getLockfilePath } from './config.js';
4
-
5
- export interface LockfileEntry {
6
- name: string;
7
- version: string;
8
- hash: string;
9
- timestamp: string;
10
- installPaths: string[];
11
- }
12
-
13
- export interface Lockfile {
14
- version: 1;
15
- installedSkills: Record<string, LockfileEntry>;
16
- }
17
-
18
- const INITIAL_LOCKFILE: Lockfile = {
19
- version: 1,
20
- installedSkills: {},
21
- };
22
-
23
- export async function readLockfile(): Promise<Lockfile> {
24
- const path = getLockfilePath();
25
- try {
26
- const content = await readFile(path, 'utf-8');
27
- return JSON.parse(content);
28
- } catch {
29
- return INITIAL_LOCKFILE;
30
- }
31
- }
32
-
33
- export async function writeLockfile(lockfile: Lockfile): Promise<void> {
34
- const path = getLockfilePath();
35
- await mkdir(dirname(path), { recursive: true });
36
- await writeFile(path, JSON.stringify(lockfile, null, 2), 'utf-8');
37
- }
38
-
39
- export async function addSkillToLockfile(
40
- skillName: string,
41
- version: string,
42
- hash: string,
43
- installPaths: string[]
44
- ): Promise<void> {
45
- const lockfile = await readLockfile();
46
- lockfile.installedSkills[skillName] = {
47
- name: skillName,
48
- version,
49
- hash,
50
- timestamp: new Date().toISOString(),
51
- installPaths,
52
- };
53
- await writeLockfile(lockfile);
54
- }
55
-
56
- export async function removeSkillFromLockfile(skillName: string): Promise<void> {
57
- const lockfile = await readLockfile();
58
- delete lockfile.installedSkills[skillName];
59
- await writeLockfile(lockfile);
60
- }
61
-
62
- export async function getInstalledSkills(): Promise<LockfileEntry[]> {
63
- const lockfile = await readLockfile();
64
- return Object.values(lockfile.installedSkills);
65
- }
66
-
67
- export async function getSkillEntry(skillName: string): Promise<LockfileEntry | null> {
68
- const lockfile = await readLockfile();
69
- return lockfile.installedSkills[skillName] || null;
70
- }
1
+ import { readFile, writeFile, mkdir } from 'fs/promises';
2
+ import { dirname } from 'path';
3
+ import { getLockfilePath } from './config.js';
4
+
5
+ export interface LockfileEntry {
6
+ name: string;
7
+ version: string;
8
+ hash: string;
9
+ timestamp: string;
10
+ installPaths: string[];
11
+ }
12
+
13
+ export interface Lockfile {
14
+ version: 1;
15
+ installedSkills: Record<string, LockfileEntry>;
16
+ }
17
+
18
+ const INITIAL_LOCKFILE: Lockfile = {
19
+ version: 1,
20
+ installedSkills: {},
21
+ };
22
+
23
+ export async function readLockfile(): Promise<Lockfile> {
24
+ const path = getLockfilePath();
25
+ try {
26
+ const content = await readFile(path, 'utf-8');
27
+ return JSON.parse(content);
28
+ } catch {
29
+ return INITIAL_LOCKFILE;
30
+ }
31
+ }
32
+
33
+ export async function writeLockfile(lockfile: Lockfile): Promise<void> {
34
+ const path = getLockfilePath();
35
+ await mkdir(dirname(path), { recursive: true });
36
+ await writeFile(path, JSON.stringify(lockfile, null, 2), 'utf-8');
37
+ }
38
+
39
+ export async function addSkillToLockfile(
40
+ skillName: string,
41
+ version: string,
42
+ hash: string,
43
+ installPaths: string[]
44
+ ): Promise<void> {
45
+ const lockfile = await readLockfile();
46
+ lockfile.installedSkills[skillName] = {
47
+ name: skillName,
48
+ version,
49
+ hash,
50
+ timestamp: new Date().toISOString(),
51
+ installPaths,
52
+ };
53
+ await writeLockfile(lockfile);
54
+ }
55
+
56
+ export async function removeSkillFromLockfile(skillName: string): Promise<void> {
57
+ const lockfile = await readLockfile();
58
+ delete lockfile.installedSkills[skillName];
59
+ await writeLockfile(lockfile);
60
+ }
61
+
62
+ export async function getInstalledSkills(): Promise<LockfileEntry[]> {
63
+ const lockfile = await readLockfile();
64
+ return Object.values(lockfile.installedSkills);
65
+ }
66
+
67
+ export async function getSkillEntry(skillName: string): Promise<LockfileEntry | null> {
68
+ const lockfile = await readLockfile();
69
+ return lockfile.installedSkills[skillName] || null;
70
+ }
package/src/index.ts CHANGED
@@ -1,80 +1,80 @@
1
- import { install } from './commands/install.js';
2
- import { remove } from './commands/remove.js';
3
- import { list } from './commands/list.js';
4
- import { update } from './commands/update.js';
5
- import { generateLocal } from './commands/generate-local.js';
6
- import { error, info } from './utils/logger.js';
7
-
8
- const args = process.argv.slice(2);
9
-
10
- export async function main(): Promise<void> {
11
- if (args.length === 0) {
12
- showHelp();
13
- return;
14
- }
15
-
16
- const command = args[0];
17
-
18
- try {
19
- if (command === 'install' && args.length >= 2) {
20
- const skill = args[1];
21
- const success = await install(skill);
22
- process.exit(success ? 0 : 1);
23
- } else if (command === 'remove' && args.length >= 2) {
24
- const skill = args[1];
25
- const success = await remove(skill);
26
- process.exit(success ? 0 : 1);
27
- } else if (command === 'list') {
28
- const jsonFlag = args.includes('--json');
29
- await list(jsonFlag);
30
- process.exit(0);
31
- } else if (command === 'update') {
32
- const forceFlag = args.includes('--force');
33
- let skillArg: string | undefined;
34
- const skillIndex = args.indexOf('--skill');
35
- if (skillIndex !== -1 && args.length > skillIndex + 1) {
36
- skillArg = args[skillIndex + 1];
37
- }
38
- const success = await update({ force: forceFlag, skill: skillArg });
39
- process.exit(success ? 0 : 1);
40
- } else if (command === 'generate-local') {
41
- const skillArg = args[1];
42
- const success = await generateLocal(skillArg);
43
- process.exit(success ? 0 : 1);
44
- } else if (!command.startsWith('-')) {
45
- // Default behavior: treat first arg as skill name to install
46
- const success = await install(command);
47
- process.exit(success ? 0 : 1);
48
- } else {
49
- showHelp();
50
- }
51
- } catch (err) {
52
- error(`CLI error: ${err instanceof Error ? err.message : String(err)}`);
53
- process.exit(1);
54
- }
55
- }
56
-
57
- function showHelp() {
58
- info(`
59
- AI Skills CLI - Install framework-agnostic SKILLS.md files
60
-
61
- Usage:
62
- ai-skills <skill> Install a skill
63
- ai-skills install <skill> Install a skill
64
- ai-skills list [--json] List installed and available skills
65
- ai-skills remove <skill> Remove an installed skill
66
- ai-skills update [--force] Update all installed skills
67
- [--skill <name>] Update a specific skill
68
- ai-skills generate-local [skill] Run local backend generator
69
-
70
- Examples:
71
- ai-skills react
72
- ai-skills install typescript
73
- ai-skills list
74
- ai-skills remove python
75
- ai-skills update --force
76
- ai-skills update --skill react
77
- ai-skills generate-local
78
- ai-skills generate-local react
79
- `);
80
- }
1
+ import { install } from './commands/install.js';
2
+ import { remove } from './commands/remove.js';
3
+ import { list } from './commands/list.js';
4
+ import { update } from './commands/update.js';
5
+ import { generateLocal } from './commands/generate-local.js';
6
+ import { error, info } from './utils/logger.js';
7
+
8
+ const args = process.argv.slice(2);
9
+
10
+ export async function main(): Promise<void> {
11
+ if (args.length === 0) {
12
+ showHelp();
13
+ return;
14
+ }
15
+
16
+ const command = args[0];
17
+
18
+ try {
19
+ if (command === 'install' && args.length >= 2) {
20
+ const skill = args[1];
21
+ const success = await install(skill);
22
+ process.exit(success ? 0 : 1);
23
+ } else if (command === 'remove' && args.length >= 2) {
24
+ const skill = args[1];
25
+ const success = await remove(skill);
26
+ process.exit(success ? 0 : 1);
27
+ } else if (command === 'list') {
28
+ const jsonFlag = args.includes('--json');
29
+ await list(jsonFlag);
30
+ process.exit(0);
31
+ } else if (command === 'update') {
32
+ const forceFlag = args.includes('--force');
33
+ let skillArg: string | undefined;
34
+ const skillIndex = args.indexOf('--skill');
35
+ if (skillIndex !== -1 && args.length > skillIndex + 1) {
36
+ skillArg = args[skillIndex + 1];
37
+ }
38
+ const success = await update({ force: forceFlag, skill: skillArg });
39
+ process.exit(success ? 0 : 1);
40
+ } else if (command === 'generate-local') {
41
+ const skillArg = args[1];
42
+ const success = await generateLocal(skillArg);
43
+ process.exit(success ? 0 : 1);
44
+ } else if (!command.startsWith('-')) {
45
+ // Default behavior: treat first arg as skill name to install
46
+ const success = await install(command);
47
+ process.exit(success ? 0 : 1);
48
+ } else {
49
+ showHelp();
50
+ }
51
+ } catch (err) {
52
+ error(`CLI error: ${err instanceof Error ? err.message : String(err)}`);
53
+ process.exit(1);
54
+ }
55
+ }
56
+
57
+ function showHelp() {
58
+ info(`
59
+ AI Skills CLI - Install framework-agnostic SKILLS.md files
60
+
61
+ Usage:
62
+ ai-skills <skill> Install a skill
63
+ ai-skills install <skill> Install a skill
64
+ ai-skills list [--json] List installed and available skills
65
+ ai-skills remove <skill> Remove an installed skill
66
+ ai-skills update [--force] Update all installed skills
67
+ [--skill <name>] Update a specific skill
68
+ ai-skills generate-local [skill] Run local backend generator
69
+
70
+ Examples:
71
+ ai-skills react
72
+ ai-skills install typescript
73
+ ai-skills list
74
+ ai-skills remove python
75
+ ai-skills update --force
76
+ ai-skills update --skill react
77
+ ai-skills generate-local
78
+ ai-skills generate-local react
79
+ `);
80
+ }
@@ -1,22 +1,22 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { sha256 } from '../../utils/hash';
3
-
4
- describe('hash', () => {
5
- it('should hash a string consistently', () => {
6
- const content = 'test content';
7
- const hash1 = sha256(content);
8
- const hash2 = sha256(content);
9
- expect(hash1).toBe(hash2);
10
- });
11
-
12
- it('should produce different hashes for different content', () => {
13
- const hash1 = sha256('content1');
14
- const hash2 = sha256('content2');
15
- expect(hash1).not.toBe(hash2);
16
- });
17
-
18
- it('should produce valid sha256 hash (64 hex characters)', () => {
19
- const hash = sha256('test');
20
- expect(hash).toMatch(/^[a-f0-9]{64}$/);
21
- });
22
- });
1
+ import { describe, it, expect } from 'vitest';
2
+ import { sha256 } from '../../utils/hash';
3
+
4
+ describe('hash', () => {
5
+ it('should hash a string consistently', () => {
6
+ const content = 'test content';
7
+ const hash1 = sha256(content);
8
+ const hash2 = sha256(content);
9
+ expect(hash1).toBe(hash2);
10
+ });
11
+
12
+ it('should produce different hashes for different content', () => {
13
+ const hash1 = sha256('content1');
14
+ const hash2 = sha256('content2');
15
+ expect(hash1).not.toBe(hash2);
16
+ });
17
+
18
+ it('should produce valid sha256 hash (64 hex characters)', () => {
19
+ const hash = sha256('test');
20
+ expect(hash).toMatch(/^[a-f0-9]{64}$/);
21
+ });
22
+ });
package/src/utils/hash.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { createHash } from 'crypto';
2
-
3
- export function sha256(content: string): string {
4
- return createHash('sha256').update(content).digest('hex');
5
- }
1
+ import { createHash } from 'crypto';
2
+
3
+ export function sha256(content: string): string {
4
+ return createHash('sha256').update(content).digest('hex');
5
+ }
@@ -1,21 +1,21 @@
1
- /* eslint-disable no-console */
2
-
3
- export function log(message: string) {
4
- console.log(message);
5
- }
6
-
7
- export function info(message: string) {
8
- console.log(`ℹ ${message}`);
9
- }
10
-
11
- export function success(message: string) {
12
- console.log(`✓ ${message}`);
13
- }
14
-
15
- export function error(message: string) {
16
- console.error(`✗ ${message}`);
17
- }
18
-
19
- export function warn(message: string) {
20
- console.warn(`⚠ ${message}`);
21
- }
1
+ /* eslint-disable no-console */
2
+
3
+ export function log(message: string) {
4
+ console.log(message);
5
+ }
6
+
7
+ export function info(message: string) {
8
+ console.log(`ℹ ${message}`);
9
+ }
10
+
11
+ export function success(message: string) {
12
+ console.log(`✓ ${message}`);
13
+ }
14
+
15
+ export function error(message: string) {
16
+ console.error(`✗ ${message}`);
17
+ }
18
+
19
+ export function warn(message: string) {
20
+ console.warn(`⚠ ${message}`);
21
+ }
@@ -1,20 +1,20 @@
1
- export async function fetchWithRetry(url: string, maxRetries: number = 3): Promise<string> {
2
- let lastError: Error | null = null;
3
-
4
- for (let i = 0; i < maxRetries; i++) {
5
- try {
6
- const response = await fetch(url);
7
- if (!response.ok) {
8
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
9
- }
10
- return await response.text();
11
- } catch (err) {
12
- lastError = err instanceof Error ? err : new Error(String(err));
13
- if (i < maxRetries - 1) {
14
- await new Promise((resolve) => setTimeout(resolve, Math.pow(2, i) * 1000));
15
- }
16
- }
17
- }
18
-
19
- throw lastError || new Error('Failed to fetch after retries');
20
- }
1
+ export async function fetchWithRetry(url: string, maxRetries: number = 3): Promise<string> {
2
+ let lastError: Error | null = null;
3
+
4
+ for (let i = 0; i < maxRetries; i++) {
5
+ try {
6
+ const response = await fetch(url);
7
+ if (!response.ok) {
8
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
9
+ }
10
+ return await response.text();
11
+ } catch (err) {
12
+ lastError = err instanceof Error ? err : new Error(String(err));
13
+ if (i < maxRetries - 1) {
14
+ await new Promise((resolve) => setTimeout(resolve, Math.pow(2, i) * 1000));
15
+ }
16
+ }
17
+ }
18
+
19
+ throw lastError || new Error('Failed to fetch after retries');
20
+ }
package/tsconfig.json CHANGED
@@ -1,11 +1,11 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src",
6
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
7
- "types": ["node"]
8
- },
9
- "include": ["src"],
10
- "exclude": ["node_modules", "dist", "**/*.test.ts"]
11
- }
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
7
+ "types": ["node"]
8
+ },
9
+ "include": ["src"],
10
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
11
+ }