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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentskillsdk",
3
- "version": "0.1.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": {
@@ -1,62 +1,114 @@
1
1
  import chalk from 'chalk';
2
- import { join } from 'node:path';
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
- // 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`));
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
- if (!skill) {
20
- let suggestions = [];
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
- 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
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
- 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('');
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
- console.log(chalk.green(' \u2713') + ` Found skill: ${chalk.bold(skill.name)}`);
71
+ spinner.stop();
72
+ step(`Found skill: ${chalk.bold(skill.name)}`);
73
+ }
41
74
 
42
- // 2. Detect agent
75
+ // --- detect agent ---
43
76
  const agents = await detectAgents(cwd);
44
- console.log(chalk.green(' \u2713') + ` Detected agent: ${chalk.bold(agents[0].name)}`);
77
+ const agent = agents[0];
78
+ step(`Detected agent: ${chalk.bold(agent.name)}`);
45
79
 
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)}`);
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
- // 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`);
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
  }
@@ -1,7 +1,9 @@
1
1
  import chalk from 'chalk';
2
2
  import { fetchSkills } from '../lib/api.js';
3
+ import { printBanner } from '../lib/ui.js';
3
4
 
4
5
  export async function listCommand() {
6
+ printBanner();
5
7
  let skills;
6
8
  try {
7
9
  skills = await fetchSkills();
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('0.1.0');
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
@@ -1,34 +1,35 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- import { createInterface } from 'node:readline';
3
+ import { homedir } from 'node:os';
4
+ import { selectPrompt } from './prompt.js';
4
5
 
5
6
  const AGENTS = [
6
- { name: 'Claude Code', folder: '.claude', path: (skill) => `.claude/skills/${skill}/` },
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) 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}`));
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
- 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
- });
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
  }
@@ -33,8 +33,18 @@ export async function downloadSkill(skill, destDir) {
33
33
  parts.shift();
34
34
  const relativePath = parts.join('/');
35
35
 
36
- if (header.type === 'file' && relativePath.startsWith(skillPath + '/')) {
37
- const fileRelative = relativePath.slice(skillPath.length + 1);
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,7 @@
1
+ export function parseGithubSource(input) {
2
+ const parts = input.split('/');
3
+ if (parts.length === 2 && parts[0] && parts[1]) {
4
+ return { owner: parts[0], repo: parts[1] };
5
+ }
6
+ return null;
7
+ }
@@ -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
+ }