agentskillsdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +2 -0
- package/package.json +34 -0
- package/src/commands/add.js +62 -0
- package/src/commands/list.js +28 -0
- package/src/index.js +23 -0
- package/src/lib/api.js +25 -0
- package/src/lib/detect-agent.js +34 -0
- package/src/lib/download.js +61 -0
package/bin/cli.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agentskillsdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Install agent skills from agentskills.dk",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"agentskillsdk": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"src/"
|
|
12
|
+
],
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"commander": "^13.0.0",
|
|
15
|
+
"chalk": "^5.4.0",
|
|
16
|
+
"tar-stream": "^3.1.0"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"agent",
|
|
23
|
+
"skills",
|
|
24
|
+
"cli",
|
|
25
|
+
"claude",
|
|
26
|
+
"ai"
|
|
27
|
+
],
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/agentskillsdk/cli"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://agentskills.dk"
|
|
34
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { fetchSkill, fetchSkills } from '../lib/api.js';
|
|
4
|
+
import { detectAgents } from '../lib/detect-agent.js';
|
|
5
|
+
import { downloadSkill } from '../lib/download.js';
|
|
6
|
+
|
|
7
|
+
export async function addCommand(skillName) {
|
|
8
|
+
const cwd = process.cwd();
|
|
9
|
+
|
|
10
|
+
// 1. Look up skill
|
|
11
|
+
let skill;
|
|
12
|
+
try {
|
|
13
|
+
skill = await fetchSkill(skillName);
|
|
14
|
+
} catch (err) {
|
|
15
|
+
console.error(chalk.red(`\n ${err.message}\n`));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!skill) {
|
|
20
|
+
let suggestions = [];
|
|
21
|
+
try {
|
|
22
|
+
const allSkills = await fetchSkills();
|
|
23
|
+
suggestions = allSkills
|
|
24
|
+
.filter(s => s.name.includes(skillName) || skillName.includes(s.name))
|
|
25
|
+
.slice(0, 3);
|
|
26
|
+
} catch {
|
|
27
|
+
// If we can't fetch suggestions, just show the not-found message
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.error(chalk.red(`\n Skill "${skillName}" not found.\n`));
|
|
31
|
+
if (suggestions.length > 0) {
|
|
32
|
+
console.error(' Did you mean:');
|
|
33
|
+
suggestions.forEach(s => console.error(` - ${chalk.bold(s.name)}`));
|
|
34
|
+
console.error('');
|
|
35
|
+
}
|
|
36
|
+
console.error(` Browse all skills: ${chalk.underline('https://agentskills.dk/skills')}\n`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(chalk.green(' \u2713') + ` Found skill: ${chalk.bold(skill.name)}`);
|
|
41
|
+
|
|
42
|
+
// 2. Detect agent
|
|
43
|
+
const agents = await detectAgents(cwd);
|
|
44
|
+
console.log(chalk.green(' \u2713') + ` Detected agent: ${chalk.bold(agents[0].name)}`);
|
|
45
|
+
|
|
46
|
+
// 3. Download + install for each selected agent
|
|
47
|
+
for (const agent of agents) {
|
|
48
|
+
const destDir = join(cwd, agent.path(skillName));
|
|
49
|
+
try {
|
|
50
|
+
await downloadSkill(skill, destDir);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error(chalk.red(`\n Failed to download skill files. Try again or report at agentskills.dk/support`));
|
|
53
|
+
console.error(chalk.red(` ${err.message}\n`));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
console.log(chalk.green(' \u2713') + ` Installed to ${agent.path(skillName)}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 4. Done
|
|
60
|
+
console.log(`\n Skill ready. Start a new ${agents[0].name} session to use it.`);
|
|
61
|
+
console.log(` Learn more: ${chalk.underline(`https://agentskills.dk/skills/${skill.namespace}`)}\n`);
|
|
62
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { fetchSkills } from '../lib/api.js';
|
|
3
|
+
|
|
4
|
+
export async function listCommand() {
|
|
5
|
+
let skills;
|
|
6
|
+
try {
|
|
7
|
+
skills = await fetchSkills();
|
|
8
|
+
} catch (err) {
|
|
9
|
+
console.error(chalk.red(`\n ${err.message}\n`));
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (skills.length === 0) {
|
|
14
|
+
console.log('\n No skills available yet.\n');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
console.log(chalk.bold('\n Available skills:\n'));
|
|
19
|
+
|
|
20
|
+
const maxName = Math.max(...skills.map(s => s.name.length));
|
|
21
|
+
|
|
22
|
+
for (const skill of skills) {
|
|
23
|
+
console.log(` ${chalk.cyan(skill.name.padEnd(maxName + 2))} ${skill.description}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log(`\n Install: ${chalk.bold('npx agentskillsdk add <skill-name>')}`);
|
|
27
|
+
console.log(` Browse: ${chalk.underline('https://agentskills.dk/skills')}\n`);
|
|
28
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { addCommand } from './commands/add.js';
|
|
3
|
+
import { listCommand } from './commands/list.js';
|
|
4
|
+
|
|
5
|
+
const program = new Command();
|
|
6
|
+
|
|
7
|
+
program
|
|
8
|
+
.name('agentskillsdk')
|
|
9
|
+
.description('Install agent skills from agentskills.dk')
|
|
10
|
+
.version('0.1.0');
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.command('add')
|
|
14
|
+
.description('Install a skill into your project')
|
|
15
|
+
.argument('<skill-name>', 'name of the skill to install')
|
|
16
|
+
.action(addCommand);
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.command('list')
|
|
20
|
+
.description('Show all available skills')
|
|
21
|
+
.action(listCommand);
|
|
22
|
+
|
|
23
|
+
program.parse();
|
package/src/lib/api.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const API_BASE = 'https://agentskills.dk/api/cli';
|
|
2
|
+
|
|
3
|
+
export async function fetchSkills() {
|
|
4
|
+
let res;
|
|
5
|
+
try {
|
|
6
|
+
res = await fetch(`${API_BASE}/skills`);
|
|
7
|
+
} catch {
|
|
8
|
+
throw new Error('Could not reach agentskills.dk. Check your internet connection.');
|
|
9
|
+
}
|
|
10
|
+
if (!res.ok) throw new Error(`Failed to fetch skills: ${res.status}`);
|
|
11
|
+
const data = await res.json();
|
|
12
|
+
return data.skills;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function fetchSkill(name) {
|
|
16
|
+
let res;
|
|
17
|
+
try {
|
|
18
|
+
res = await fetch(`${API_BASE}/skills/${encodeURIComponent(name)}`);
|
|
19
|
+
} catch {
|
|
20
|
+
throw new Error('Could not reach agentskills.dk. Check your internet connection.');
|
|
21
|
+
}
|
|
22
|
+
if (res.status === 404) return null;
|
|
23
|
+
if (!res.ok) throw new Error(`Failed to fetch skill: ${res.status}`);
|
|
24
|
+
return res.json();
|
|
25
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { createInterface } from 'node:readline';
|
|
4
|
+
|
|
5
|
+
const AGENTS = [
|
|
6
|
+
{ name: 'Claude Code', folder: '.claude', path: (skill) => `.claude/skills/${skill}/` },
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
export async function detectAgents(cwd) {
|
|
10
|
+
const found = AGENTS.filter(agent => existsSync(join(cwd, agent.folder)));
|
|
11
|
+
|
|
12
|
+
if (found.length === 1) return [found[0]];
|
|
13
|
+
if (found.length > 1) return await promptUserChoice(found);
|
|
14
|
+
return await promptUserChoice(AGENTS);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function promptUserChoice(agents) {
|
|
18
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
19
|
+
|
|
20
|
+
console.log('\nWhich agent do you use?\n');
|
|
21
|
+
agents.forEach((a, i) => console.log(` ${i + 1}) ${a.name}`));
|
|
22
|
+
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
rl.question('\nChoice: ', (answer) => {
|
|
25
|
+
rl.close();
|
|
26
|
+
const idx = parseInt(answer, 10) - 1;
|
|
27
|
+
if (idx >= 0 && idx < agents.length) {
|
|
28
|
+
resolve([agents[idx]]);
|
|
29
|
+
} else {
|
|
30
|
+
resolve([agents[0]]);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { pipeline } from 'node:stream/promises';
|
|
2
|
+
import { createGunzip } from 'node:zlib';
|
|
3
|
+
import { createWriteStream, mkdirSync } from 'node:fs';
|
|
4
|
+
import { join, dirname } from 'node:path';
|
|
5
|
+
import tar from 'tar-stream';
|
|
6
|
+
|
|
7
|
+
export async function downloadSkill(skill, destDir) {
|
|
8
|
+
const url = `https://api.github.com/repos/${skill.githubOwner}/${skill.githubRepo}/tarball/${skill.defaultBranch}`;
|
|
9
|
+
|
|
10
|
+
let res;
|
|
11
|
+
try {
|
|
12
|
+
res = await fetch(url, {
|
|
13
|
+
headers: {
|
|
14
|
+
'Accept': 'application/vnd.github+json',
|
|
15
|
+
'User-Agent': 'agentskillsdk-cli',
|
|
16
|
+
},
|
|
17
|
+
redirect: 'follow',
|
|
18
|
+
});
|
|
19
|
+
} catch {
|
|
20
|
+
throw new Error('Failed to download skill files. Check your internet connection.');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!res.ok) throw new Error(`GitHub download failed: ${res.status}`);
|
|
24
|
+
|
|
25
|
+
const extract = tar.extract();
|
|
26
|
+
const skillPath = skill.githubPath;
|
|
27
|
+
const filePromises = [];
|
|
28
|
+
|
|
29
|
+
extract.on('entry', (header, stream, next) => {
|
|
30
|
+
// Tarball entries: "{owner}-{repo}-{sha}/skills/real-estate-crm/SKILL.md"
|
|
31
|
+
// Strip the first path segment (the repo prefix)
|
|
32
|
+
const parts = header.name.split('/');
|
|
33
|
+
parts.shift();
|
|
34
|
+
const relativePath = parts.join('/');
|
|
35
|
+
|
|
36
|
+
if (header.type === 'file' && relativePath.startsWith(skillPath + '/')) {
|
|
37
|
+
const fileRelative = relativePath.slice(skillPath.length + 1);
|
|
38
|
+
const destPath = join(destDir, fileRelative);
|
|
39
|
+
|
|
40
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
41
|
+
|
|
42
|
+
const writePromise = new Promise((resolve, reject) => {
|
|
43
|
+
stream.pipe(createWriteStream(destPath))
|
|
44
|
+
.on('finish', resolve)
|
|
45
|
+
.on('error', reject);
|
|
46
|
+
});
|
|
47
|
+
filePromises.push(writePromise);
|
|
48
|
+
} else {
|
|
49
|
+
stream.resume();
|
|
50
|
+
}
|
|
51
|
+
next();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await pipeline(
|
|
55
|
+
res.body,
|
|
56
|
+
createGunzip(),
|
|
57
|
+
extract,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
await Promise.all(filePromises);
|
|
61
|
+
}
|