agentskillsdk 0.1.2 → 0.1.4
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 +59 -31
- package/src/commands/list.js +2 -0
- package/src/index.js +7 -1
- package/src/lib/detect-agent.js +23 -22
- package/src/lib/prompt.js +90 -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.4",
|
|
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,19 +1,30 @@
|
|
|
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
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';
|
|
7
9
|
|
|
8
10
|
export async function addCommand(skillName, options) {
|
|
11
|
+
printBanner();
|
|
12
|
+
|
|
9
13
|
const cwd = process.cwd();
|
|
10
14
|
const githubSource = parseGithubSource(skillName);
|
|
15
|
+
const isGithub = !!githubSource;
|
|
16
|
+
|
|
17
|
+
// --- resolve scope flags ---
|
|
18
|
+
if (options.global && options.project) {
|
|
19
|
+
error('Cannot use both --global and --project. Pick one.');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
11
22
|
|
|
23
|
+
// --- resolve skill ---
|
|
12
24
|
let skill;
|
|
13
25
|
let installName;
|
|
14
26
|
|
|
15
|
-
if (
|
|
16
|
-
// GitHub owner/repo source
|
|
27
|
+
if (isGithub) {
|
|
17
28
|
const skillPath = options.skill || '';
|
|
18
29
|
installName = skillPath ? skillPath.split('/').pop() : githubSource.repo;
|
|
19
30
|
skill = {
|
|
@@ -23,19 +34,22 @@ export async function addCommand(skillName, options) {
|
|
|
23
34
|
githubPath: skillPath,
|
|
24
35
|
defaultBranch: 'main',
|
|
25
36
|
};
|
|
26
|
-
|
|
37
|
+
step(`GitHub source: ${chalk.bold(`${githubSource.owner}/${githubSource.repo}`)}` +
|
|
27
38
|
(options.skill ? ` (skill: ${chalk.bold(options.skill)})` : ''));
|
|
28
39
|
} else {
|
|
29
|
-
// Registry source
|
|
30
40
|
installName = skillName;
|
|
41
|
+
const spinner = ora({ text: `Looking up ${chalk.bold(skillName)}...`, indent: 2 }).start();
|
|
31
42
|
try {
|
|
32
43
|
skill = await fetchSkill(skillName);
|
|
33
44
|
} catch (err) {
|
|
34
|
-
|
|
45
|
+
spinner.fail('Registry lookup failed');
|
|
46
|
+
error(err.message);
|
|
35
47
|
process.exit(1);
|
|
36
48
|
}
|
|
37
49
|
|
|
38
50
|
if (!skill) {
|
|
51
|
+
spinner.fail(`Skill "${skillName}" not found`);
|
|
52
|
+
|
|
39
53
|
let suggestions = [];
|
|
40
54
|
try {
|
|
41
55
|
const allSkills = await fetchSkills();
|
|
@@ -43,44 +57,58 @@ export async function addCommand(skillName, options) {
|
|
|
43
57
|
.filter(s => s.name.includes(skillName) || skillName.includes(s.name))
|
|
44
58
|
.slice(0, 3);
|
|
45
59
|
} catch {
|
|
46
|
-
//
|
|
60
|
+
// ignore
|
|
47
61
|
}
|
|
48
62
|
|
|
49
|
-
console.error(chalk.red(`\n Skill "${skillName}" not found.\n`));
|
|
50
63
|
if (suggestions.length > 0) {
|
|
51
|
-
console.error(' Did you mean:');
|
|
64
|
+
console.error('\n Did you mean:');
|
|
52
65
|
suggestions.forEach(s => console.error(` - ${chalk.bold(s.name)}`));
|
|
53
|
-
console.error('');
|
|
54
66
|
}
|
|
55
|
-
console.error(
|
|
67
|
+
console.error(`\n Browse all skills: ${chalk.underline('https://agentskills.dk/skills')}\n`);
|
|
56
68
|
process.exit(1);
|
|
57
69
|
}
|
|
58
70
|
|
|
59
|
-
|
|
71
|
+
spinner.stop();
|
|
72
|
+
step(`Found skill: ${chalk.bold(skill.name)}`);
|
|
60
73
|
}
|
|
61
74
|
|
|
62
|
-
//
|
|
75
|
+
// --- detect agent ---
|
|
63
76
|
const agents = await detectAgents(cwd);
|
|
64
|
-
|
|
77
|
+
const agent = agents[0];
|
|
78
|
+
step(`Detected agent: ${chalk.bold(agent.name)}`);
|
|
65
79
|
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
+
]);
|
|
77
91
|
}
|
|
78
92
|
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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);
|
|
85
102
|
}
|
|
103
|
+
spinner.succeed('Downloaded skill files');
|
|
104
|
+
|
|
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
|
+
});
|
|
86
114
|
}
|
package/src/commands/list.js
CHANGED
package/src/index.js
CHANGED
|
@@ -1,19 +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
19
|
.argument('<skill-name>', 'name of the skill to install (or owner/repo for GitHub)')
|
|
16
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)')
|
|
17
23
|
.action(addCommand);
|
|
18
24
|
|
|
19
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
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Interactive select prompt with arrow-key navigation.
|
|
5
|
+
* Up/Down to move, Enter to confirm.
|
|
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
|
+
return new Promise((resolve) => {
|
|
14
|
+
let selected = defaultIndex;
|
|
15
|
+
const { stdin, stdout } = process;
|
|
16
|
+
|
|
17
|
+
function render() {
|
|
18
|
+
// Move cursor up to overwrite previous render (all choice lines + 1 blank line below question)
|
|
19
|
+
// On first render we just printed the question, so we only clear choice lines after that
|
|
20
|
+
const lines = choices.map((c, i) => {
|
|
21
|
+
const marker = i === selected ? chalk.cyan('\u276f') : ' ';
|
|
22
|
+
const label = i === selected ? chalk.cyan(c.label) : c.label;
|
|
23
|
+
const hint = c.hint ? chalk.dim(` ${c.hint}`) : '';
|
|
24
|
+
return ` ${marker} ${label}${hint}`;
|
|
25
|
+
});
|
|
26
|
+
return lines.join('\n');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Print question
|
|
30
|
+
stdout.write(`\n ${question}\n\n`);
|
|
31
|
+
|
|
32
|
+
// Initial render
|
|
33
|
+
stdout.write(render());
|
|
34
|
+
|
|
35
|
+
// Enter raw mode for keypress detection
|
|
36
|
+
const wasRaw = stdin.isRaw;
|
|
37
|
+
stdin.setRawMode(true);
|
|
38
|
+
stdin.resume();
|
|
39
|
+
|
|
40
|
+
function onData(buf) {
|
|
41
|
+
const key = buf.toString();
|
|
42
|
+
|
|
43
|
+
// Ctrl+C
|
|
44
|
+
if (key === '\x03') {
|
|
45
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
46
|
+
stdin.removeListener('data', onData);
|
|
47
|
+
stdin.pause();
|
|
48
|
+
stdout.write('\n');
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Enter
|
|
53
|
+
if (key === '\r' || key === '\n') {
|
|
54
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
55
|
+
stdin.removeListener('data', onData);
|
|
56
|
+
stdin.pause();
|
|
57
|
+
// Clear the choices and rewrite with final selection
|
|
58
|
+
stdout.write(`\x1b[${choices.length}A`); // move up
|
|
59
|
+
stdout.write('\x1b[J'); // clear to end
|
|
60
|
+
const final = choices.map((c, i) => {
|
|
61
|
+
if (i === selected) {
|
|
62
|
+
return ` ${chalk.cyan('\u276f')} ${chalk.cyan(c.label)}`;
|
|
63
|
+
}
|
|
64
|
+
return '';
|
|
65
|
+
}).filter(Boolean).join('\n');
|
|
66
|
+
stdout.write(final + '\n');
|
|
67
|
+
resolve(choices[selected].value);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Arrow keys come as escape sequences: \x1b[A (up), \x1b[B (down)
|
|
72
|
+
if (key === '\x1b[A' || key === '\x1b[D') {
|
|
73
|
+
// Up or Left
|
|
74
|
+
selected = (selected - 1 + choices.length) % choices.length;
|
|
75
|
+
} else if (key === '\x1b[B' || key === '\x1b[C') {
|
|
76
|
+
// Down or Right
|
|
77
|
+
selected = (selected + 1) % choices.length;
|
|
78
|
+
} else {
|
|
79
|
+
return; // ignore other keys
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Redraw: move cursor up N lines, clear, rewrite
|
|
83
|
+
stdout.write(`\x1b[${choices.length}A`);
|
|
84
|
+
stdout.write('\x1b[J');
|
|
85
|
+
stdout.write(render());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
stdin.on('data', onData);
|
|
89
|
+
});
|
|
90
|
+
}
|
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
|
+
}
|