@botskill/cli 1.0.3 → 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.
- package/README.md +159 -159
- package/package.json +1 -1
- package/scripts/build.js +35 -35
- package/scripts/postinstall.js +25 -25
- package/scripts/release.js +81 -81
- package/src/commands/config.js +64 -64
- package/src/commands/get.js +82 -88
- package/src/commands/help.js +24 -33
- package/src/commands/info.js +75 -79
- package/src/commands/init.js +128 -128
- package/src/commands/list.js +79 -84
- package/src/commands/login.js +73 -75
- package/src/commands/logout.js +21 -19
- package/src/commands/publish.js +60 -62
- package/src/commands/push.js +60 -62
- package/src/commands/search.js +57 -61
- package/src/index.js +44 -44
- package/src/lib/auth.js +118 -95
- package/src/lib/constants.js +10 -10
- package/src/lib/formatError.js +113 -0
- package/src/lib/uploadSkill.js +93 -91
package/src/commands/init.js
CHANGED
|
@@ -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 };
|
package/src/commands/list.js
CHANGED
|
@@ -1,84 +1,79 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { createApiClient } from '../lib/auth.js';
|
|
3
|
-
import {
|
|
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
|
-
.
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (options.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (options.
|
|
43
|
-
if (params.q
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
console.log(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
console.log(`
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (err.response?.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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 };
|
package/src/commands/login.js
CHANGED
|
@@ -1,75 +1,73 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import inquirer from 'inquirer';
|
|
3
|
-
import axios from 'axios';
|
|
4
|
-
import { getApiUrl, setAuth,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
loginCommand
|
|
8
|
-
|
|
9
|
-
.
|
|
10
|
-
.option('-
|
|
11
|
-
.option('-
|
|
12
|
-
.option('-
|
|
13
|
-
.option('
|
|
14
|
-
.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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 };
|
package/src/commands/logout.js
CHANGED
|
@@ -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
|
-
.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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 };
|