agentskillsdk 0.1.1 → 0.1.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.
- package/package.json +2 -1
- package/src/commands/add.js +93 -41
- package/src/commands/list.js +2 -0
- package/src/index.js +9 -2
- package/src/lib/detect-agent.js +23 -22
- package/src/lib/download.js +12 -2
- package/src/lib/parse-source.js +7 -0
- package/src/lib/prompt.js +42 -0
- package/src/lib/ui.js +80 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentskillsdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Install agent skills from agentskills.dk",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"commander": "^13.0.0",
|
|
15
15
|
"chalk": "^5.4.0",
|
|
16
|
+
"ora": "^8.1.0",
|
|
16
17
|
"tar-stream": "^3.1.0"
|
|
17
18
|
},
|
|
18
19
|
"engines": {
|
package/src/commands/add.js
CHANGED
|
@@ -1,62 +1,114 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import
|
|
2
|
+
import ora from 'ora';
|
|
3
3
|
import { fetchSkill, fetchSkills } from '../lib/api.js';
|
|
4
4
|
import { detectAgents } from '../lib/detect-agent.js';
|
|
5
5
|
import { downloadSkill } from '../lib/download.js';
|
|
6
|
+
import { parseGithubSource } from '../lib/parse-source.js';
|
|
7
|
+
import { selectPrompt } from '../lib/prompt.js';
|
|
8
|
+
import { printBanner, printCompletionSummary, step, error } from '../lib/ui.js';
|
|
9
|
+
|
|
10
|
+
export async function addCommand(skillName, options) {
|
|
11
|
+
printBanner();
|
|
6
12
|
|
|
7
|
-
export async function addCommand(skillName) {
|
|
8
13
|
const cwd = process.cwd();
|
|
14
|
+
const githubSource = parseGithubSource(skillName);
|
|
15
|
+
const isGithub = !!githubSource;
|
|
9
16
|
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
skill = await fetchSkill(skillName);
|
|
14
|
-
} catch (err) {
|
|
15
|
-
console.error(chalk.red(`\n ${err.message}\n`));
|
|
17
|
+
// --- resolve scope flags ---
|
|
18
|
+
if (options.global && options.project) {
|
|
19
|
+
error('Cannot use both --global and --project. Pick one.');
|
|
16
20
|
process.exit(1);
|
|
17
21
|
}
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
// --- resolve skill ---
|
|
24
|
+
let skill;
|
|
25
|
+
let installName;
|
|
26
|
+
|
|
27
|
+
if (isGithub) {
|
|
28
|
+
const skillPath = options.skill || '';
|
|
29
|
+
installName = skillPath ? skillPath.split('/').pop() : githubSource.repo;
|
|
30
|
+
skill = {
|
|
31
|
+
name: installName,
|
|
32
|
+
githubOwner: githubSource.owner,
|
|
33
|
+
githubRepo: githubSource.repo,
|
|
34
|
+
githubPath: skillPath,
|
|
35
|
+
defaultBranch: 'main',
|
|
36
|
+
};
|
|
37
|
+
step(`GitHub source: ${chalk.bold(`${githubSource.owner}/${githubSource.repo}`)}` +
|
|
38
|
+
(options.skill ? ` (skill: ${chalk.bold(options.skill)})` : ''));
|
|
39
|
+
} else {
|
|
40
|
+
installName = skillName;
|
|
41
|
+
const spinner = ora({ text: `Looking up ${chalk.bold(skillName)}...`, indent: 2 }).start();
|
|
21
42
|
try {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// If we can't fetch suggestions, just show the not-found message
|
|
43
|
+
skill = await fetchSkill(skillName);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
spinner.fail('Registry lookup failed');
|
|
46
|
+
error(err.message);
|
|
47
|
+
process.exit(1);
|
|
28
48
|
}
|
|
29
49
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
suggestions
|
|
34
|
-
|
|
50
|
+
if (!skill) {
|
|
51
|
+
spinner.fail(`Skill "${skillName}" not found`);
|
|
52
|
+
|
|
53
|
+
let suggestions = [];
|
|
54
|
+
try {
|
|
55
|
+
const allSkills = await fetchSkills();
|
|
56
|
+
suggestions = allSkills
|
|
57
|
+
.filter(s => s.name.includes(skillName) || skillName.includes(s.name))
|
|
58
|
+
.slice(0, 3);
|
|
59
|
+
} catch {
|
|
60
|
+
// ignore
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (suggestions.length > 0) {
|
|
64
|
+
console.error('\n Did you mean:');
|
|
65
|
+
suggestions.forEach(s => console.error(` - ${chalk.bold(s.name)}`));
|
|
66
|
+
}
|
|
67
|
+
console.error(`\n Browse all skills: ${chalk.underline('https://agentskills.dk/skills')}\n`);
|
|
68
|
+
process.exit(1);
|
|
35
69
|
}
|
|
36
|
-
console.error(` Browse all skills: ${chalk.underline('https://agentskills.dk/skills')}\n`);
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
70
|
|
|
40
|
-
|
|
71
|
+
spinner.stop();
|
|
72
|
+
step(`Found skill: ${chalk.bold(skill.name)}`);
|
|
73
|
+
}
|
|
41
74
|
|
|
42
|
-
//
|
|
75
|
+
// --- detect agent ---
|
|
43
76
|
const agents = await detectAgents(cwd);
|
|
44
|
-
|
|
77
|
+
const agent = agents[0];
|
|
78
|
+
step(`Detected agent: ${chalk.bold(agent.name)}`);
|
|
45
79
|
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
80
|
+
// --- resolve scope ---
|
|
81
|
+
let scope;
|
|
82
|
+
if (options.global) {
|
|
83
|
+
scope = 'global';
|
|
84
|
+
} else if (options.project) {
|
|
85
|
+
scope = 'project';
|
|
86
|
+
} else {
|
|
87
|
+
scope = await selectPrompt('Install scope:', [
|
|
88
|
+
{ label: 'Project', hint: '(.claude/skills/ in current directory)', value: 'project' },
|
|
89
|
+
{ label: 'Global', hint: '(~/.claude/skills/ for all projects)', value: 'global' },
|
|
90
|
+
]);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// --- download ---
|
|
94
|
+
const destDir = agent.path(installName, { cwd, scope });
|
|
95
|
+
const spinner = ora({ text: 'Downloading skill files...', indent: 2 }).start();
|
|
96
|
+
try {
|
|
97
|
+
await downloadSkill(skill, destDir);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
spinner.fail('Download failed');
|
|
100
|
+
error(err.message);
|
|
101
|
+
process.exit(1);
|
|
57
102
|
}
|
|
103
|
+
spinner.succeed('Downloaded skill files');
|
|
58
104
|
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
105
|
+
// --- completion summary ---
|
|
106
|
+
printCompletionSummary({
|
|
107
|
+
skillName: installName,
|
|
108
|
+
scope,
|
|
109
|
+
installPath: agent.displayPath(installName, scope),
|
|
110
|
+
agentName: agent.name,
|
|
111
|
+
isGithub,
|
|
112
|
+
namespace: skill.namespace,
|
|
113
|
+
});
|
|
62
114
|
}
|
package/src/commands/list.js
CHANGED
package/src/index.js
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
2
3
|
import { addCommand } from './commands/add.js';
|
|
3
4
|
import { listCommand } from './commands/list.js';
|
|
4
5
|
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const pkg = require('../package.json');
|
|
8
|
+
|
|
5
9
|
const program = new Command();
|
|
6
10
|
|
|
7
11
|
program
|
|
8
12
|
.name('agentskillsdk')
|
|
9
13
|
.description('Install agent skills from agentskills.dk')
|
|
10
|
-
.version(
|
|
14
|
+
.version(pkg.version);
|
|
11
15
|
|
|
12
16
|
program
|
|
13
17
|
.command('add')
|
|
14
18
|
.description('Install a skill into your project')
|
|
15
|
-
.argument('<skill-name>', 'name of the skill to install')
|
|
19
|
+
.argument('<skill-name>', 'name of the skill to install (or owner/repo for GitHub)')
|
|
20
|
+
.option('--skill <path>', 'path within repo, e.g. skills/twitter')
|
|
21
|
+
.option('-g, --global', 'install globally to ~/.claude/skills/')
|
|
22
|
+
.option('-p, --project', 'install to project .claude/skills/ (default)')
|
|
16
23
|
.action(addCommand);
|
|
17
24
|
|
|
18
25
|
program
|
package/src/lib/detect-agent.js
CHANGED
|
@@ -1,34 +1,35 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { selectPrompt } from './prompt.js';
|
|
4
5
|
|
|
5
6
|
const AGENTS = [
|
|
6
|
-
{
|
|
7
|
+
{
|
|
8
|
+
name: 'Claude Code',
|
|
9
|
+
folder: '.claude',
|
|
10
|
+
path: (skill, { cwd, scope }) => {
|
|
11
|
+
if (scope === 'global') return join(homedir(), '.claude', 'skills', skill);
|
|
12
|
+
return join(cwd, '.claude', 'skills', skill);
|
|
13
|
+
},
|
|
14
|
+
displayPath: (skill, scope) => {
|
|
15
|
+
if (scope === 'global') return `~/.claude/skills/${skill}/`;
|
|
16
|
+
return `.claude/skills/${skill}/`;
|
|
17
|
+
},
|
|
18
|
+
},
|
|
7
19
|
];
|
|
8
20
|
|
|
9
21
|
export async function detectAgents(cwd) {
|
|
10
22
|
const found = AGENTS.filter(agent => existsSync(join(cwd, agent.folder)));
|
|
11
23
|
|
|
12
24
|
if (found.length === 1) return [found[0]];
|
|
13
|
-
if (found.length > 1)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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}`));
|
|
25
|
+
if (found.length > 1) {
|
|
26
|
+
const choices = found.map(a => ({ label: a.name, value: a }));
|
|
27
|
+
const selected = await selectPrompt('Which agent do you use?', choices);
|
|
28
|
+
return [selected];
|
|
29
|
+
}
|
|
22
30
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (idx >= 0 && idx < agents.length) {
|
|
28
|
-
resolve([agents[idx]]);
|
|
29
|
-
} else {
|
|
30
|
-
resolve([agents[0]]);
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
});
|
|
31
|
+
// None found — ask user to pick
|
|
32
|
+
const choices = AGENTS.map(a => ({ label: a.name, value: a }));
|
|
33
|
+
const selected = await selectPrompt('Which agent do you use?', choices);
|
|
34
|
+
return [selected];
|
|
34
35
|
}
|
package/src/lib/download.js
CHANGED
|
@@ -33,8 +33,18 @@ export async function downloadSkill(skill, destDir) {
|
|
|
33
33
|
parts.shift();
|
|
34
34
|
const relativePath = parts.join('/');
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
let fileRelative;
|
|
37
|
+
if (skillPath === '') {
|
|
38
|
+
if (header.type === 'file' && relativePath) {
|
|
39
|
+
fileRelative = relativePath;
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
if (header.type === 'file' && relativePath.startsWith(skillPath + '/')) {
|
|
43
|
+
fileRelative = relativePath.slice(skillPath.length + 1);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (fileRelative) {
|
|
38
48
|
const destPath = join(destDir, fileRelative);
|
|
39
49
|
|
|
40
50
|
mkdirSync(dirname(destPath), { recursive: true });
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { createInterface } from 'node:readline';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Enhanced select prompt with arrow indicator and hints.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} question
|
|
8
|
+
* @param {{ label: string, hint?: string, value: any }[]} choices
|
|
9
|
+
* @param {{ defaultIndex?: number }} opts
|
|
10
|
+
* @returns {Promise<any>} selected value
|
|
11
|
+
*/
|
|
12
|
+
export function selectPrompt(question, choices, { defaultIndex = 0 } = {}) {
|
|
13
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
14
|
+
|
|
15
|
+
console.log(`\n ${question}\n`);
|
|
16
|
+
choices.forEach((c, i) => {
|
|
17
|
+
const marker = i === defaultIndex ? chalk.cyan('>') : ' ';
|
|
18
|
+
const num = `${i + 1})`;
|
|
19
|
+
const label = i === defaultIndex ? chalk.cyan(`${num} ${c.label}`) : `${num} ${c.label}`;
|
|
20
|
+
const hint = c.hint ? chalk.dim(` ${c.hint}`) : '';
|
|
21
|
+
console.log(` ${marker} ${label}${hint}`);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const defaultDisplay = defaultIndex + 1;
|
|
25
|
+
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
rl.question(`\n Choice ${chalk.dim(`[${defaultDisplay}]`)}: `, (answer) => {
|
|
28
|
+
rl.close();
|
|
29
|
+
const trimmed = answer.trim();
|
|
30
|
+
if (trimmed === '') {
|
|
31
|
+
resolve(choices[defaultIndex].value);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const idx = parseInt(trimmed, 10) - 1;
|
|
35
|
+
if (idx >= 0 && idx < choices.length) {
|
|
36
|
+
resolve(choices[idx].value);
|
|
37
|
+
} else {
|
|
38
|
+
resolve(choices[defaultIndex].value);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
package/src/lib/ui.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const pkg = require('../../package.json');
|
|
6
|
+
|
|
7
|
+
// --- strip ANSI for width calculation ---
|
|
8
|
+
|
|
9
|
+
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
10
|
+
function stripAnsi(str) {
|
|
11
|
+
return str.replace(ANSI_RE, '');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// --- box drawing ---
|
|
15
|
+
|
|
16
|
+
export function box(lines, { borderColor, padding = 2 } = {}) {
|
|
17
|
+
const maxWidth = Math.max(...lines.map(l => stripAnsi(l).length));
|
|
18
|
+
const pad = ' '.repeat(padding);
|
|
19
|
+
const inner = maxWidth + padding * 2;
|
|
20
|
+
|
|
21
|
+
const colorize = borderColor ? chalk[borderColor].bind(chalk) : (s) => s;
|
|
22
|
+
|
|
23
|
+
const top = colorize(` \u250c${'─'.repeat(inner)}\u2510`);
|
|
24
|
+
const bottom = colorize(` \u2514${'─'.repeat(inner)}\u2518`);
|
|
25
|
+
const rowLines = lines.map(l => {
|
|
26
|
+
const visible = stripAnsi(l).length;
|
|
27
|
+
const rightPad = ' '.repeat(maxWidth - visible);
|
|
28
|
+
return ` ${colorize('\u2502')}${pad}${l}${rightPad}${pad}${colorize('\u2502')}`;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return [top, ...rowLines, bottom].join('\n');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// --- banner ---
|
|
35
|
+
|
|
36
|
+
export function printBanner() {
|
|
37
|
+
const lines = [
|
|
38
|
+
`${chalk.bold('agentskillsdk')} ${chalk.dim(`v${pkg.version}`)}`,
|
|
39
|
+
chalk.dim('Install skills for AI agents'),
|
|
40
|
+
];
|
|
41
|
+
console.log('');
|
|
42
|
+
console.log(box(lines));
|
|
43
|
+
console.log('');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// --- completion summary ---
|
|
47
|
+
|
|
48
|
+
export function printCompletionSummary({ skillName, scope, installPath, agentName, isGithub, namespace }) {
|
|
49
|
+
const scopeLabel = scope === 'global'
|
|
50
|
+
? 'Global (all projects)'
|
|
51
|
+
: 'Project (local)';
|
|
52
|
+
|
|
53
|
+
const lines = [
|
|
54
|
+
chalk.bold.green('Skill installed successfully!'),
|
|
55
|
+
'',
|
|
56
|
+
` ${chalk.dim('Skill:')} ${skillName}`,
|
|
57
|
+
` ${chalk.dim('Scope:')} ${scopeLabel}`,
|
|
58
|
+
` ${chalk.dim('Path:')} ${installPath}`,
|
|
59
|
+
'',
|
|
60
|
+
`Start a new ${agentName} session to use it.`,
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
if (!isGithub && namespace) {
|
|
64
|
+
lines.push(`Learn more: ${chalk.underline(`https://agentskills.dk/skills/${namespace}`)}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log('');
|
|
68
|
+
console.log(box(lines, { borderColor: 'green' }));
|
|
69
|
+
console.log('');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// --- step / error output ---
|
|
73
|
+
|
|
74
|
+
export function step(text) {
|
|
75
|
+
console.log(chalk.green(' \u2713') + ` ${text}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function error(text) {
|
|
79
|
+
console.error(chalk.red(` ${text}`));
|
|
80
|
+
}
|