skill-market-cli 1.0.0
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 +121 -0
- package/bin/skill-market-cli.js +4 -0
- package/package.json +44 -0
- package/src/api/client.js +132 -0
- package/src/auth/oauth.js +219 -0
- package/src/auth/token-store.js +100 -0
- package/src/commands/delete.js +63 -0
- package/src/commands/list.js +61 -0
- package/src/commands/login.js +21 -0
- package/src/commands/logout.js +24 -0
- package/src/commands/run-example.js +272 -0
- package/src/commands/update.js +123 -0
- package/src/commands/upload.js +175 -0
- package/src/index.js +113 -0
- package/src/skills/upload-guide/SKILL.md +92 -0
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Skill Market CLI
|
|
2
|
+
|
|
3
|
+
A command-line tool for managing skills on Skill Market.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g skill-market-cli
|
|
9
|
+
# or
|
|
10
|
+
npx skill-market-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Login
|
|
17
|
+
skill-market-cli login
|
|
18
|
+
|
|
19
|
+
# List all skills
|
|
20
|
+
skill-market-cli list
|
|
21
|
+
|
|
22
|
+
# List your skills
|
|
23
|
+
skill-market-cli list --my
|
|
24
|
+
|
|
25
|
+
# Upload a new skill
|
|
26
|
+
skill-market-cli upload ./my-skill
|
|
27
|
+
|
|
28
|
+
# Collect AI responses for examples
|
|
29
|
+
skill-market-cli run-example ./my-skill --model claude-3-5-sonnet
|
|
30
|
+
|
|
31
|
+
# Update a skill
|
|
32
|
+
skill-market-cli update <skill-id> --file ./my-skill/SKILL.md
|
|
33
|
+
|
|
34
|
+
# Delete a skill
|
|
35
|
+
skill-market-cli delete <skill-id>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Commands
|
|
39
|
+
|
|
40
|
+
### `login`
|
|
41
|
+
|
|
42
|
+
Login to Skill Market using OAuth.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
skill-market-cli login
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### `logout`
|
|
49
|
+
|
|
50
|
+
Logout and revoke access token.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
skill-market-cli logout
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### `list`
|
|
57
|
+
|
|
58
|
+
List skills on the market.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
skill-market-cli list
|
|
62
|
+
skill-market-cli list --my # Show only your skills
|
|
63
|
+
skill-market-cli list --json # Output as JSON
|
|
64
|
+
skill-market-cli list -p 2 -s 10 # Page 2, 10 items per page
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### `upload`
|
|
68
|
+
|
|
69
|
+
Upload a new skill.
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
skill-market-cli upload ./my-skill
|
|
73
|
+
skill-market-cli upload ./my-skill/SKILL.md --name "my-skill" --tags "tag1,tag2"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### `run-example`
|
|
77
|
+
|
|
78
|
+
Run user examples and collect AI responses.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
skill-market-cli run-example ./my-skill --model claude-3-5-sonnet
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
This command:
|
|
85
|
+
1. Reads your SKILL.md
|
|
86
|
+
2. Runs each example through the specified AI model
|
|
87
|
+
3. Collects thinking steps, tool calls, and messages
|
|
88
|
+
4. Saves to `.skill-examples.json`
|
|
89
|
+
|
|
90
|
+
### `update`
|
|
91
|
+
|
|
92
|
+
Update an existing skill.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
skill-market-cli update <skill-id> --file ./my-skill/SKILL.md
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### `delete`
|
|
99
|
+
|
|
100
|
+
Delete a skill.
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
skill-market-cli delete <skill-id>
|
|
104
|
+
skill-market-cli delete <skill-id> --force # Skip confirmation
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### `guide`
|
|
108
|
+
|
|
109
|
+
Show skill upload guide.
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
skill-market-cli guide
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Configuration
|
|
116
|
+
|
|
117
|
+
Configuration is stored in `~/.skill-market-cli/config.json`.
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "skill-market-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool for managing skills on Skill Market",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"skill-market-cli": "./bin/skill-market-cli.js",
|
|
8
|
+
"smcli": "./bin/skill-market-cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"skill",
|
|
15
|
+
"cli",
|
|
16
|
+
"market",
|
|
17
|
+
"ai",
|
|
18
|
+
"kimi",
|
|
19
|
+
"claude",
|
|
20
|
+
"cursor"
|
|
21
|
+
],
|
|
22
|
+
"author": "Kirigaya",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"axios": "^1.6.0",
|
|
26
|
+
"chalk": "^4.1.2",
|
|
27
|
+
"commander": "^11.0.0",
|
|
28
|
+
"fs-extra": "^11.1.1",
|
|
29
|
+
"inquirer": "^8.2.6",
|
|
30
|
+
"open": "^8.4.2",
|
|
31
|
+
"yaml": "^2.3.4"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=14.0.0"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/yourusername/skill-market-cli.git"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://kirigaya.cn/ktools/skillmanager",
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/yourusername/skill-market-cli/issues"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const { getToken, isLoggedIn, getServerConfig } = require('../auth/token-store');
|
|
4
|
+
const { refreshAccessToken } = require('../auth/oauth');
|
|
5
|
+
|
|
6
|
+
class ApiClient {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.client = null;
|
|
9
|
+
this.init();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
init() {
|
|
13
|
+
const serverConfig = getServerConfig();
|
|
14
|
+
this.client = axios.create({
|
|
15
|
+
baseURL: serverConfig.apiBase,
|
|
16
|
+
timeout: 30000,
|
|
17
|
+
headers: {
|
|
18
|
+
'Content-Type': 'application/json'
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// 请求拦截器 - 添加 Token
|
|
23
|
+
this.client.interceptors.request.use(
|
|
24
|
+
async (config) => {
|
|
25
|
+
if (isLoggedIn()) {
|
|
26
|
+
const { accessToken, expiresAt } = getToken();
|
|
27
|
+
|
|
28
|
+
// 检查 Token 是否即将过期
|
|
29
|
+
if (expiresAt && Date.now() > expiresAt - 60000) {
|
|
30
|
+
// Token 即将过期,尝试刷新
|
|
31
|
+
try {
|
|
32
|
+
const newToken = await refreshAccessToken();
|
|
33
|
+
config.headers['Authorization'] = `Bearer ${newToken}`;
|
|
34
|
+
} catch (e) {
|
|
35
|
+
// 刷新失败,使用现有 Token
|
|
36
|
+
config.headers['Authorization'] = `Bearer ${accessToken}`;
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
config.headers['Authorization'] = `Bearer ${accessToken}`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return config;
|
|
43
|
+
},
|
|
44
|
+
(error) => Promise.reject(error)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// 响应拦截器 - 处理错误
|
|
48
|
+
this.client.interceptors.response.use(
|
|
49
|
+
(response) => response,
|
|
50
|
+
async (error) => {
|
|
51
|
+
if (error.response) {
|
|
52
|
+
// 401 未授权 - Token 可能已过期
|
|
53
|
+
if (error.response.status === 401) {
|
|
54
|
+
console.error(chalk.red('\n❌ Authentication expired. Please login again.'));
|
|
55
|
+
console.error(chalk.gray('Run: skill-market-cli login\n'));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 403 禁止访问
|
|
59
|
+
if (error.response.status === 403) {
|
|
60
|
+
console.error(chalk.red('\n❌ You do not have permission to perform this action.'));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 429 请求过多
|
|
64
|
+
if (error.response.status === 429) {
|
|
65
|
+
console.error(chalk.yellow('\n⚠️ Too many requests. Please try again later.'));
|
|
66
|
+
}
|
|
67
|
+
} else if (error.request) {
|
|
68
|
+
console.error(chalk.red('\n❌ Network error. Please check your connection.'));
|
|
69
|
+
}
|
|
70
|
+
return Promise.reject(error);
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Skills API
|
|
76
|
+
async listSkills(page = 1, pageSize = 20) {
|
|
77
|
+
const response = await this.client.get('/skill/list', {
|
|
78
|
+
params: { page, pageSize }
|
|
79
|
+
});
|
|
80
|
+
return response.data;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async getMySkills() {
|
|
84
|
+
// 获取当前用户的 skills
|
|
85
|
+
const response = await this.client.get('/skill/list', {
|
|
86
|
+
params: { page: 1, pageSize: 1000 }
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const { user } = getToken();
|
|
90
|
+
if (response.data && response.data.data && response.data.data.list) {
|
|
91
|
+
response.data.data.list = response.data.data.list.filter(
|
|
92
|
+
skill => skill.creator === user.name
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return response.data;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async getSkillDetail(id) {
|
|
99
|
+
const response = await this.client.get(`/skill/detail/${id}`);
|
|
100
|
+
return response.data;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async uploadSkill(data) {
|
|
104
|
+
const response = await this.client.post('/skill/upload', data);
|
|
105
|
+
return response.data;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async updateSkill(id, data) {
|
|
109
|
+
const response = await this.client.post(`/skill/update/${id}`, data);
|
|
110
|
+
return response.data;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async deleteSkill(id) {
|
|
114
|
+
const response = await this.client.post(`/skill/delete/${id}`);
|
|
115
|
+
return response.data;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// OAuth API
|
|
119
|
+
async getUserInfo() {
|
|
120
|
+
const serverConfig = getServerConfig();
|
|
121
|
+
const { accessToken } = getToken();
|
|
122
|
+
const response = await axios.get(`${serverConfig.baseURL}/oauth/userinfo`, {
|
|
123
|
+
headers: {
|
|
124
|
+
'Authorization': `Bearer ${accessToken}`
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
return response.data;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 导出单例
|
|
132
|
+
module.exports = new ApiClient();
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
const http = require('http');
|
|
2
|
+
const url = require('url');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const open = require('open');
|
|
5
|
+
const axios = require('axios');
|
|
6
|
+
const { saveToken, getServerConfig, setServerConfig } = require('./token-store');
|
|
7
|
+
|
|
8
|
+
const DEFAULT_SERVER_URL = 'https://kirigaya.cn';
|
|
9
|
+
const CLIENT_ID = 'skill-market-cli';
|
|
10
|
+
|
|
11
|
+
// 获取 OAuth 配置
|
|
12
|
+
async function getOAuthConfig() {
|
|
13
|
+
try {
|
|
14
|
+
const serverConfig = getServerConfig();
|
|
15
|
+
const response = await axios.get(`${serverConfig.baseURL}/oauth/config`);
|
|
16
|
+
if (response.data && response.data.code === 200) {
|
|
17
|
+
return response.data.data;
|
|
18
|
+
}
|
|
19
|
+
} catch (e) {
|
|
20
|
+
// 使用默认配置
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
clientId: CLIENT_ID,
|
|
24
|
+
redirectUri: 'http://localhost:0/callback',
|
|
25
|
+
authorizeURL: `${DEFAULT_SERVER_URL}/oauth/authorize`,
|
|
26
|
+
tokenURL: `${DEFAULT_SERVER_URL}/oauth/token`,
|
|
27
|
+
scopes: ['skill:read', 'skill:write', 'user:read']
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 启动本地回调服务器
|
|
32
|
+
function startCallbackServer(port = 0) {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const server = http.createServer((req, res) => {
|
|
35
|
+
const parsedUrl = url.parse(req.url, true);
|
|
36
|
+
const { code, error, error_description } = parsedUrl.query;
|
|
37
|
+
|
|
38
|
+
if (error) {
|
|
39
|
+
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
40
|
+
res.end(`
|
|
41
|
+
<!DOCTYPE html>
|
|
42
|
+
<html>
|
|
43
|
+
<head><title>授权失败</title></head>
|
|
44
|
+
<body style="text-align:center;padding:50px;font-family:sans-serif;">
|
|
45
|
+
<h1 style="color:#f44336;">❌ 授权失败</h1>
|
|
46
|
+
<p>${error_description || error}</p>
|
|
47
|
+
<p>请关闭此窗口并返回命令行</p>
|
|
48
|
+
</body>
|
|
49
|
+
</html>
|
|
50
|
+
`);
|
|
51
|
+
server.close();
|
|
52
|
+
reject(new Error(error_description || error));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (code) {
|
|
57
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
58
|
+
res.end(`
|
|
59
|
+
<!DOCTYPE html>
|
|
60
|
+
<html>
|
|
61
|
+
<head><title>授权成功</title></head>
|
|
62
|
+
<body style="text-align:center;padding:50px;font-family:sans-serif;background:linear-gradient(135deg,#667eea,#764ba2);">
|
|
63
|
+
<div style="background:white;padding:40px;border-radius:12px;box-shadow:0 10px 40px rgba(0,0,0,0.2);display:inline-block;">
|
|
64
|
+
<div style="font-size:64px;color:#4CAF50;">✓</div>
|
|
65
|
+
<h1 style="color:#333;">授权成功</h1>
|
|
66
|
+
<p style="color:#666;">您已成功授权 Skill Market CLI</p>
|
|
67
|
+
<p style="color:#999;font-size:14px;">请关闭此窗口并返回命令行</p>
|
|
68
|
+
</div>
|
|
69
|
+
</body>
|
|
70
|
+
</html>
|
|
71
|
+
`);
|
|
72
|
+
server.close();
|
|
73
|
+
resolve(code);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
server.listen(port, () => {
|
|
78
|
+
const actualPort = server.address().port;
|
|
79
|
+
console.log(chalk.gray(`Callback server listening on port ${actualPort}`));
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
server.on('error', reject);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 执行 OAuth 登录流程
|
|
87
|
+
async function login(options = {}) {
|
|
88
|
+
console.log(chalk.blue('🔐 Starting OAuth login flow...\n'));
|
|
89
|
+
|
|
90
|
+
// 获取 OAuth 配置
|
|
91
|
+
const oauthConfig = await getOAuthConfig();
|
|
92
|
+
console.log(chalk.gray(`Server: ${oauthConfig.authorizeURL}`));
|
|
93
|
+
|
|
94
|
+
// 启动本地回调服务器
|
|
95
|
+
let callbackPort = 0;
|
|
96
|
+
const callbackPromise = startCallbackServer(callbackPort);
|
|
97
|
+
|
|
98
|
+
// 构造授权 URL
|
|
99
|
+
const state = generateRandomString(16);
|
|
100
|
+
const redirectUri = `http://localhost:${callbackPort}/callback`;
|
|
101
|
+
|
|
102
|
+
const authUrl = new URL(oauthConfig.authorizeURL);
|
|
103
|
+
authUrl.searchParams.set('response_type', 'code');
|
|
104
|
+
authUrl.searchParams.set('client_id', oauthConfig.clientId);
|
|
105
|
+
authUrl.searchParams.set('redirect_uri', redirectUri);
|
|
106
|
+
authUrl.searchParams.set('scope', oauthConfig.scopes.join(' '));
|
|
107
|
+
authUrl.searchParams.set('state', state);
|
|
108
|
+
|
|
109
|
+
console.log(chalk.cyan('\n📱 Please authorize the CLI in your browser.\n'));
|
|
110
|
+
console.log(chalk.gray('Authorization URL:'));
|
|
111
|
+
console.log(chalk.underline(authUrl.toString()));
|
|
112
|
+
console.log();
|
|
113
|
+
|
|
114
|
+
// 自动打开浏览器
|
|
115
|
+
if (options.open !== false) {
|
|
116
|
+
try {
|
|
117
|
+
await open(authUrl.toString());
|
|
118
|
+
console.log(chalk.gray('Browser opened automatically.\n'));
|
|
119
|
+
} catch (e) {
|
|
120
|
+
console.log(chalk.yellow('Could not open browser automatically.'));
|
|
121
|
+
console.log(chalk.yellow('Please open the URL manually.\n'));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 等待回调
|
|
126
|
+
try {
|
|
127
|
+
const code = await callbackPromise;
|
|
128
|
+
console.log(chalk.green('✅ Authorization code received'));
|
|
129
|
+
|
|
130
|
+
// 交换 Token
|
|
131
|
+
console.log(chalk.gray('Exchanging code for token...'));
|
|
132
|
+
|
|
133
|
+
const tokenResponse = await axios.post(oauthConfig.tokenURL, {
|
|
134
|
+
grant_type: 'authorization_code',
|
|
135
|
+
code: code,
|
|
136
|
+
redirect_uri: redirectUri,
|
|
137
|
+
client_id: oauthConfig.clientId,
|
|
138
|
+
client_secret: 'dummy-secret' // 实际使用时需要从安全的地方获取
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const { access_token, refresh_token, expires_in } = tokenResponse.data;
|
|
142
|
+
|
|
143
|
+
// 获取用户信息
|
|
144
|
+
const userResponse = await axios.get(`${oauthConfig.authorizeURL.replace('/authorize', '/userinfo')}`, {
|
|
145
|
+
headers: {
|
|
146
|
+
'Authorization': `Bearer ${access_token}`
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const user = userResponse.data;
|
|
151
|
+
|
|
152
|
+
// 保存 Token
|
|
153
|
+
const expiresAt = Date.now() + (expires_in * 1000);
|
|
154
|
+
saveToken(access_token, refresh_token, expiresAt, {
|
|
155
|
+
name: user.name,
|
|
156
|
+
email: user.email,
|
|
157
|
+
picture: user.picture
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
console.log();
|
|
161
|
+
console.log(chalk.green('✅ Login successful!'));
|
|
162
|
+
console.log(chalk.cyan(`👤 Welcome, ${user.name}!`));
|
|
163
|
+
console.log();
|
|
164
|
+
|
|
165
|
+
return true;
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error();
|
|
168
|
+
console.error(chalk.red('❌ Login failed:'), error.message);
|
|
169
|
+
if (error.response) {
|
|
170
|
+
console.error(chalk.red('Server response:'), error.response.data);
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 刷新 Token
|
|
177
|
+
async function refreshAccessToken() {
|
|
178
|
+
const { refreshToken } = require('./token-store').getToken();
|
|
179
|
+
if (!refreshToken) {
|
|
180
|
+
throw new Error('No refresh token available');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const oauthConfig = await getOAuthConfig();
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const response = await axios.post(oauthConfig.tokenURL, {
|
|
187
|
+
grant_type: 'refresh_token',
|
|
188
|
+
refresh_token: refreshToken,
|
|
189
|
+
client_id: oauthConfig.clientId,
|
|
190
|
+
client_secret: 'dummy-secret'
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const { access_token, refresh_token, expires_in } = response.data;
|
|
194
|
+
const expiresAt = Date.now() + (expires_in * 1000);
|
|
195
|
+
|
|
196
|
+
const { user } = require('./token-store').getToken();
|
|
197
|
+
saveToken(access_token, refresh_token, expiresAt, user);
|
|
198
|
+
|
|
199
|
+
return access_token;
|
|
200
|
+
} catch (error) {
|
|
201
|
+
throw new Error('Failed to refresh token: ' + error.message);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 生成随机字符串
|
|
206
|
+
function generateRandomString(length) {
|
|
207
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
208
|
+
let result = '';
|
|
209
|
+
for (let i = 0; i < length; i++) {
|
|
210
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
module.exports = {
|
|
216
|
+
login,
|
|
217
|
+
refreshAccessToken,
|
|
218
|
+
getOAuthConfig
|
|
219
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), '.skill-market-cli');
|
|
6
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
7
|
+
|
|
8
|
+
// 确保配置目录存在
|
|
9
|
+
function ensureConfigDir() {
|
|
10
|
+
fs.ensureDirSync(CONFIG_DIR);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// 获取配置
|
|
14
|
+
function getConfig() {
|
|
15
|
+
ensureConfigDir();
|
|
16
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
17
|
+
try {
|
|
18
|
+
return fs.readJsonSync(CONFIG_FILE);
|
|
19
|
+
} catch (e) {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 保存配置
|
|
27
|
+
function saveConfig(config) {
|
|
28
|
+
ensureConfigDir();
|
|
29
|
+
fs.writeJsonSync(CONFIG_FILE, config, { spaces: 2 });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 保存 Token
|
|
33
|
+
function saveToken(accessToken, refreshToken, expiresAt, user) {
|
|
34
|
+
const config = getConfig();
|
|
35
|
+
config.accessToken = accessToken;
|
|
36
|
+
config.refreshToken = refreshToken;
|
|
37
|
+
config.expiresAt = expiresAt;
|
|
38
|
+
config.user = user;
|
|
39
|
+
saveConfig(config);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 获取 Token
|
|
43
|
+
function getToken() {
|
|
44
|
+
const config = getConfig();
|
|
45
|
+
return {
|
|
46
|
+
accessToken: config.accessToken,
|
|
47
|
+
refreshToken: config.refreshToken,
|
|
48
|
+
expiresAt: config.expiresAt,
|
|
49
|
+
user: config.user
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 清除 Token
|
|
54
|
+
function clearToken() {
|
|
55
|
+
const config = getConfig();
|
|
56
|
+
delete config.accessToken;
|
|
57
|
+
delete config.refreshToken;
|
|
58
|
+
delete config.expiresAt;
|
|
59
|
+
delete config.user;
|
|
60
|
+
saveConfig(config);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 检查是否已登录
|
|
64
|
+
function isLoggedIn() {
|
|
65
|
+
const config = getConfig();
|
|
66
|
+
if (!config.accessToken) return false;
|
|
67
|
+
if (config.expiresAt && Date.now() > config.expiresAt) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 获取服务器配置
|
|
74
|
+
function getServerConfig() {
|
|
75
|
+
const config = getConfig();
|
|
76
|
+
return config.server || {
|
|
77
|
+
baseURL: 'https://kirigaya.cn',
|
|
78
|
+
apiBase: 'https://kirigaya.cn/api'
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 保存服务器配置
|
|
83
|
+
function setServerConfig(serverConfig) {
|
|
84
|
+
const config = getConfig();
|
|
85
|
+
config.server = serverConfig;
|
|
86
|
+
saveConfig(config);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = {
|
|
90
|
+
getConfig,
|
|
91
|
+
saveConfig,
|
|
92
|
+
saveToken,
|
|
93
|
+
getToken,
|
|
94
|
+
clearToken,
|
|
95
|
+
isLoggedIn,
|
|
96
|
+
getServerConfig,
|
|
97
|
+
setServerConfig,
|
|
98
|
+
CONFIG_DIR,
|
|
99
|
+
CONFIG_FILE
|
|
100
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const inquirer = require('inquirer');
|
|
3
|
+
const { isLoggedIn } = require('../auth/token-store');
|
|
4
|
+
const apiClient = require('../api/client');
|
|
5
|
+
|
|
6
|
+
async function remove(skillId, options) {
|
|
7
|
+
if (!isLoggedIn()) {
|
|
8
|
+
console.error(chalk.red('❌ Please login first: skill-market-cli login\n'));
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
// 获取 skill 信息
|
|
14
|
+
const detailResponse = await apiClient.getSkillDetail(skillId);
|
|
15
|
+
|
|
16
|
+
if (detailResponse.code !== 200) {
|
|
17
|
+
console.error(chalk.red('❌ Skill not found'));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const skill = detailResponse.data;
|
|
22
|
+
|
|
23
|
+
console.log(chalk.yellow('\n⚠️ You are about to delete:'));
|
|
24
|
+
console.log(` ${chalk.bold(skill.name)}`);
|
|
25
|
+
console.log(` ${chalk.gray(skill.purpose || 'No description')}`);
|
|
26
|
+
console.log();
|
|
27
|
+
|
|
28
|
+
// 确认删除
|
|
29
|
+
let confirm = options.force;
|
|
30
|
+
|
|
31
|
+
if (!confirm) {
|
|
32
|
+
const answer = await inquirer.prompt([{
|
|
33
|
+
type: 'confirm',
|
|
34
|
+
name: 'confirm',
|
|
35
|
+
message: chalk.red('Are you sure? This action cannot be undone.'),
|
|
36
|
+
default: false
|
|
37
|
+
}]);
|
|
38
|
+
confirm = answer.confirm;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!confirm) {
|
|
42
|
+
console.log(chalk.gray('Deletion cancelled.\n'));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 执行删除
|
|
47
|
+
console.log(chalk.blue('\n🗑️ Deleting...\n'));
|
|
48
|
+
|
|
49
|
+
const response = await apiClient.deleteSkill(skillId);
|
|
50
|
+
|
|
51
|
+
if (response.code === 200) {
|
|
52
|
+
console.log(chalk.green('✅ Skill deleted successfully.\n'));
|
|
53
|
+
} else {
|
|
54
|
+
console.error(chalk.red('❌ Deletion failed:'), response.data || 'Unknown error');
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error(chalk.red('❌ Delete error:'), error.message);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = remove;
|