@botskill/cli 1.0.2 → 1.0.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.
@@ -1,128 +1,128 @@
1
- import { Command } from 'commander';
2
- import path from 'path';
3
- import fs from 'fs-extra';
4
- import inquirer from 'inquirer';
5
-
6
- const initCommand = new Command('init');
7
- initCommand
8
- .description('Initialize a new skill project')
9
- .option('-n, --name <name>', 'Project/skill name')
10
- .option('-d, --description <description>', 'Skill description')
11
- .option('-c, --category <category>', 'Category: ai, data, web, devops, security, tools')
12
- .option('-y, --yes', 'Use defaults without prompting')
13
- .action(async (options) => {
14
- 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
- const validCategories = ['ai', 'data', 'web', 'devops', 'security', 'tools'];
23
- let answers = {};
24
-
25
- if (options.yes) {
26
- answers = {
27
- name: options.name || 'my-skill',
28
- description: options.description || 'A new AI skill',
29
- category: options.category || 'tools',
30
- version: '1.0.0',
31
- license: 'MIT',
32
- };
33
- } else {
34
- answers = await inquirer.prompt([
35
- {
36
- type: 'input',
37
- name: 'name',
38
- message: 'Skill name:',
39
- default: options.name || 'my-skill',
40
- validate: (v) => (v && v.length >= 2 ? true : 'Name must be at least 2 characters'),
41
- },
42
- {
43
- type: 'input',
44
- name: 'description',
45
- message: 'Description:',
46
- default: options.description || 'A new AI skill',
47
- validate: (v) => (v ? true : 'Description is required'),
48
- },
49
- {
50
- type: 'list',
51
- name: 'category',
52
- message: 'Category:',
53
- choices: validCategories,
54
- default: options.category && validCategories.includes(options.category) ? options.category : 'tools',
55
- },
56
- {
57
- type: 'input',
58
- name: 'version',
59
- message: 'Version:',
60
- default: '1.0.0',
61
- },
62
- {
63
- type: 'input',
64
- name: 'license',
65
- message: 'License:',
66
- default: 'MIT',
67
- },
68
- ]);
69
- }
70
-
71
- const config = {
72
- name: answers.name,
73
- description: answers.description,
74
- version: answers.version,
75
- category: answers.category,
76
- license: answers.license,
77
- tags: [],
78
- repositoryUrl: '',
79
- documentationUrl: '',
80
- demoUrl: '',
81
- };
82
-
83
- await fs.writeJson(configPath, config, { spaces: 2 });
84
-
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
- const skillMd = `---
92
- name: ${skillName}
93
- description: ${answers.description}
94
- license: ${answers.license}
95
- metadata:
96
- author: ${skillName}
97
- version: "${answers.version}"
98
- # Platform-specific (optional)
99
- category: ${answers.category}
100
- tags: []
101
- ---
102
-
103
- # ${answers.name}
104
-
105
- ${answers.description}
106
-
107
- ## Usage
108
-
109
- Add your usage documentation here. The Markdown body contains skill instructions for agents.
110
-
111
- ## Installation
112
-
113
- \`\`\`bash
114
- # Add installation instructions
115
- \`\`\`
116
- `;
117
-
118
- const skillMdPath = path.join(cwd, 'SKILL.md');
119
- await fs.writeFile(skillMdPath, skillMd, 'utf-8');
120
-
121
- console.log('Created skill.config.json and SKILL.md');
122
- console.log('\nNext steps:');
123
- console.log('1. Edit SKILL.md to add documentation (the content below the frontmatter)');
124
- console.log('2. Run "skm login" to authenticate');
125
- console.log('3. Run "skm push" or "skm publish" to upload your skill');
126
- });
127
-
128
- export { initCommand };
1
+ import { Command } from 'commander';
2
+ import path from 'path';
3
+ import fs from 'fs-extra';
4
+ import inquirer from 'inquirer';
5
+
6
+ const initCommand = new Command('init');
7
+ initCommand
8
+ .description('Initialize a new skill project')
9
+ .option('-n, --name <name>', 'Project/skill name')
10
+ .option('-d, --description <description>', 'Skill description')
11
+ .option('-c, --category <category>', 'Category: ai, data, web, devops, security, tools')
12
+ .option('-y, --yes', 'Use defaults without prompting')
13
+ .action(async (options) => {
14
+ 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
+ const validCategories = ['ai', 'data', 'web', 'devops', 'security', 'tools'];
23
+ let answers = {};
24
+
25
+ if (options.yes) {
26
+ answers = {
27
+ name: options.name || 'my-skill',
28
+ description: options.description || 'A new AI skill',
29
+ category: options.category || 'tools',
30
+ version: '1.0.0',
31
+ license: 'MIT',
32
+ };
33
+ } else {
34
+ answers = await inquirer.prompt([
35
+ {
36
+ type: 'input',
37
+ name: 'name',
38
+ message: 'Skill name:',
39
+ default: options.name || 'my-skill',
40
+ validate: (v) => (v && v.length >= 2 ? true : 'Name must be at least 2 characters'),
41
+ },
42
+ {
43
+ type: 'input',
44
+ name: 'description',
45
+ message: 'Description:',
46
+ default: options.description || 'A new AI skill',
47
+ validate: (v) => (v ? true : 'Description is required'),
48
+ },
49
+ {
50
+ type: 'list',
51
+ name: 'category',
52
+ message: 'Category:',
53
+ choices: validCategories,
54
+ default: options.category && validCategories.includes(options.category) ? options.category : 'tools',
55
+ },
56
+ {
57
+ type: 'input',
58
+ name: 'version',
59
+ message: 'Version:',
60
+ default: '1.0.0',
61
+ },
62
+ {
63
+ type: 'input',
64
+ name: 'license',
65
+ message: 'License:',
66
+ default: 'MIT',
67
+ },
68
+ ]);
69
+ }
70
+
71
+ const config = {
72
+ name: answers.name,
73
+ description: answers.description,
74
+ version: answers.version,
75
+ category: answers.category,
76
+ license: answers.license,
77
+ tags: [],
78
+ repositoryUrl: '',
79
+ documentationUrl: '',
80
+ demoUrl: '',
81
+ };
82
+
83
+ await fs.writeJson(configPath, config, { spaces: 2 });
84
+
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
+ const skillMd = `---
92
+ name: ${skillName}
93
+ description: ${answers.description}
94
+ license: ${answers.license}
95
+ metadata:
96
+ author: ${skillName}
97
+ version: "${answers.version}"
98
+ # Platform-specific (optional)
99
+ category: ${answers.category}
100
+ tags: []
101
+ ---
102
+
103
+ # ${answers.name}
104
+
105
+ ${answers.description}
106
+
107
+ ## Usage
108
+
109
+ Add your usage documentation here. The Markdown body contains skill instructions for agents.
110
+
111
+ ## Installation
112
+
113
+ \`\`\`bash
114
+ # Add installation instructions
115
+ \`\`\`
116
+ `;
117
+
118
+ const skillMdPath = path.join(cwd, 'SKILL.md');
119
+ await fs.writeFile(skillMdPath, skillMd, 'utf-8');
120
+
121
+ console.log('Created skill.config.json and SKILL.md');
122
+ console.log('\nNext steps:');
123
+ console.log('1. Edit SKILL.md to add documentation (the content below the frontmatter)');
124
+ console.log('2. Run "skm login" to authenticate');
125
+ console.log('3. Run "skm push" or "skm publish" to upload your skill');
126
+ });
127
+
128
+ export { initCommand };
@@ -1,84 +1,79 @@
1
- import { Command } from 'commander';
2
- import { createApiClient } from '../lib/auth.js';
3
- import { isLoggedIn } from '../lib/auth.js';
4
-
5
- function formatSkillDisplay(skill) {
6
- const name = skill.name || '?';
7
- const version = skill.version || (skill.versions?.[0]?.version) || '—';
8
- const downloads = skill.downloads ?? 0;
9
- const category = skill.category || '—';
10
- const status = skill.status || '—';
11
- return { name, version, downloads, category, status };
12
- }
13
-
14
- const listCommand = new Command('list');
15
- listCommand
16
- .alias('ls')
17
- .description('List skills from BotSkill')
18
- .option('-c, --category <category>', 'Filter by category (ai, data, web, devops, security, tools)')
19
- .option('-s, --search <query>', 'Search skills by name or description')
20
- .option('-m, --mine', 'Show only your skills (requires login)')
21
- .option('-l, --limit <number>', 'Maximum number of results (default: 20)', '20')
22
- .option('-p, --page <number>', 'Page number for pagination (default: 1)', '1')
23
- .action(async (options) => {
24
- const api = createApiClient();
25
- const limit = parseInt(options.limit, 10) || 20;
26
- const page = parseInt(options.page, 10) || 1;
27
-
28
- try {
29
- let res;
30
- if (options.mine) {
31
- if (!isLoggedIn()) {
32
- console.error('Error: --mine requires login. Run "skm login" first.');
33
- process.exit(1);
34
- }
35
- const params = { page, limit };
36
- if (options.category) params.category = options.category;
37
- if (options.search) params.q = options.search;
38
- res = await api.get('/skills/my', { params });
39
- } else {
40
- const params = { page, limit };
41
- if (options.category) params.category = options.category;
42
- if (options.search) params.q = options.search;
43
- if (params.q || params.category) {
44
- res = await api.get('/skills/search', { params });
45
- } else {
46
- res = await api.get('/skills', { params });
47
- }
48
- }
49
-
50
- const skills = res.data?.skills ?? res.data ?? [];
51
- const pagination = res.data?.pagination ?? {};
52
-
53
- if (skills.length === 0) {
54
- console.log('No skills found.');
55
- return;
56
- }
57
-
58
- console.log(`\nFound ${pagination.totalSkills ?? skills.length} skill(s):`);
59
- console.log('─'.repeat(60));
60
- skills.forEach((skill) => {
61
- const { name, version, downloads, category, status } = formatSkillDisplay(skill);
62
- const statusStr = options.mine ? ` | ${status}` : '';
63
- console.log(` ${name}`);
64
- console.log(` Version: ${version} | Downloads: ${downloads} | Category: ${category}${statusStr}`);
65
- });
66
- if (pagination.totalPages > 1) {
67
- console.log(`\nPage ${pagination.currentPage}/${pagination.totalPages}`);
68
- }
69
- console.log('\nUse "skm get name" or "skm get name@version" to download.');
70
- } catch (err) {
71
- let msg = err.message;
72
- if (err.response?.data) {
73
- const d = err.response.data;
74
- msg = d.error || d.message || msg;
75
- }
76
- if (err.response?.status === 401 && options.mine) {
77
- msg = 'Login required. Run "skm login" first.';
78
- }
79
- console.error('Error:', msg);
80
- process.exit(1);
81
- }
82
- });
83
-
84
- export { listCommand };
1
+ import { Command } from 'commander';
2
+ import { createApiClient, isLoggedIn } from '../lib/auth.js';
3
+ import { printApiError } from '../lib/formatError.js';
4
+
5
+ function formatSkillDisplay(skill) {
6
+ const name = skill.name || '?';
7
+ const version = skill.version || (skill.versions?.[0]?.version) || '—';
8
+ const downloads = skill.downloads ?? 0;
9
+ const category = skill.category || '—';
10
+ const status = skill.status || '—';
11
+ return { name, version, downloads, category, status };
12
+ }
13
+
14
+ const listCommand = new Command('list');
15
+ listCommand
16
+ .alias('ls')
17
+ .description('List skills from BotSkill')
18
+ .option('-c, --category <category>', 'Filter by category (ai, data, web, devops, security, tools)')
19
+ .option('-s, --search <query>', 'Search skills by name or description')
20
+ .option('-m, --mine', 'Show only your skills (requires login)')
21
+ .option('-l, --limit <number>', 'Maximum number of results (default: 20)', '20')
22
+ .option('-p, --page <number>', 'Page number for pagination (default: 1)', '1')
23
+ .option('--api-url <url>', 'API base URL (overrides config for this command)')
24
+ .action(async (options) => {
25
+ const api = createApiClient(options.apiUrl);
26
+ const limit = parseInt(options.limit, 10) || 20;
27
+ const page = parseInt(options.page, 10) || 1;
28
+
29
+ try {
30
+ let res;
31
+ if (options.mine) {
32
+ if (!isLoggedIn()) {
33
+ console.error('Error: --mine requires login. Run "skm login" first.');
34
+ process.exit(1);
35
+ }
36
+ const params = { page, limit };
37
+ if (options.category) params.category = options.category;
38
+ if (options.search) params.q = options.search;
39
+ res = await api.get('/skills/my', { params });
40
+ } else {
41
+ const params = { page, limit };
42
+ if (options.category) params.category = options.category;
43
+ if (options.search) params.q = options.search;
44
+ if (params.q || params.category) {
45
+ res = await api.get('/skills/search', { params });
46
+ } else {
47
+ res = await api.get('/skills', { params });
48
+ }
49
+ }
50
+
51
+ const skills = res.data?.skills ?? res.data ?? [];
52
+ const pagination = res.data?.pagination ?? {};
53
+
54
+ if (skills.length === 0) {
55
+ console.log('No skills found.');
56
+ return;
57
+ }
58
+
59
+ console.log(`\nFound ${pagination.totalSkills ?? skills.length} skill(s):`);
60
+ console.log('─'.repeat(60));
61
+ skills.forEach((skill) => {
62
+ const { name, version, downloads, category, status } = formatSkillDisplay(skill);
63
+ const statusStr = options.mine ? ` | ${status}` : '';
64
+ console.log(` ${name}`);
65
+ console.log(` Version: ${version} | Downloads: ${downloads} | Category: ${category}${statusStr}`);
66
+ });
67
+ if (pagination.totalPages > 1) {
68
+ console.log(`\nPage ${pagination.currentPage}/${pagination.totalPages}`);
69
+ }
70
+ console.log('\nUse "skm get name" or "skm get name@version" to download.');
71
+ } catch (err) {
72
+ if (err.response?.status === 401 && options.mine) {
73
+ err._overrideMsg = 'Login required. Run "skm login" first.';
74
+ }
75
+ printApiError(err, { prefix: 'List failed' });
76
+ }
77
+ });
78
+
79
+ export { listCommand };
@@ -1,75 +1,73 @@
1
- import { Command } from 'commander';
2
- import inquirer from 'inquirer';
3
- import axios from 'axios';
4
- import { getApiUrl, setAuth, setApiUrl } from '../lib/auth.js';
5
-
6
- const loginCommand = new Command('login');
7
- loginCommand
8
- .description('Login to BotSkill platform')
9
- .option('-u, --username <username>', 'Username')
10
- .option('-e, --email <email>', 'Email address')
11
- .option('-p, --password <password>', 'Password')
12
- .option('-t, --token <token>', 'Use access token directly (from web)')
13
- .option('--api-url <url>', 'API base URL')
14
- .action(async (options) => {
15
- const apiUrl = options.apiUrl || getApiUrl();
16
- if (options.apiUrl) {
17
- setApiUrl(options.apiUrl);
18
- }
19
-
20
- if (options.token) {
21
- setAuth({ token: options.token });
22
- console.log('Token saved. Logged in successfully.');
23
- return;
24
- }
25
-
26
- let emailOrUsername = options.email || options.username;
27
- let password = options.password;
28
-
29
- if (!emailOrUsername || !password) {
30
- const answers = await inquirer.prompt([
31
- {
32
- type: 'input',
33
- name: 'emailOrUsername',
34
- message: 'Email or Username:',
35
- default: emailOrUsername,
36
- validate: (v) => (v?.trim() ? true : 'Required'),
37
- },
38
- {
39
- type: 'password',
40
- name: 'password',
41
- message: 'Password:',
42
- mask: '*',
43
- validate: (v) => (v ? true : 'Required'),
44
- },
45
- ]);
46
- emailOrUsername = answers.emailOrUsername?.trim();
47
- password = answers.password;
48
- }
49
-
50
- console.log('Logging in to BotSkill...');
51
- try {
52
- const res = await axios.post(`${apiUrl}/auth/login`, {
53
- email: emailOrUsername,
54
- password,
55
- });
56
- const data = res.data?.data || res.data;
57
- const accessToken = data.accessToken || data.token;
58
- const refreshToken = data.refreshToken;
59
- const user = data.user;
60
-
61
- if (!accessToken) {
62
- console.error('Login failed: No token received');
63
- process.exit(1);
64
- }
65
-
66
- setAuth({ token: accessToken, refreshToken, user });
67
- console.log(`Logged in as ${user?.username || user?.email || 'user'}`);
68
- } catch (err) {
69
- const msg = err.response?.data?.error || err.message || 'Login failed';
70
- console.error('Login failed:', msg);
71
- process.exit(1);
72
- }
73
- });
74
-
75
- export { loginCommand };
1
+ import { Command } from 'commander';
2
+ import inquirer from 'inquirer';
3
+ import axios from 'axios';
4
+ import { getApiUrl, setAuth, normalizeApiUrl } from '../lib/auth.js';
5
+ import { printApiError } from '../lib/formatError.js';
6
+
7
+ const loginCommand = new Command('login');
8
+ loginCommand
9
+ .description('Login to BotSkill platform')
10
+ .option('-u, --username <username>', 'Username')
11
+ .option('-e, --email <email>', 'Email address')
12
+ .option('-p, --password <password>', 'Password')
13
+ .option('-t, --token <token>', 'Use access token directly (from web)')
14
+ .option('--api-url <url>', 'API base URL (overrides config for this command)')
15
+ .action(async (options) => {
16
+ const apiUrl = normalizeApiUrl(options.apiUrl || getApiUrl());
17
+
18
+ if (options.token) {
19
+ setAuth({ token: options.token });
20
+ console.log('Token saved. Logged in successfully.');
21
+ return;
22
+ }
23
+
24
+ let emailOrUsername = options.email || options.username;
25
+ let password = options.password;
26
+
27
+ if (!emailOrUsername || !password) {
28
+ const answers = await inquirer.prompt([
29
+ {
30
+ type: 'input',
31
+ name: 'emailOrUsername',
32
+ message: 'Email or Username:',
33
+ default: emailOrUsername,
34
+ validate: (v) => (v?.trim() ? true : 'Required'),
35
+ },
36
+ {
37
+ type: 'password',
38
+ name: 'password',
39
+ message: 'Password:',
40
+ mask: '*',
41
+ validate: (v) => (v ? true : 'Required'),
42
+ },
43
+ ]);
44
+ emailOrUsername = answers.emailOrUsername?.trim();
45
+ password = answers.password;
46
+ }
47
+
48
+ console.log('Logging in to BotSkill...');
49
+ try {
50
+ const res = await axios.post(`${apiUrl}/auth/login`, {
51
+ email: emailOrUsername,
52
+ password,
53
+ });
54
+ const data = res.data?.data || res.data;
55
+ const accessToken = data.accessToken || data.token;
56
+ const refreshToken = data.refreshToken;
57
+ const user = data.user;
58
+
59
+ if (!accessToken) {
60
+ console.error('Login failed: No token received');
61
+ process.exit(1);
62
+ }
63
+
64
+ setAuth({ token: accessToken, refreshToken, user });
65
+ console.log(`Logged in as ${user?.username || user?.email || 'user'}`);
66
+ } catch (err) {
67
+ const msg = err.response?.data?.error || err.message;
68
+ if (msg) err._overrideMsg = msg;
69
+ printApiError(err, { prefix: 'Login failed' });
70
+ }
71
+ });
72
+
73
+ export { loginCommand };
@@ -1,19 +1,21 @@
1
- import { Command } from 'commander';
2
- import { clearAuth, getApiUrl, getRefreshToken } from '../lib/auth.js';
3
- import axios from 'axios';
4
-
5
- const logoutCommand = new Command('logout');
6
- logoutCommand
7
- .description('Logout from BotSkill')
8
- .action(async () => {
9
- const refreshToken = getRefreshToken();
10
- if (refreshToken) {
11
- try {
12
- await axios.post(`${getApiUrl()}/auth/logout`, { refreshToken });
13
- } catch (_) {}
14
- }
15
- clearAuth();
16
- console.log('Logged out successfully.');
17
- });
18
-
19
- export { logoutCommand };
1
+ import { Command } from 'commander';
2
+ import { clearAuth, getApiUrl, getRefreshToken, normalizeApiUrl } from '../lib/auth.js';
3
+ import axios from 'axios';
4
+
5
+ const logoutCommand = new Command('logout');
6
+ logoutCommand
7
+ .description('Logout from BotSkill')
8
+ .option('--api-url <url>', 'API base URL (overrides config for this command)')
9
+ .action(async (options) => {
10
+ const apiUrl = normalizeApiUrl(options.apiUrl || getApiUrl());
11
+ const refreshToken = getRefreshToken();
12
+ if (refreshToken) {
13
+ try {
14
+ await axios.post(`${apiUrl}/auth/logout`, { refreshToken });
15
+ } catch (_) {}
16
+ }
17
+ clearAuth();
18
+ console.log('Logged out successfully.');
19
+ });
20
+
21
+ export { logoutCommand };