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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentskillsdk",
3
- "version": "0.1.2",
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": {
@@ -1,19 +1,30 @@
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
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 (githubSource) {
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
- console.log(chalk.green(' \u2713') + ` GitHub source: ${chalk.bold(`${githubSource.owner}/${githubSource.repo}`)}` +
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
- console.error(chalk.red(`\n ${err.message}\n`));
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
- // If we can't fetch suggestions, just show the not-found message
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(` Browse all skills: ${chalk.underline('https://agentskills.dk/skills')}\n`);
67
+ console.error(`\n Browse all skills: ${chalk.underline('https://agentskills.dk/skills')}\n`);
56
68
  process.exit(1);
57
69
  }
58
70
 
59
- console.log(chalk.green(' \u2713') + ` Found skill: ${chalk.bold(skill.name)}`);
71
+ spinner.stop();
72
+ step(`Found skill: ${chalk.bold(skill.name)}`);
60
73
  }
61
74
 
62
- // 2. Detect agent
75
+ // --- detect agent ---
63
76
  const agents = await detectAgents(cwd);
64
- 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)}`);
65
79
 
66
- // 3. Download + install for each selected agent
67
- for (const agent of agents) {
68
- const destDir = join(cwd, agent.path(installName));
69
- try {
70
- await downloadSkill(skill, destDir);
71
- } catch (err) {
72
- console.error(chalk.red(`\n Failed to download skill files. Try again or report at agentskills.dk/support`));
73
- console.error(chalk.red(` ${err.message}\n`));
74
- process.exit(1);
75
- }
76
- console.log(chalk.green(' \u2713') + ` Installed to ${agent.path(installName)}`);
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
- // 4. Done
80
- console.log(`\n Skill ready. Start a new ${agents[0].name} session to use it.`);
81
- if (!githubSource) {
82
- console.log(` Learn more: ${chalk.underline(`https://agentskills.dk/skills/${skill.namespace}`)}\n`);
83
- } else {
84
- console.log('');
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
  }
@@ -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,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('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
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
@@ -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
  }
@@ -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
+ }