@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.
- package/README.md +24 -24
- package/bin/skills.js +3 -3
- package/package.json +34 -34
- package/src/bin/skills.ts +8 -8
- package/src/commands/__tests__/install.test.ts +49 -49
- package/src/commands/generate-local.ts +38 -38
- package/src/commands/install.ts +31 -31
- package/src/commands/list.ts +41 -41
- package/src/commands/remove.ts +22 -22
- package/src/commands/update.ts +69 -69
- package/src/core/__tests__/downloader.test.ts +66 -66
- package/src/core/__tests__/lockfile.test.ts +90 -90
- package/src/core/config.ts +29 -29
- package/src/core/downloader.ts +27 -27
- package/src/core/installer.ts +101 -101
- package/src/core/lockfile.ts +70 -70
- package/src/index.ts +80 -80
- package/src/utils/__tests__/hash.test.ts +22 -22
- package/src/utils/hash.ts +5 -5
- package/src/utils/logger.ts +21 -21
- package/src/utils/retry.ts +20 -20
- package/tsconfig.json +11 -11
- package/vitest.config.ts +13 -13
package/README.md
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
# AI Skills CLI
|
|
2
|
-
|
|
3
|
-
Install framework-agnostic `SKILLS.md` files for AI agents.
|
|
4
|
-
|
|
5
|
-
## Usage
|
|
6
|
-
|
|
7
|
-
- `npx ai-skills react` - Install React skill
|
|
8
|
-
- `npx ai-skills install react` - Install React skill (explicit command)
|
|
9
|
-
- `npx ai-skills list` - List available and installed skills
|
|
10
|
-
- `npx ai-skills update` - Update all installed skills
|
|
11
|
-
- `npx ai-skills remove react` - Remove React skill
|
|
12
|
-
- `npx ai-skills generate-local` - Run local backend generator
|
|
13
|
-
|
|
14
|
-
## Publish to npm
|
|
15
|
-
|
|
16
|
-
This package is the publishable CLI package for the `ai-skills` command.
|
|
17
|
-
|
|
18
|
-
1. Build from workspace root: `pnpm build`
|
|
19
|
-
2. Login to npm: `npm login`
|
|
20
|
-
3. Publish CLI package: `npm publish --access public` (run inside `packages/cli`)
|
|
21
|
-
|
|
22
|
-
After publishing, users can run:
|
|
23
|
-
|
|
24
|
-
- `npx ai-skills list`
|
|
1
|
+
# AI Skills CLI
|
|
2
|
+
|
|
3
|
+
Install framework-agnostic `SKILLS.md` files for AI agents.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
- `npx @emmraan/ai-skills react` - Install React skill
|
|
8
|
+
- `npx @emmraan/ai-skills install react` - Install React skill (explicit command)
|
|
9
|
+
- `npx @emmraan/ai-skills list` - List available and installed skills
|
|
10
|
+
- `npx @emmraan/ai-skills update` - Update all installed skills
|
|
11
|
+
- `npx @emmraan/ai-skills remove react` - Remove React skill
|
|
12
|
+
- `npx @emmraan/ai-skills generate-local` - Run local backend generator
|
|
13
|
+
|
|
14
|
+
## Publish to npm
|
|
15
|
+
|
|
16
|
+
This package is the publishable CLI package for the `ai-skills` command.
|
|
17
|
+
|
|
18
|
+
1. Build from workspace root: `pnpm build`
|
|
19
|
+
2. Login to npm: `npm login`
|
|
20
|
+
3. Publish CLI package: `npm publish --access public` (run inside `packages/cli`)
|
|
21
|
+
|
|
22
|
+
After publishing, users can run:
|
|
23
|
+
|
|
24
|
+
- `npx @emmraan/ai-skills list`
|
package/bin/skills.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import '../dist/bin/skills.js';
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import '../dist/bin/skills.js';
|
package/package.json
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@emmraan/ai-skills",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "CLI tool for installing AI Skills across agent platforms",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"ai-skills": "bin/skills.js",
|
|
8
|
-
"skills": "bin/skills.js"
|
|
9
|
-
},
|
|
10
|
-
"main": "./dist/index.js",
|
|
11
|
-
"types": "./dist/index.d.ts",
|
|
12
|
-
"exports": {
|
|
13
|
-
".": {
|
|
14
|
-
"import": "./dist/index.js",
|
|
15
|
-
"types": "./dist/index.d.ts"
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
|
-
"scripts": {
|
|
19
|
-
"build": "tsc --project tsconfig.json",
|
|
20
|
-
"dev": "tsx src/bin/skills.ts",
|
|
21
|
-
"test": "vitest run",
|
|
22
|
-
"lint": "node -e \"process.exit(0)\"",
|
|
23
|
-
"typecheck": "tsc --noEmit --project tsconfig.json",
|
|
24
|
-
"clean": "rm -rf dist"
|
|
25
|
-
},
|
|
26
|
-
"dependencies": {
|
|
27
|
-
"@ai-skills/shared-types": "
|
|
28
|
-
},
|
|
29
|
-
"devDependencies": {
|
|
30
|
-
"@types/node": "^20.10.0",
|
|
31
|
-
"tsx": "^4.7.0",
|
|
32
|
-
"vitest": "^1.1.0"
|
|
33
|
-
}
|
|
34
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@emmraan/ai-skills",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "CLI tool for installing AI Skills across agent platforms",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ai-skills": "bin/skills.js",
|
|
8
|
+
"skills": "bin/skills.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc --project tsconfig.json",
|
|
20
|
+
"dev": "tsx src/bin/skills.ts",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"lint": "node -e \"process.exit(0)\"",
|
|
23
|
+
"typecheck": "tsc --noEmit --project tsconfig.json",
|
|
24
|
+
"clean": "rm -rf dist"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@ai-skills/shared-types": "^0.0.1"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^20.10.0",
|
|
31
|
+
"tsx": "^4.7.0",
|
|
32
|
+
"vitest": "^1.1.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/bin/skills.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { main } from '../index.js';
|
|
4
|
-
|
|
5
|
-
main().catch((err) => {
|
|
6
|
-
console.error('Fatal error:', err);
|
|
7
|
-
process.exit(1);
|
|
8
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { main } from '../index.js';
|
|
4
|
+
|
|
5
|
+
main().catch((err) => {
|
|
6
|
+
console.error('Fatal error:', err);
|
|
7
|
+
process.exit(1);
|
|
8
|
+
});
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
|
|
3
|
-
vi.mock('../../core/downloader', () => ({
|
|
4
|
-
fetchSkill: vi.fn(),
|
|
5
|
-
fetchRegistryIndex: vi.fn(),
|
|
6
|
-
}));
|
|
7
|
-
|
|
8
|
-
vi.mock('../../core/installer', () => ({
|
|
9
|
-
installSkill: vi.fn(),
|
|
10
|
-
}));
|
|
11
|
-
|
|
12
|
-
vi.mock('../../core/lockfile', () => ({
|
|
13
|
-
addSkillToLockfile: vi.fn(),
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
import { install } from '../../commands/install';
|
|
17
|
-
|
|
18
|
-
describe('install command', () => {
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
vi.clearAllMocks();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('uses registry index version when writing lockfile', async () => {
|
|
24
|
-
const downloader = await import('../../core/downloader');
|
|
25
|
-
const installer = await import('../../core/installer');
|
|
26
|
-
const lockfile = await import('../../core/lockfile');
|
|
27
|
-
|
|
28
|
-
vi.mocked(downloader.fetchSkill).mockResolvedValue('# React Skill');
|
|
29
|
-
vi.mocked(downloader.fetchRegistryIndex).mockResolvedValue({
|
|
30
|
-
skills: {
|
|
31
|
-
react: {
|
|
32
|
-
version: '18.2.0',
|
|
33
|
-
domains: ['frontend'],
|
|
34
|
-
lastGenerated: '2026-02-06T00:00:00Z',
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
vi.mocked(installer.installSkill).mockResolvedValue(['/tmp/.agents/skills/react/SKILLS.md']);
|
|
39
|
-
|
|
40
|
-
const ok = await install('react');
|
|
41
|
-
expect(ok).toBe(true);
|
|
42
|
-
expect(lockfile.addSkillToLockfile).toHaveBeenCalledWith(
|
|
43
|
-
'react',
|
|
44
|
-
'18.2.0',
|
|
45
|
-
expect.stringMatching(/^[a-f0-9]{64}$/),
|
|
46
|
-
['/tmp/.agents/skills/react/SKILLS.md']
|
|
47
|
-
);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
vi.mock('../../core/downloader', () => ({
|
|
4
|
+
fetchSkill: vi.fn(),
|
|
5
|
+
fetchRegistryIndex: vi.fn(),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
vi.mock('../../core/installer', () => ({
|
|
9
|
+
installSkill: vi.fn(),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
vi.mock('../../core/lockfile', () => ({
|
|
13
|
+
addSkillToLockfile: vi.fn(),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
import { install } from '../../commands/install';
|
|
17
|
+
|
|
18
|
+
describe('install command', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
vi.clearAllMocks();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('uses registry index version when writing lockfile', async () => {
|
|
24
|
+
const downloader = await import('../../core/downloader');
|
|
25
|
+
const installer = await import('../../core/installer');
|
|
26
|
+
const lockfile = await import('../../core/lockfile');
|
|
27
|
+
|
|
28
|
+
vi.mocked(downloader.fetchSkill).mockResolvedValue('# React Skill');
|
|
29
|
+
vi.mocked(downloader.fetchRegistryIndex).mockResolvedValue({
|
|
30
|
+
skills: {
|
|
31
|
+
react: {
|
|
32
|
+
version: '18.2.0',
|
|
33
|
+
domains: ['frontend'],
|
|
34
|
+
lastGenerated: '2026-02-06T00:00:00Z',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
vi.mocked(installer.installSkill).mockResolvedValue(['/tmp/.agents/skills/react/SKILLS.md']);
|
|
39
|
+
|
|
40
|
+
const ok = await install('react');
|
|
41
|
+
expect(ok).toBe(true);
|
|
42
|
+
expect(lockfile.addSkillToLockfile).toHaveBeenCalledWith(
|
|
43
|
+
'react',
|
|
44
|
+
'18.2.0',
|
|
45
|
+
expect.stringMatching(/^[a-f0-9]{64}$/),
|
|
46
|
+
['/tmp/.agents/skills/react/SKILLS.md']
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
2
|
-
import { resolve } from 'path';
|
|
3
|
-
import { error, info, success } from '../utils/logger.js';
|
|
4
|
-
|
|
5
|
-
export async function generateLocal(skill?: string): Promise<boolean> {
|
|
6
|
-
const backendDir = resolve(process.cwd(), 'backend');
|
|
7
|
-
const command = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
|
|
8
|
-
const args = ['run', 'generate'];
|
|
9
|
-
|
|
10
|
-
if (skill) {
|
|
11
|
-
args.push('--', '--skill', skill);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
info(`Running local generator in ${backendDir}...`);
|
|
15
|
-
|
|
16
|
-
return new Promise((resolvePromise) => {
|
|
17
|
-
const child = spawn(command, args, {
|
|
18
|
-
cwd: backendDir,
|
|
19
|
-
stdio: 'inherit',
|
|
20
|
-
shell: false,
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
child.on('error', (err) => {
|
|
24
|
-
error(`Failed to start local generator: ${err.message}`);
|
|
25
|
-
resolvePromise(false);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
child.on('exit', (code) => {
|
|
29
|
-
if (code === 0) {
|
|
30
|
-
success('Local generation completed successfully');
|
|
31
|
-
resolvePromise(true);
|
|
32
|
-
} else {
|
|
33
|
-
error(`Local generation failed with exit code ${code ?? -1}`);
|
|
34
|
-
resolvePromise(false);
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
}
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
import { error, info, success } from '../utils/logger.js';
|
|
4
|
+
|
|
5
|
+
export async function generateLocal(skill?: string): Promise<boolean> {
|
|
6
|
+
const backendDir = resolve(process.cwd(), 'backend');
|
|
7
|
+
const command = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
|
|
8
|
+
const args = ['run', 'generate'];
|
|
9
|
+
|
|
10
|
+
if (skill) {
|
|
11
|
+
args.push('--', '--skill', skill);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
info(`Running local generator in ${backendDir}...`);
|
|
15
|
+
|
|
16
|
+
return new Promise((resolvePromise) => {
|
|
17
|
+
const child = spawn(command, args, {
|
|
18
|
+
cwd: backendDir,
|
|
19
|
+
stdio: 'inherit',
|
|
20
|
+
shell: false,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
child.on('error', (err) => {
|
|
24
|
+
error(`Failed to start local generator: ${err.message}`);
|
|
25
|
+
resolvePromise(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
child.on('exit', (code) => {
|
|
29
|
+
if (code === 0) {
|
|
30
|
+
success('Local generation completed successfully');
|
|
31
|
+
resolvePromise(true);
|
|
32
|
+
} else {
|
|
33
|
+
error(`Local generation failed with exit code ${code ?? -1}`);
|
|
34
|
+
resolvePromise(false);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
package/src/commands/install.ts
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import { fetchSkill, fetchRegistryIndex } from '../core/downloader.js';
|
|
2
|
-
import { installSkill } from '../core/installer.js';
|
|
3
|
-
import { addSkillToLockfile } from '../core/lockfile.js';
|
|
4
|
-
import { sha256 } from '../utils/hash.js';
|
|
5
|
-
import { error, success, info } from '../utils/logger.js';
|
|
6
|
-
|
|
7
|
-
export async function install(skillName: string): Promise<boolean> {
|
|
8
|
-
try {
|
|
9
|
-
info(`Fetching ${skillName}...`);
|
|
10
|
-
const skillContent = await fetchSkill(skillName);
|
|
11
|
-
const hash = sha256(skillContent);
|
|
12
|
-
|
|
13
|
-
info(`Installing ${skillName}...`);
|
|
14
|
-
const installPaths = await installSkill(skillName, skillContent);
|
|
15
|
-
|
|
16
|
-
if (installPaths.length === 0) {
|
|
17
|
-
error(`Failed to install ${skillName} to any location`);
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const index = await fetchRegistryIndex();
|
|
22
|
-
const skillVersion = index.skills[skillName]?.version || 'unknown';
|
|
23
|
-
|
|
24
|
-
await addSkillToLockfile(skillName, skillVersion, hash, installPaths);
|
|
25
|
-
success(`Installed ${skillName} (${skillVersion}) to ${installPaths.length} location(s)`);
|
|
26
|
-
return true;
|
|
27
|
-
} catch (err) {
|
|
28
|
-
error(`Failed to install ${skillName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
1
|
+
import { fetchSkill, fetchRegistryIndex } from '../core/downloader.js';
|
|
2
|
+
import { installSkill } from '../core/installer.js';
|
|
3
|
+
import { addSkillToLockfile } from '../core/lockfile.js';
|
|
4
|
+
import { sha256 } from '../utils/hash.js';
|
|
5
|
+
import { error, success, info } from '../utils/logger.js';
|
|
6
|
+
|
|
7
|
+
export async function install(skillName: string): Promise<boolean> {
|
|
8
|
+
try {
|
|
9
|
+
info(`Fetching ${skillName}...`);
|
|
10
|
+
const skillContent = await fetchSkill(skillName);
|
|
11
|
+
const hash = sha256(skillContent);
|
|
12
|
+
|
|
13
|
+
info(`Installing ${skillName}...`);
|
|
14
|
+
const installPaths = await installSkill(skillName, skillContent);
|
|
15
|
+
|
|
16
|
+
if (installPaths.length === 0) {
|
|
17
|
+
error(`Failed to install ${skillName} to any location`);
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const index = await fetchRegistryIndex();
|
|
22
|
+
const skillVersion = index.skills[skillName]?.version || 'unknown';
|
|
23
|
+
|
|
24
|
+
await addSkillToLockfile(skillName, skillVersion, hash, installPaths);
|
|
25
|
+
success(`Installed ${skillName} (${skillVersion}) to ${installPaths.length} location(s)`);
|
|
26
|
+
return true;
|
|
27
|
+
} catch (err) {
|
|
28
|
+
error(`Failed to install ${skillName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/commands/list.ts
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
import { getAvailableSkills } from '../core/downloader.js';
|
|
2
|
-
import { getInstalledSkills } from '../core/lockfile.js';
|
|
3
|
-
import { log } from '../utils/logger.js';
|
|
4
|
-
|
|
5
|
-
export async function list(json: boolean = false): Promise<void> {
|
|
6
|
-
const installedSkills = await getInstalledSkills();
|
|
7
|
-
const availableSkills = await getAvailableSkills();
|
|
8
|
-
|
|
9
|
-
const installed = installedSkills.map((s) => s.name);
|
|
10
|
-
|
|
11
|
-
if (json) {
|
|
12
|
-
const output = {
|
|
13
|
-
installed,
|
|
14
|
-
available: availableSkills,
|
|
15
|
-
summary: {
|
|
16
|
-
installedCount: installed.length,
|
|
17
|
-
availableCount: availableSkills.length,
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
log(JSON.stringify(output, null, 2));
|
|
21
|
-
} else {
|
|
22
|
-
log('\n📦 Installed Skills:');
|
|
23
|
-
if (installed.length === 0) {
|
|
24
|
-
log(' (none)');
|
|
25
|
-
} else {
|
|
26
|
-
installed.forEach((name) => {
|
|
27
|
-
const entry = installedSkills.find((s) => s.name === name);
|
|
28
|
-
log(` • ${name} (v${entry?.version || 'unknown'})`);
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
log('\n🌐 Available Skills:');
|
|
33
|
-
availableSkills.forEach((name) => {
|
|
34
|
-
const isInstalled = installed.includes(name);
|
|
35
|
-
const status = isInstalled ? ' [installed]' : '';
|
|
36
|
-
log(` • ${name}${status}`);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
log(`\nTotal: ${installed.length} installed, ${availableSkills.length} available\n`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
1
|
+
import { getAvailableSkills } from '../core/downloader.js';
|
|
2
|
+
import { getInstalledSkills } from '../core/lockfile.js';
|
|
3
|
+
import { log } from '../utils/logger.js';
|
|
4
|
+
|
|
5
|
+
export async function list(json: boolean = false): Promise<void> {
|
|
6
|
+
const installedSkills = await getInstalledSkills();
|
|
7
|
+
const availableSkills = await getAvailableSkills();
|
|
8
|
+
|
|
9
|
+
const installed = installedSkills.map((s) => s.name);
|
|
10
|
+
|
|
11
|
+
if (json) {
|
|
12
|
+
const output = {
|
|
13
|
+
installed,
|
|
14
|
+
available: availableSkills,
|
|
15
|
+
summary: {
|
|
16
|
+
installedCount: installed.length,
|
|
17
|
+
availableCount: availableSkills.length,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
log(JSON.stringify(output, null, 2));
|
|
21
|
+
} else {
|
|
22
|
+
log('\n📦 Installed Skills:');
|
|
23
|
+
if (installed.length === 0) {
|
|
24
|
+
log(' (none)');
|
|
25
|
+
} else {
|
|
26
|
+
installed.forEach((name) => {
|
|
27
|
+
const entry = installedSkills.find((s) => s.name === name);
|
|
28
|
+
log(` • ${name} (v${entry?.version || 'unknown'})`);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
log('\n🌐 Available Skills:');
|
|
33
|
+
availableSkills.forEach((name) => {
|
|
34
|
+
const isInstalled = installed.includes(name);
|
|
35
|
+
const status = isInstalled ? ' [installed]' : '';
|
|
36
|
+
log(` • ${name}${status}`);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
log(`\nTotal: ${installed.length} installed, ${availableSkills.length} available\n`);
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/commands/remove.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { removeSkill } from '../core/installer.js';
|
|
2
|
-
import { removeSkillFromLockfile, getSkillEntry } from '../core/lockfile.js';
|
|
3
|
-
import { error, success, info } from '../utils/logger.js';
|
|
4
|
-
|
|
5
|
-
export async function remove(skillName: string): Promise<boolean> {
|
|
6
|
-
try {
|
|
7
|
-
const entry = await getSkillEntry(skillName);
|
|
8
|
-
if (!entry) {
|
|
9
|
-
info(`${skillName} is not installed`);
|
|
10
|
-
return true;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
info(`Removing ${skillName}...`);
|
|
14
|
-
await removeSkill(skillName);
|
|
15
|
-
await removeSkillFromLockfile(skillName);
|
|
16
|
-
success(`Removed ${skillName}`);
|
|
17
|
-
return true;
|
|
18
|
-
} catch (err) {
|
|
19
|
-
error(`Failed to remove ${skillName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
1
|
+
import { removeSkill } from '../core/installer.js';
|
|
2
|
+
import { removeSkillFromLockfile, getSkillEntry } from '../core/lockfile.js';
|
|
3
|
+
import { error, success, info } from '../utils/logger.js';
|
|
4
|
+
|
|
5
|
+
export async function remove(skillName: string): Promise<boolean> {
|
|
6
|
+
try {
|
|
7
|
+
const entry = await getSkillEntry(skillName);
|
|
8
|
+
if (!entry) {
|
|
9
|
+
info(`${skillName} is not installed`);
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
info(`Removing ${skillName}...`);
|
|
14
|
+
await removeSkill(skillName);
|
|
15
|
+
await removeSkillFromLockfile(skillName);
|
|
16
|
+
success(`Removed ${skillName}`);
|
|
17
|
+
return true;
|
|
18
|
+
} catch (err) {
|
|
19
|
+
error(`Failed to remove ${skillName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
package/src/commands/update.ts
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
import { fetchSkill, fetchRegistryIndex } from '../core/downloader.js';
|
|
2
|
-
import { installSkill } from '../core/installer.js';
|
|
3
|
-
import { addSkillToLockfile, getInstalledSkills, getSkillEntry } from '../core/lockfile.js';
|
|
4
|
-
import { sha256 } from '../utils/hash.js';
|
|
5
|
-
import { error, success, info, warn } from '../utils/logger.js';
|
|
6
|
-
|
|
7
|
-
export async function update(options: { force?: boolean; skill?: string } = {}): Promise<boolean> {
|
|
8
|
-
try {
|
|
9
|
-
const installed = await getInstalledSkills();
|
|
10
|
-
const index = await fetchRegistryIndex();
|
|
11
|
-
|
|
12
|
-
if (options.skill) {
|
|
13
|
-
// Update specific skill
|
|
14
|
-
const skillName = options.skill;
|
|
15
|
-
const entry = await getSkillEntry(skillName);
|
|
16
|
-
|
|
17
|
-
if (!entry) {
|
|
18
|
-
warn(`${skillName} is not installed`);
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const skillContent = await fetchSkill(skillName);
|
|
23
|
-
const newHash = sha256(skillContent);
|
|
24
|
-
|
|
25
|
-
if (newHash === entry.hash && !options.force) {
|
|
26
|
-
info(`${skillName} is already up-to-date`);
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
info(`Updating ${skillName}...`);
|
|
31
|
-
const installPaths = await installSkill(skillName, skillContent);
|
|
32
|
-
const version = index.skills[skillName]?.version || 'unknown';
|
|
33
|
-
await addSkillToLockfile(skillName, version, newHash, installPaths);
|
|
34
|
-
success(`Updated ${skillName}`);
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Update all installed skills
|
|
39
|
-
let updateCount = 0;
|
|
40
|
-
for (const entry of installed) {
|
|
41
|
-
try {
|
|
42
|
-
const skillContent = await fetchSkill(entry.name);
|
|
43
|
-
const newHash = sha256(skillContent);
|
|
44
|
-
|
|
45
|
-
if (newHash === entry.hash && !options.force) {
|
|
46
|
-
info(`${entry.name} is already up-to-date`);
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
info(`Updating ${entry.name}...`);
|
|
51
|
-
const installPaths = await installSkill(entry.name, skillContent);
|
|
52
|
-
const version = index.skills[entry.name]?.version || 'unknown';
|
|
53
|
-
await addSkillToLockfile(entry.name, version, newHash, installPaths);
|
|
54
|
-
success(`Updated ${entry.name}`);
|
|
55
|
-
updateCount++;
|
|
56
|
-
} catch (err) {
|
|
57
|
-
error(
|
|
58
|
-
`Failed to update ${entry.name}: ${err instanceof Error ? err.message : String(err)}`
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
success(`Updated ${updateCount} skill(s)`);
|
|
64
|
-
return true;
|
|
65
|
-
} catch (err) {
|
|
66
|
-
error(`Update failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
1
|
+
import { fetchSkill, fetchRegistryIndex } from '../core/downloader.js';
|
|
2
|
+
import { installSkill } from '../core/installer.js';
|
|
3
|
+
import { addSkillToLockfile, getInstalledSkills, getSkillEntry } from '../core/lockfile.js';
|
|
4
|
+
import { sha256 } from '../utils/hash.js';
|
|
5
|
+
import { error, success, info, warn } from '../utils/logger.js';
|
|
6
|
+
|
|
7
|
+
export async function update(options: { force?: boolean; skill?: string } = {}): Promise<boolean> {
|
|
8
|
+
try {
|
|
9
|
+
const installed = await getInstalledSkills();
|
|
10
|
+
const index = await fetchRegistryIndex();
|
|
11
|
+
|
|
12
|
+
if (options.skill) {
|
|
13
|
+
// Update specific skill
|
|
14
|
+
const skillName = options.skill;
|
|
15
|
+
const entry = await getSkillEntry(skillName);
|
|
16
|
+
|
|
17
|
+
if (!entry) {
|
|
18
|
+
warn(`${skillName} is not installed`);
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const skillContent = await fetchSkill(skillName);
|
|
23
|
+
const newHash = sha256(skillContent);
|
|
24
|
+
|
|
25
|
+
if (newHash === entry.hash && !options.force) {
|
|
26
|
+
info(`${skillName} is already up-to-date`);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
info(`Updating ${skillName}...`);
|
|
31
|
+
const installPaths = await installSkill(skillName, skillContent);
|
|
32
|
+
const version = index.skills[skillName]?.version || 'unknown';
|
|
33
|
+
await addSkillToLockfile(skillName, version, newHash, installPaths);
|
|
34
|
+
success(`Updated ${skillName}`);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Update all installed skills
|
|
39
|
+
let updateCount = 0;
|
|
40
|
+
for (const entry of installed) {
|
|
41
|
+
try {
|
|
42
|
+
const skillContent = await fetchSkill(entry.name);
|
|
43
|
+
const newHash = sha256(skillContent);
|
|
44
|
+
|
|
45
|
+
if (newHash === entry.hash && !options.force) {
|
|
46
|
+
info(`${entry.name} is already up-to-date`);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
info(`Updating ${entry.name}...`);
|
|
51
|
+
const installPaths = await installSkill(entry.name, skillContent);
|
|
52
|
+
const version = index.skills[entry.name]?.version || 'unknown';
|
|
53
|
+
await addSkillToLockfile(entry.name, version, newHash, installPaths);
|
|
54
|
+
success(`Updated ${entry.name}`);
|
|
55
|
+
updateCount++;
|
|
56
|
+
} catch (err) {
|
|
57
|
+
error(
|
|
58
|
+
`Failed to update ${entry.name}: ${err instanceof Error ? err.message : String(err)}`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
success(`Updated ${updateCount} skill(s)`);
|
|
64
|
+
return true;
|
|
65
|
+
} catch (err) {
|
|
66
|
+
error(`Update failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|