@botskill/cli 1.0.4 → 1.0.5

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": "@botskill/cli",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "CLI tool for BotSkill - AI agent skills platform",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -25,7 +25,8 @@ getCommand
25
25
  .option('-o, --output <dir>', 'Output directory (default: current directory)')
26
26
  .option('--dry-run', 'Show what would be downloaded without actually downloading')
27
27
  .option('--api-url <url>', 'API base URL (overrides config for this command)')
28
- .action(async (specifier, options) => {
28
+ .action(async (specifier, options, command) => {
29
+ const apiUrl = command.optsWithGlobals().apiUrl;
29
30
  const { name, version } = parseSpecifier(specifier);
30
31
  const outputDir = path.resolve(options.output || process.cwd());
31
32
 
@@ -42,7 +43,7 @@ getCommand
42
43
  }
43
44
 
44
45
  try {
45
- const api = createApiClient(options.apiUrl);
46
+ const api = createApiClient(apiUrl);
46
47
 
47
48
  const fullSpec = version ? `${name}@${version}` : name;
48
49
  console.log(`Downloading skill: ${fullSpec}`);
@@ -17,7 +17,7 @@ infoCommand
17
17
  .description('Show skill details from BotSkill')
18
18
  .argument('<specifier>', 'Skill name or name@version')
19
19
  .option('--api-url <url>', 'API base URL (overrides config for this command)')
20
- .action(async (specifier, options = {}) => {
20
+ .action(async (specifier, options, command) => {
21
21
  const { name, version } = parseSpecifier(specifier);
22
22
 
23
23
  if (!name) {
@@ -25,8 +25,9 @@ infoCommand
25
25
  process.exit(1);
26
26
  }
27
27
 
28
+ const apiUrl = command.optsWithGlobals().apiUrl;
28
29
  try {
29
- const api = createApiClient(options.apiUrl);
30
+ const api = createApiClient(apiUrl);
30
31
  const fullSpec = version ? `${name}@${version}` : name;
31
32
 
32
33
  const resolveRes = await api.get(`/skills/by-name/${encodeURIComponent(fullSpec)}`);
@@ -3,22 +3,25 @@ import path from 'path';
3
3
  import fs from 'fs-extra';
4
4
  import inquirer from 'inquirer';
5
5
 
6
+ function toSkillName(raw) {
7
+ const s = String(raw || 'my-skill').trim();
8
+ return s.toLowerCase()
9
+ .replace(/\s+/g, '-')
10
+ .replace(/[^a-z0-9-]/g, '')
11
+ .replace(/-+/g, '-')
12
+ .replace(/^-|-$/g, '') || 'my-skill';
13
+ }
14
+
6
15
  const initCommand = new Command('init');
7
16
  initCommand
8
17
  .description('Initialize a new skill project')
18
+ .argument('[path]', 'Target directory: "." or path for current/specified dir; omit to create skill-named directory')
9
19
  .option('-n, --name <name>', 'Project/skill name')
10
20
  .option('-d, --description <description>', 'Skill description')
11
21
  .option('-c, --category <category>', 'Category: ai, data, web, devops, security, tools')
12
22
  .option('-y, --yes', 'Use defaults without prompting')
13
- .action(async (options) => {
23
+ .action(async (pathArg, options) => {
14
24
  const cwd = process.cwd();
15
- const configPath = path.join(cwd, 'skill.config.json');
16
-
17
- if (await fs.pathExists(configPath)) {
18
- console.error('skill.config.json already exists in this directory.');
19
- process.exit(1);
20
- }
21
-
22
25
  const validCategories = ['ai', 'data', 'web', 'devops', 'security', 'tools'];
23
26
  let answers = {};
24
27
 
@@ -68,6 +71,25 @@ initCommand
68
71
  ]);
69
72
  }
70
73
 
74
+ const skillName = toSkillName(answers.name);
75
+ let targetDir;
76
+
77
+ if (pathArg && pathArg !== '.') {
78
+ targetDir = path.resolve(cwd, pathArg);
79
+ await fs.ensureDir(targetDir);
80
+ } else if (pathArg === '.') {
81
+ targetDir = cwd;
82
+ } else {
83
+ targetDir = path.join(cwd, skillName);
84
+ await fs.ensureDir(targetDir);
85
+ }
86
+
87
+ const configPath = path.join(targetDir, 'skill.config.json');
88
+ if (await fs.pathExists(configPath)) {
89
+ console.error(`skill.config.json already exists in ${targetDir}`);
90
+ process.exit(1);
91
+ }
92
+
71
93
  const config = {
72
94
  name: answers.name,
73
95
  description: answers.description,
@@ -82,12 +104,6 @@ initCommand
82
104
 
83
105
  await fs.writeJson(configPath, config, { spaces: 2 });
84
106
 
85
- // Agent Skills spec: https://agentskills.io/specification
86
- // name: required, 1-64 chars, lowercase + hyphens
87
- // description: required, max 1024 chars
88
- // metadata.version, metadata.author: optional
89
- const rawName = String(answers.name || 'my-skill').trim();
90
- const skillName = rawName.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '').replace(/-+/g, '-').replace(/^-|-$/g, '') || 'my-skill';
91
107
  const skillMd = `---
92
108
  name: ${skillName}
93
109
  description: ${answers.description}
@@ -115,10 +131,13 @@ Add your usage documentation here. The Markdown body contains skill instructions
115
131
  \`\`\`
116
132
  `;
117
133
 
118
- const skillMdPath = path.join(cwd, 'SKILL.md');
134
+ const skillMdPath = path.join(targetDir, 'SKILL.md');
119
135
  await fs.writeFile(skillMdPath, skillMd, 'utf-8');
120
136
 
121
137
  console.log('Created skill.config.json and SKILL.md');
138
+ if (targetDir !== cwd) {
139
+ console.log(`Location: ${targetDir}`);
140
+ }
122
141
  console.log('\nNext steps:');
123
142
  console.log('1. Edit SKILL.md to add documentation (the content below the frontmatter)');
124
143
  console.log('2. Run "skm login" to authenticate');
@@ -8,7 +8,9 @@ function formatSkillDisplay(skill) {
8
8
  const downloads = skill.downloads ?? 0;
9
9
  const category = skill.category || '—';
10
10
  const status = skill.status || '—';
11
- return { name, version, downloads, category, status };
11
+ const desc = (skill.description || '').trim();
12
+ const description = desc.length > 80 ? desc.slice(0, 77) + '...' : desc || '—';
13
+ return { name, version, downloads, category, status, description };
12
14
  }
13
15
 
14
16
  const listCommand = new Command('list');
@@ -21,8 +23,9 @@ listCommand
21
23
  .option('-l, --limit <number>', 'Maximum number of results (default: 20)', '20')
22
24
  .option('-p, --page <number>', 'Page number for pagination (default: 1)', '1')
23
25
  .option('--api-url <url>', 'API base URL (overrides config for this command)')
24
- .action(async (options) => {
25
- const api = createApiClient(options.apiUrl);
26
+ .action(async (options, command) => {
27
+ const apiUrl = command.optsWithGlobals().apiUrl;
28
+ const api = createApiClient(apiUrl);
26
29
  const limit = parseInt(options.limit, 10) || 20;
27
30
  const page = parseInt(options.page, 10) || 1;
28
31
 
@@ -59,9 +62,10 @@ listCommand
59
62
  console.log(`\nFound ${pagination.totalSkills ?? skills.length} skill(s):`);
60
63
  console.log('─'.repeat(60));
61
64
  skills.forEach((skill) => {
62
- const { name, version, downloads, category, status } = formatSkillDisplay(skill);
65
+ const { name, version, downloads, category, status, description } = formatSkillDisplay(skill);
63
66
  const statusStr = options.mine ? ` | ${status}` : '';
64
67
  console.log(` ${name}`);
68
+ console.log(` ${description}`);
65
69
  console.log(` Version: ${version} | Downloads: ${downloads} | Category: ${category}${statusStr}`);
66
70
  });
67
71
  if (pagination.totalPages > 1) {
@@ -12,8 +12,8 @@ loginCommand
12
12
  .option('-p, --password <password>', 'Password')
13
13
  .option('-t, --token <token>', 'Use access token directly (from web)')
14
14
  .option('--api-url <url>', 'API base URL (overrides config for this command)')
15
- .action(async (options) => {
16
- const apiUrl = normalizeApiUrl(options.apiUrl || getApiUrl());
15
+ .action(async (options, command) => {
16
+ const apiUrl = normalizeApiUrl(command.optsWithGlobals().apiUrl || getApiUrl());
17
17
 
18
18
  if (options.token) {
19
19
  setAuth({ token: options.token });
@@ -6,8 +6,8 @@ const logoutCommand = new Command('logout');
6
6
  logoutCommand
7
7
  .description('Logout from BotSkill')
8
8
  .option('--api-url <url>', 'API base URL (overrides config for this command)')
9
- .action(async (options) => {
10
- const apiUrl = normalizeApiUrl(options.apiUrl || getApiUrl());
9
+ .action(async (options, command) => {
10
+ const apiUrl = normalizeApiUrl(command.optsWithGlobals().apiUrl || getApiUrl());
11
11
  const refreshToken = getRefreshToken();
12
12
  if (refreshToken) {
13
13
  try {
@@ -11,7 +11,8 @@ publishCommand
11
11
  .option('-f, --file <path>', 'Path to SKILL.md, .zip, or .tar.gz')
12
12
  .option('--dry-run', 'Validate without uploading')
13
13
  .option('--api-url <url>', 'API base URL (overrides config for this command)')
14
- .action(async (options) => {
14
+ .action(async (options, command) => {
15
+ const apiUrl = command.optsWithGlobals().apiUrl;
15
16
  if (!getToken()) {
16
17
  console.error('Not logged in. Run: skm login');
17
18
  process.exit(1);
@@ -38,7 +39,7 @@ publishCommand
38
39
 
39
40
  console.log(`Publishing skill from ${path.basename(filePath)}...`);
40
41
  try {
41
- const skill = await uploadSkillFile(filePath, { apiUrl: options.apiUrl });
42
+ const skill = await uploadSkillFile(filePath, { apiUrl });
42
43
  console.log('Skill published successfully!');
43
44
  console.log(`Name: ${skill?.name}`);
44
45
  console.log(`Version: ${skill?.version || (skill?.versions?.[0]?.version)}`);
@@ -11,7 +11,8 @@ pushCommand
11
11
  .option('-f, --file <path>', 'Path to SKILL.md, .zip, or .tar.gz')
12
12
  .option('--dry-run', 'Validate without uploading')
13
13
  .option('--api-url <url>', 'API base URL')
14
- .action(async (options) => {
14
+ .action(async (options, command) => {
15
+ const apiUrl = command.optsWithGlobals().apiUrl;
15
16
  if (!getToken()) {
16
17
  console.error('Not logged in. Run: skm login');
17
18
  process.exit(1);
@@ -38,7 +39,7 @@ pushCommand
38
39
 
39
40
  console.log(`Pushing skill from ${path.basename(filePath)}...`);
40
41
  try {
41
- const skill = await uploadSkillFile(filePath, { apiUrl: options.apiUrl });
42
+ const skill = await uploadSkillFile(filePath, { apiUrl });
42
43
  console.log('Skill uploaded successfully!');
43
44
  console.log(`Name: ${skill?.name}`);
44
45
  console.log(`Version: ${skill?.version || (skill?.versions?.[0]?.version)}`);
@@ -9,7 +9,9 @@ function formatSkillDisplay(skill) {
9
9
  const version = skill.version || (skill.versions?.[0]?.version) || '—';
10
10
  const downloads = skill.downloads ?? 0;
11
11
  const category = skill.category || '—';
12
- return { displayName, version, downloads, category };
12
+ const desc = (skill.description || '').trim();
13
+ const description = desc.length > 80 ? desc.slice(0, 77) + '...' : desc || '—';
14
+ return { displayName, version, downloads, category, description };
13
15
  }
14
16
 
15
17
  const searchCommand = new Command('search');
@@ -20,8 +22,9 @@ searchCommand
20
22
  .option('-l, --limit <number>', 'Maximum number of results (default: 20)', '20')
21
23
  .option('-p, --page <number>', 'Page number for pagination (default: 1)', '1')
22
24
  .option('--api-url <url>', 'API base URL (overrides config for this command)')
23
- .action(async (query, options) => {
24
- const api = createApiClient(options.apiUrl);
25
+ .action(async (query, options, command) => {
26
+ const apiUrl = command.optsWithGlobals().apiUrl;
27
+ const api = createApiClient(apiUrl);
25
28
  const limit = parseInt(options.limit, 10) || 20;
26
29
  const page = parseInt(options.page, 10) || 1;
27
30
 
@@ -41,8 +44,9 @@ searchCommand
41
44
  console.log(`\nFound ${pagination.totalSkills ?? skills.length} skill(s) for "${query}":`);
42
45
  console.log('─'.repeat(60));
43
46
  skills.forEach((skill) => {
44
- const { displayName, version, downloads, category } = formatSkillDisplay(skill);
47
+ const { displayName, version, downloads, category, description } = formatSkillDisplay(skill);
45
48
  console.log(` ${displayName}`);
49
+ console.log(` ${description}`);
46
50
  console.log(` Version: ${version} | Downloads: ${downloads} | Category: ${category}`);
47
51
  });
48
52
  if (pagination.totalPages > 1) {
package/src/index.js CHANGED
@@ -28,7 +28,8 @@ const program = new Command();
28
28
  program
29
29
  .name('skm')
30
30
  .description('CLI tool for managing BotSkill - a platform for AI agent skills')
31
- .version(version);
31
+ .version(version)
32
+ .option('--api-url <url>', 'API base URL (overrides config for this command)');
32
33
 
33
34
  program.addCommand(initCommand);
34
35
  program.addCommand(loginCommand);