byteplan-cli 1.3.2 → 1.3.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 +71 -39
- package/package.json +1 -1
- package/src/cli.js +122 -3
- package/src/commands/skills.js +167 -212
package/README.md
CHANGED
|
@@ -12,8 +12,37 @@ npm install -g byteplan-cli
|
|
|
12
12
|
|
|
13
13
|
### Login
|
|
14
14
|
|
|
15
|
+
登录并保存凭据(一次登录,后续无需重复):
|
|
16
|
+
|
|
15
17
|
```bash
|
|
18
|
+
# 首次登录(需要提供账号密码)
|
|
16
19
|
byteplan login -u <phone> -p <password>
|
|
20
|
+
|
|
21
|
+
# 使用已保存的凭据刷新 Token(无需账号密码)
|
|
22
|
+
byteplan login
|
|
23
|
+
|
|
24
|
+
# 覆盖已有凭据(直接传新账号密码即可)
|
|
25
|
+
byteplan login -u <new_phone> -p <new_password>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Config
|
|
29
|
+
|
|
30
|
+
检查当前配置状态:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
byteplan config
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
输出示例:
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"configured": true,
|
|
40
|
+
"user": "18256485741",
|
|
41
|
+
"env": "uat",
|
|
42
|
+
"hasToken": true,
|
|
43
|
+
"tokenExpired": false,
|
|
44
|
+
"message": "Configuration is valid."
|
|
45
|
+
}
|
|
17
46
|
```
|
|
18
47
|
|
|
19
48
|
### User Commands
|
|
@@ -45,71 +74,74 @@ byteplan data query <modelCode>
|
|
|
45
74
|
|
|
46
75
|
### Skills Commands
|
|
47
76
|
|
|
48
|
-
BytePlan CLI 提供了一系列
|
|
77
|
+
BytePlan CLI 提供了一系列 Skills,可以安装到多个 AI CLI 平台。
|
|
49
78
|
|
|
50
|
-
|
|
79
|
+
**命令列表**:
|
|
51
80
|
|
|
52
81
|
```bash
|
|
82
|
+
# 查看可用的 Skills
|
|
53
83
|
byteplan skills list
|
|
84
|
+
|
|
85
|
+
# 安装所有 Skills
|
|
86
|
+
byteplan skills install -p claude-code
|
|
87
|
+
|
|
88
|
+
# 查看已安装的 Skills
|
|
89
|
+
byteplan skills installed -p claude-code
|
|
54
90
|
```
|
|
55
91
|
|
|
56
|
-
|
|
92
|
+
**支持的平台**:
|
|
57
93
|
|
|
58
|
-
|
|
94
|
+
| Platform | 目录 |
|
|
95
|
+
|----------|------|
|
|
96
|
+
| `claude-code` (alias: `claude`) | `~/.claude/commands` |
|
|
97
|
+
| `codex` | `~/.codex` |
|
|
98
|
+
| `openclaw` | `~/.openclaw/skills` |
|
|
99
|
+
|
|
100
|
+
**安装示例**:
|
|
59
101
|
|
|
60
102
|
```bash
|
|
61
|
-
#
|
|
62
|
-
byteplan skills install
|
|
103
|
+
# 安装到单个平台
|
|
104
|
+
byteplan skills install -p claude-code
|
|
63
105
|
|
|
64
106
|
# 安装到多个平台
|
|
65
107
|
byteplan skills install -p claude-code,codex,openclaw
|
|
66
108
|
|
|
67
|
-
#
|
|
68
|
-
byteplan skills install
|
|
109
|
+
# 使用自定义目录
|
|
110
|
+
byteplan skills install -d ~/.claude/commands
|
|
69
111
|
```
|
|
70
112
|
|
|
71
|
-
|
|
113
|
+
**Windows 用户**:`~` 路径会自动展开,也可使用 `%USERPROFILE%` 环境变量。
|
|
72
114
|
|
|
73
|
-
|
|
74
|
-
|----------|------|------|
|
|
75
|
-
| `claude-code` (alias: `claude`) | `~/.claude/commands` | Claude Code CLI |
|
|
76
|
-
| `codex` | `~/.codex` | OpenAI Codex CLI |
|
|
77
|
-
| `openclaw` | `~/.openclaw/skills` | OpenClaw |
|
|
78
|
-
|
|
79
|
-
### 自定义目录
|
|
115
|
+
## Options
|
|
80
116
|
|
|
81
|
-
|
|
117
|
+
- `-e, --env <environment>` - Environment (uat, prod), default: uat
|
|
82
118
|
|
|
83
|
-
|
|
84
|
-
# 向后兼容:指定自定义目录
|
|
85
|
-
byteplan skills install byteplan-api -d ~/.claude/commands
|
|
119
|
+
## Configuration
|
|
86
120
|
|
|
87
|
-
|
|
88
|
-
byteplan skills install byteplan-api -p claude-code -d /custom/path
|
|
89
|
-
```
|
|
121
|
+
`byteplan login` 会将凭据持久化保存到 `~/.byteplan/.env`,后续命令无需重复登录。
|
|
90
122
|
|
|
91
|
-
|
|
123
|
+
**保存的内容**:
|
|
92
124
|
|
|
93
|
-
```
|
|
94
|
-
|
|
125
|
+
```env
|
|
126
|
+
BP_ENV=uat # 环境 (uat/prod)
|
|
127
|
+
BP_USER=<phone> # 手机号(用于自动重登)
|
|
128
|
+
BP_PASSWORD=<password> # 密码(用于自动重登)
|
|
129
|
+
ACCESS_TOKEN=<token> # 当前访问令牌
|
|
130
|
+
REFRESH_TOKEN=<refresh_token> # 刷新令牌
|
|
131
|
+
TOKEN_EXPIRES_IN=<timestamp> # Token 过期时间戳
|
|
95
132
|
```
|
|
96
133
|
|
|
97
|
-
|
|
134
|
+
**Token 自动刷新**:
|
|
98
135
|
|
|
99
|
-
-
|
|
136
|
+
- Token 有效期默认 2 小时
|
|
137
|
+
- CLI 检测到 Token 即将过期(剩余 < 5 分钟)时,会自动用保存的账号密码重新登录
|
|
138
|
+
- 无需手动干预,命令会自动执行
|
|
100
139
|
|
|
101
|
-
|
|
140
|
+
**安全性说明**:
|
|
102
141
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
BP_ENV=uat
|
|
107
|
-
BP_USER=<phone>
|
|
108
|
-
BP_PASSWORD=<password>
|
|
109
|
-
ACCESS_TOKEN=<token>
|
|
110
|
-
REFRESH_TOKEN=<refresh_token>
|
|
111
|
-
TOKEN_EXPIRES_IN=<timestamp>
|
|
112
|
-
```
|
|
142
|
+
- 凭据以明文存储在本地 `.env` 文件中
|
|
143
|
+
- 请勿在共享环境中使用,或确保 `~/.byteplan/` 目录权限受限
|
|
144
|
+
- 如需清除凭据:`rm ~/.byteplan/.env`
|
|
113
145
|
|
|
114
146
|
## License
|
|
115
147
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
import { Command } from 'commander';
|
|
8
8
|
import { createRequire } from 'module';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { existsSync, readFileSync } from 'fs';
|
|
9
12
|
import {
|
|
10
13
|
login,
|
|
11
14
|
loginWithEnv,
|
|
@@ -23,6 +26,9 @@ import { skillsCmd } from './commands/skills.js';
|
|
|
23
26
|
const require = createRequire(import.meta.url);
|
|
24
27
|
const { version } = require('../package.json');
|
|
25
28
|
|
|
29
|
+
const configDir = join(homedir(), '.byteplan');
|
|
30
|
+
const envFile = join(configDir, '.env');
|
|
31
|
+
|
|
26
32
|
function printJSON(data) {
|
|
27
33
|
console.log(JSON.stringify(data, null, 2));
|
|
28
34
|
}
|
|
@@ -32,6 +38,36 @@ function printError(error) {
|
|
|
32
38
|
process.exit(1);
|
|
33
39
|
}
|
|
34
40
|
|
|
41
|
+
// 检查是否已有配置
|
|
42
|
+
function checkExistingConfig() {
|
|
43
|
+
if (!existsSync(envFile)) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const content = readFileSync(envFile, 'utf-8');
|
|
48
|
+
const lines = content.split('\n');
|
|
49
|
+
const config = {};
|
|
50
|
+
|
|
51
|
+
for (const line of lines) {
|
|
52
|
+
if (line.startsWith('BP_USER=')) {
|
|
53
|
+
config.user = line.split('=')[1];
|
|
54
|
+
}
|
|
55
|
+
if (line.startsWith('BP_ENV=')) {
|
|
56
|
+
config.env = line.split('=')[1];
|
|
57
|
+
}
|
|
58
|
+
if (line.startsWith('ACCESS_TOKEN=')) {
|
|
59
|
+
config.hasToken = true;
|
|
60
|
+
}
|
|
61
|
+
if (line.startsWith('TOKEN_EXPIRES_IN=')) {
|
|
62
|
+
const expiresAt = parseInt(line.split('=')[1]);
|
|
63
|
+
config.tokenExpiresAt = expiresAt;
|
|
64
|
+
config.tokenExpired = expiresAt < Date.now();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return config;
|
|
69
|
+
}
|
|
70
|
+
|
|
35
71
|
const program = new Command();
|
|
36
72
|
|
|
37
73
|
program
|
|
@@ -40,20 +76,102 @@ program
|
|
|
40
76
|
.version(version)
|
|
41
77
|
.option('-e, --env <environment>', 'Environment (uat, prod)', 'uat');
|
|
42
78
|
|
|
79
|
+
// Config command - 检查配置状态
|
|
80
|
+
program
|
|
81
|
+
.command('config')
|
|
82
|
+
.description('Check current configuration status')
|
|
83
|
+
.action(() => {
|
|
84
|
+
try {
|
|
85
|
+
const config = checkExistingConfig();
|
|
86
|
+
|
|
87
|
+
if (!config) {
|
|
88
|
+
printJSON({
|
|
89
|
+
configured: false,
|
|
90
|
+
configFile: envFile,
|
|
91
|
+
message: 'No configuration found. Please run: byteplan login -u <phone> -p <password>'
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
printJSON({
|
|
97
|
+
configured: true,
|
|
98
|
+
configFile: envFile,
|
|
99
|
+
user: config.user || '',
|
|
100
|
+
env: config.env || 'uat',
|
|
101
|
+
hasToken: config.hasToken || false,
|
|
102
|
+
tokenExpired: config.tokenExpired || false,
|
|
103
|
+
tokenExpiresAt: config.tokenExpiresAt ? new Date(config.tokenExpiresAt).toISOString() : null,
|
|
104
|
+
message: config.tokenExpired
|
|
105
|
+
? 'Token expired. Will auto-refresh on next command.'
|
|
106
|
+
: 'Configuration is valid.'
|
|
107
|
+
});
|
|
108
|
+
} catch (error) {
|
|
109
|
+
printError(error);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
43
113
|
// Login command
|
|
44
114
|
program
|
|
45
115
|
.command('login')
|
|
46
|
-
.description('Login to BytePlan')
|
|
47
|
-
.
|
|
48
|
-
.
|
|
116
|
+
.description('Login to BytePlan (use saved credentials if not provided)')
|
|
117
|
+
.option('-u, --user <phone>', 'Phone number (optional if already configured)')
|
|
118
|
+
.option('-p, --password <password>', 'Password (optional if already configured)')
|
|
49
119
|
.action(async (options) => {
|
|
50
120
|
try {
|
|
121
|
+
const existingConfig = checkExistingConfig();
|
|
51
122
|
const env = program.opts().env;
|
|
123
|
+
|
|
124
|
+
// 没有提供账号密码,使用已保存的配置
|
|
125
|
+
if (!options.user && !options.password) {
|
|
126
|
+
if (!existingConfig) {
|
|
127
|
+
printJSON({
|
|
128
|
+
error: true,
|
|
129
|
+
message: 'No saved credentials. Please provide user and password.',
|
|
130
|
+
hint: 'byteplan login -u <phone> -p <password>'
|
|
131
|
+
});
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 使用保存的凭据重新登录
|
|
136
|
+
const result = await loginWithEnv(true);
|
|
137
|
+
const userInfo = await getUserInfo();
|
|
138
|
+
|
|
139
|
+
printJSON({
|
|
140
|
+
success: true,
|
|
141
|
+
source: 'saved_credentials',
|
|
142
|
+
user: {
|
|
143
|
+
userId: userInfo.user?.id || '',
|
|
144
|
+
userName: userInfo.user?.userName || '',
|
|
145
|
+
userPhone: userInfo.user?.mobile || existingConfig.user,
|
|
146
|
+
email: userInfo.user?.email || '',
|
|
147
|
+
},
|
|
148
|
+
tenant: {
|
|
149
|
+
tenantId: userInfo.user?.tenantId || '',
|
|
150
|
+
tenantName: userInfo.user?.tenantName || '',
|
|
151
|
+
tenantCode: userInfo.user?.tenantCode || '',
|
|
152
|
+
},
|
|
153
|
+
expiresIn: result.expires_in,
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 提供了账号密码(直接覆盖)
|
|
159
|
+
if (!options.user || !options.password) {
|
|
160
|
+
printJSON({
|
|
161
|
+
error: true,
|
|
162
|
+
message: 'Both user and password are required when providing credentials.',
|
|
163
|
+
hint: 'byteplan login -u <phone> -p <password>'
|
|
164
|
+
});
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 新登录(覆盖已有配置)
|
|
52
169
|
const result = await login(options.user, options.password, env);
|
|
53
170
|
const userInfo = await getUserInfo(result.access_token);
|
|
54
171
|
|
|
55
172
|
printJSON({
|
|
56
173
|
success: true,
|
|
174
|
+
source: 'new_credentials',
|
|
57
175
|
user: {
|
|
58
176
|
userId: userInfo.user?.id || '',
|
|
59
177
|
userName: userInfo.user?.userName || '',
|
|
@@ -66,6 +184,7 @@ program
|
|
|
66
184
|
tenantCode: userInfo.user?.tenantCode || '',
|
|
67
185
|
},
|
|
68
186
|
expiresIn: result.expires_in,
|
|
187
|
+
overwritten: existingConfig ? true : false,
|
|
69
188
|
});
|
|
70
189
|
} catch (error) {
|
|
71
190
|
printError(error);
|
package/src/commands/skills.js
CHANGED
|
@@ -12,6 +12,44 @@ import { createRequire } from 'module';
|
|
|
12
12
|
|
|
13
13
|
const require = createRequire(import.meta.url);
|
|
14
14
|
|
|
15
|
+
// 配置文件路径
|
|
16
|
+
const configDir = path.join(homedir(), '.byteplan');
|
|
17
|
+
const envFile = path.join(configDir, '.env');
|
|
18
|
+
|
|
19
|
+
// 检查配置状态
|
|
20
|
+
function checkConfig() {
|
|
21
|
+
if (!existsSync(envFile)) {
|
|
22
|
+
return { configured: false };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const content = readFileSync(envFile, 'utf-8');
|
|
26
|
+
const config = { configured: true };
|
|
27
|
+
|
|
28
|
+
for (const line of content.split('\n')) {
|
|
29
|
+
if (line.startsWith('BP_USER=')) config.user = line.split('=')[1];
|
|
30
|
+
if (line.startsWith('BP_ENV=')) config.env = line.split('=')[1];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return config;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 展开 ~ 路径,支持跨平台
|
|
37
|
+
function expandHomeDir(dirPath) {
|
|
38
|
+
if (!dirPath) return dirPath;
|
|
39
|
+
|
|
40
|
+
// 处理 ~ 开头的路径
|
|
41
|
+
if (dirPath.startsWith('~')) {
|
|
42
|
+
return path.join(homedir(), dirPath.slice(1));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Windows: 处理 %USERPROFILE% 环境变量
|
|
46
|
+
if (dirPath.includes('%USERPROFILE%')) {
|
|
47
|
+
return dirPath.replace('%USERPROFILE%', homedir());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return dirPath;
|
|
51
|
+
}
|
|
52
|
+
|
|
15
53
|
// 平台默认目录映射
|
|
16
54
|
const PLATFORM_DIRS = {
|
|
17
55
|
'claude-code': path.join(homedir(), '.claude', 'commands'),
|
|
@@ -22,24 +60,20 @@ const PLATFORM_DIRS = {
|
|
|
22
60
|
|
|
23
61
|
// Skills 源目录(在 npm 包中)
|
|
24
62
|
const getSkillsSourceDir = () => {
|
|
25
|
-
// 获取当前模块所在目录,然后向上找到项目根目录
|
|
26
63
|
const currentDir = path.dirname(new URL(import.meta.url).pathname);
|
|
27
|
-
// 从 src/commands/skills.js -> src -> 项目根目录 -> skills
|
|
28
64
|
return path.join(currentDir, '..', '..', 'skills');
|
|
29
65
|
};
|
|
30
66
|
|
|
31
|
-
//
|
|
67
|
+
// 解析平台和目录参数
|
|
32
68
|
function resolveTargetDirs(platformArg, dirArg) {
|
|
33
69
|
const targets = [];
|
|
70
|
+
const expandedDirArg = expandHomeDir(dirArg);
|
|
34
71
|
|
|
35
72
|
if (platformArg) {
|
|
36
|
-
// 解析平台列表(逗号分隔)
|
|
37
73
|
const platforms = platformArg.split(',').map(p => p.trim());
|
|
38
|
-
|
|
39
74
|
for (const platform of platforms) {
|
|
40
75
|
const normalizedName = platform.toLowerCase();
|
|
41
76
|
const defaultDir = PLATFORM_DIRS[normalizedName];
|
|
42
|
-
|
|
43
77
|
if (!defaultDir) {
|
|
44
78
|
return {
|
|
45
79
|
error: true,
|
|
@@ -47,20 +81,17 @@ function resolveTargetDirs(platformArg, dirArg) {
|
|
|
47
81
|
available: Object.keys(PLATFORM_DIRS).filter(k => !k.includes('-')),
|
|
48
82
|
};
|
|
49
83
|
}
|
|
50
|
-
|
|
51
84
|
targets.push({
|
|
52
85
|
platform: normalizedName,
|
|
53
|
-
targetDir:
|
|
86
|
+
targetDir: expandedDirArg || defaultDir,
|
|
54
87
|
});
|
|
55
88
|
}
|
|
56
|
-
} else if (
|
|
57
|
-
// 向后兼容:只指定 -d 的情况
|
|
89
|
+
} else if (expandedDirArg) {
|
|
58
90
|
targets.push({
|
|
59
91
|
platform: 'custom',
|
|
60
|
-
targetDir:
|
|
92
|
+
targetDir: expandedDirArg,
|
|
61
93
|
});
|
|
62
94
|
} else {
|
|
63
|
-
// 都不指定,返回错误
|
|
64
95
|
return {
|
|
65
96
|
error: true,
|
|
66
97
|
message: 'Must specify at least one of --platform or --dir',
|
|
@@ -82,17 +113,11 @@ function printError(error) {
|
|
|
82
113
|
// 获取 skill 信息
|
|
83
114
|
function getSkillInfo(skillPath) {
|
|
84
115
|
const skillMdPath = path.join(skillPath, 'SKILL.md');
|
|
85
|
-
if (!existsSync(skillMdPath))
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
116
|
+
if (!existsSync(skillMdPath)) return null;
|
|
88
117
|
|
|
89
118
|
const content = readFileSync(skillMdPath, 'utf-8');
|
|
90
|
-
|
|
91
|
-
// 解析 frontmatter
|
|
92
119
|
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
93
|
-
if (!frontmatterMatch)
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
120
|
+
if (!frontmatterMatch) return null;
|
|
96
121
|
|
|
97
122
|
const frontmatter = frontmatterMatch[1];
|
|
98
123
|
const nameMatch = frontmatter.match(/name:\s*(.+)/);
|
|
@@ -101,283 +126,213 @@ function getSkillInfo(skillPath) {
|
|
|
101
126
|
return {
|
|
102
127
|
name: nameMatch ? nameMatch[1].trim() : path.basename(skillPath),
|
|
103
128
|
description: descMatch ? descMatch[1].trim() : '',
|
|
104
|
-
path: skillPath,
|
|
105
129
|
};
|
|
106
130
|
}
|
|
107
131
|
|
|
108
132
|
// 获取所有可用 skills
|
|
109
133
|
function getAvailableSkills() {
|
|
110
134
|
const sourceDir = getSkillsSourceDir();
|
|
111
|
-
if (!existsSync(sourceDir))
|
|
112
|
-
return [];
|
|
113
|
-
}
|
|
135
|
+
if (!existsSync(sourceDir)) return [];
|
|
114
136
|
|
|
115
137
|
const skills = [];
|
|
116
138
|
const dirs = readdirSync(sourceDir, { withFileTypes: true });
|
|
117
|
-
|
|
118
139
|
for (const dir of dirs) {
|
|
119
140
|
if (dir.isDirectory()) {
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
if (info) {
|
|
123
|
-
skills.push(info);
|
|
124
|
-
}
|
|
141
|
+
const info = getSkillInfo(path.join(sourceDir, dir.name));
|
|
142
|
+
if (info) skills.push(info);
|
|
125
143
|
}
|
|
126
144
|
}
|
|
127
|
-
|
|
128
145
|
return skills;
|
|
129
146
|
}
|
|
130
147
|
|
|
131
|
-
// 获取已安装的 skills
|
|
148
|
+
// 获取已安装的 skills
|
|
132
149
|
function getInstalledSkills(targetDir) {
|
|
133
|
-
if (!existsSync(targetDir))
|
|
134
|
-
return [];
|
|
135
|
-
}
|
|
150
|
+
if (!existsSync(targetDir)) return [];
|
|
136
151
|
|
|
137
152
|
const skills = [];
|
|
138
153
|
const dirs = readdirSync(targetDir, { withFileTypes: true });
|
|
139
|
-
|
|
140
154
|
for (const dir of dirs) {
|
|
141
155
|
if (dir.isDirectory() && dir.name.startsWith('byteplan-')) {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
if (info) {
|
|
145
|
-
skills.push(info);
|
|
146
|
-
}
|
|
156
|
+
const info = getSkillInfo(path.join(targetDir, dir.name));
|
|
157
|
+
if (info) skills.push(info);
|
|
147
158
|
}
|
|
148
159
|
}
|
|
149
|
-
|
|
150
160
|
return skills;
|
|
151
161
|
}
|
|
152
162
|
|
|
163
|
+
// 安装所有 skills
|
|
164
|
+
function installAllSkills(targetDirs) {
|
|
165
|
+
const sourceDir = getSkillsSourceDir();
|
|
166
|
+
const availableSkills = getAvailableSkills();
|
|
167
|
+
const toInstall = availableSkills.map(s => s.name);
|
|
168
|
+
const results = {};
|
|
169
|
+
|
|
170
|
+
for (const target of targetDirs) {
|
|
171
|
+
if (!existsSync(target.targetDir)) {
|
|
172
|
+
mkdirSync(target.targetDir, { recursive: true });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const installed = [];
|
|
176
|
+
for (const skillName of toInstall) {
|
|
177
|
+
cpSync(
|
|
178
|
+
path.join(sourceDir, skillName),
|
|
179
|
+
path.join(target.targetDir, skillName),
|
|
180
|
+
{ recursive: true }
|
|
181
|
+
);
|
|
182
|
+
installed.push(skillName);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
results[target.platform] = {
|
|
186
|
+
targetDir: target.targetDir,
|
|
187
|
+
installed: installed,
|
|
188
|
+
count: installed.length,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return { results, totalSkills: toInstall.length };
|
|
193
|
+
}
|
|
194
|
+
|
|
153
195
|
// Skills 命令
|
|
154
196
|
const skillsCmd = new Command('skills')
|
|
155
|
-
.description('Manage BytePlan
|
|
156
|
-
.option('-
|
|
157
|
-
.option('-
|
|
197
|
+
.description('Manage BytePlan skills')
|
|
198
|
+
.option('-p, --platform <platforms>', 'Target platform(s): claude-code, codex, openclaw (comma-separated)')
|
|
199
|
+
.option('-d, --dir <directory>', 'Custom target directory')
|
|
200
|
+
.option('--check-config', 'Check configuration status before install', false)
|
|
158
201
|
.addHelpText('after', `
|
|
159
202
|
Commands:
|
|
160
|
-
skills
|
|
161
|
-
skills
|
|
162
|
-
skills
|
|
163
|
-
skills install-all Install all BytePlan skills (requires -p or -d)
|
|
203
|
+
skills install Install all BytePlan skills
|
|
204
|
+
skills list List all available skills
|
|
205
|
+
skills installed List installed skills
|
|
164
206
|
|
|
165
|
-
|
|
166
|
-
claude-code (
|
|
167
|
-
codex
|
|
168
|
-
openclaw
|
|
207
|
+
Platforms:
|
|
208
|
+
claude-code (claude) ~/.claude/commands
|
|
209
|
+
codex ~/.codex
|
|
210
|
+
openclaw ~/.openclaw/skills
|
|
169
211
|
|
|
170
212
|
Examples:
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
$ byteplan skills install byteplan-api byteplan-analysis -p claude-code,codex
|
|
175
|
-
$ byteplan skills install-all -p claude-code,openclaw
|
|
176
|
-
$ byteplan skills install byteplan-api -d ~/.claude/commands
|
|
177
|
-
$ byteplan skills install byteplan-api -p claude-code -d /custom/path
|
|
213
|
+
byteplan skills install -p claude-code
|
|
214
|
+
byteplan skills install -p claude-code,codex,openclaw
|
|
215
|
+
byteplan skills list
|
|
178
216
|
`);
|
|
179
217
|
|
|
180
|
-
// skills
|
|
218
|
+
// skills install 子命令 - 安装全部
|
|
181
219
|
skillsCmd
|
|
182
|
-
.command('
|
|
183
|
-
.description('
|
|
184
|
-
.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
skills: skills.map(s => ({
|
|
190
|
-
name: s.name,
|
|
191
|
-
description: s.description,
|
|
192
|
-
})),
|
|
193
|
-
total: skills.length,
|
|
194
|
-
sourceDir: getSkillsSourceDir(),
|
|
195
|
-
});
|
|
196
|
-
} catch (error) {
|
|
197
|
-
printError(error);
|
|
198
|
-
}
|
|
199
|
-
});
|
|
220
|
+
.command('install')
|
|
221
|
+
.description('Install all BytePlan skills to target platform(s)')
|
|
222
|
+
.addHelpText('after', `
|
|
223
|
+
Options (from parent):
|
|
224
|
+
-p, --platform Target platform(s): claude-code, codex, openclaw
|
|
225
|
+
-d, --dir Custom target directory
|
|
226
|
+
--check-config Check configuration status first
|
|
200
227
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
228
|
+
Examples:
|
|
229
|
+
byteplan skills install -p claude-code
|
|
230
|
+
byteplan skills install -p claude-code,codex
|
|
231
|
+
byteplan skills install -d ~/.claude/commands
|
|
232
|
+
`)
|
|
205
233
|
.action(() => {
|
|
206
234
|
try {
|
|
235
|
+
// 检查配置状态
|
|
236
|
+
const config = checkConfig();
|
|
207
237
|
const parentOptions = skillsCmd.opts();
|
|
208
|
-
const platformArg = parentOptions.platform;
|
|
209
|
-
const dirArg = parentOptions.dir;
|
|
210
238
|
|
|
211
|
-
|
|
239
|
+
// 如果请求检查配置或未配置,显示配置状态
|
|
240
|
+
if (parentOptions.checkConfig || !config.configured) {
|
|
241
|
+
printJSON({
|
|
242
|
+
config: config.configured ? {
|
|
243
|
+
configured: true,
|
|
244
|
+
user: config.user,
|
|
245
|
+
env: config.env
|
|
246
|
+
} : {
|
|
247
|
+
configured: false,
|
|
248
|
+
configFile: envFile
|
|
249
|
+
},
|
|
250
|
+
hint: config.configured
|
|
251
|
+
? 'Configuration found. Run without --check-config to install.'
|
|
252
|
+
: 'Please login first: byteplan login -u <phone> -p <password>'
|
|
253
|
+
});
|
|
254
|
+
if (!config.configured) process.exit(1);
|
|
255
|
+
if (parentOptions.checkConfig) process.exit(0);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const resolved = resolveTargetDirs(parentOptions.platform, parentOptions.dir);
|
|
259
|
+
|
|
212
260
|
if (resolved.error) {
|
|
213
261
|
printJSON(resolved);
|
|
214
262
|
process.exit(1);
|
|
215
263
|
}
|
|
216
264
|
|
|
217
|
-
const results =
|
|
218
|
-
for (const target of resolved.targets) {
|
|
219
|
-
const skills = getInstalledSkills(target.targetDir);
|
|
220
|
-
results[target.platform] = {
|
|
221
|
-
targetDir: target.targetDir,
|
|
222
|
-
skills: skills.map(s => ({
|
|
223
|
-
name: s.name,
|
|
224
|
-
description: s.description,
|
|
225
|
-
})),
|
|
226
|
-
count: skills.length,
|
|
227
|
-
};
|
|
228
|
-
}
|
|
265
|
+
const { results, totalSkills } = installAllSkills(resolved.targets);
|
|
229
266
|
|
|
230
267
|
printJSON({
|
|
268
|
+
success: true,
|
|
269
|
+
config: { user: config.user, env: config.env },
|
|
231
270
|
platforms: results,
|
|
232
271
|
totalPlatforms: resolved.targets.length,
|
|
272
|
+
totalSkills: totalSkills,
|
|
233
273
|
});
|
|
234
274
|
} catch (error) {
|
|
235
275
|
printError(error);
|
|
236
276
|
}
|
|
237
277
|
});
|
|
238
278
|
|
|
239
|
-
// skills
|
|
279
|
+
// skills list 子命令
|
|
240
280
|
skillsCmd
|
|
241
|
-
.command('
|
|
242
|
-
.description('
|
|
243
|
-
.
|
|
244
|
-
.addHelpText('after', `
|
|
245
|
-
Examples:
|
|
246
|
-
$ byteplan skills install byteplan-api -p claude-code
|
|
247
|
-
$ byteplan skills install byteplan-api byteplan-analysis -p claude-code,codex
|
|
248
|
-
$ byteplan skills install --all -p claude-code
|
|
249
|
-
$ byteplan skills install byteplan-api -d ~/.claude/commands
|
|
250
|
-
$ byteplan skills install byteplan-api -p claude-code -d /custom/path
|
|
251
|
-
`)
|
|
252
|
-
.action(async (names, options) => {
|
|
281
|
+
.command('list')
|
|
282
|
+
.description('List all available BytePlan skills')
|
|
283
|
+
.action(() => {
|
|
253
284
|
try {
|
|
254
|
-
const
|
|
255
|
-
const
|
|
256
|
-
const dirArg = parentOptions.dir;
|
|
257
|
-
|
|
258
|
-
const resolved = resolveTargetDirs(platformArg, dirArg);
|
|
259
|
-
if (resolved.error) {
|
|
260
|
-
printJSON(resolved);
|
|
261
|
-
process.exit(1);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const sourceDir = getSkillsSourceDir();
|
|
265
|
-
|
|
266
|
-
// 获取要安装的 skills
|
|
267
|
-
let toInstall = names || [];
|
|
268
|
-
if (options.all || toInstall.length === 0) {
|
|
269
|
-
const available = getAvailableSkills();
|
|
270
|
-
toInstall = available.map(s => s.name);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// 验证 skills 存在
|
|
274
|
-
const availableSkills = getAvailableSkills();
|
|
275
|
-
const availableNames = availableSkills.map(s => s.name);
|
|
276
|
-
|
|
277
|
-
const invalid = toInstall.filter(n => !availableNames.includes(n));
|
|
278
|
-
if (invalid.length > 0) {
|
|
279
|
-
printJSON({
|
|
280
|
-
error: true,
|
|
281
|
-
message: `Unknown skills: ${invalid.join(', ')}`,
|
|
282
|
-
available: availableNames,
|
|
283
|
-
});
|
|
284
|
-
process.exit(1);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// 安装到各目标目录
|
|
288
|
-
const results = {};
|
|
289
|
-
for (const target of resolved.targets) {
|
|
290
|
-
const targetDir = target.targetDir;
|
|
291
|
-
|
|
292
|
-
// 确保目标目录存在
|
|
293
|
-
if (!existsSync(targetDir)) {
|
|
294
|
-
mkdirSync(targetDir, { recursive: true });
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// 安装 skills
|
|
298
|
-
const installed = [];
|
|
299
|
-
for (const skillName of toInstall) {
|
|
300
|
-
const skillSourcePath = path.join(sourceDir, skillName);
|
|
301
|
-
const skillTargetPath = path.join(targetDir, skillName);
|
|
302
|
-
|
|
303
|
-
// 复制 skill 目录
|
|
304
|
-
cpSync(skillSourcePath, skillTargetPath, { recursive: true });
|
|
305
|
-
installed.push(skillName);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
results[target.platform] = {
|
|
309
|
-
targetDir: targetDir,
|
|
310
|
-
installed: installed,
|
|
311
|
-
count: installed.length,
|
|
312
|
-
};
|
|
313
|
-
}
|
|
285
|
+
const config = checkConfig();
|
|
286
|
+
const skills = getAvailableSkills();
|
|
314
287
|
|
|
315
288
|
printJSON({
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
totalSkills: toInstall.length,
|
|
289
|
+
config: config.configured ? { user: config.user, env: config.env } : { configured: false },
|
|
290
|
+
skills: skills.map(s => ({ name: s.name, description: s.description })),
|
|
291
|
+
total: skills.length,
|
|
320
292
|
});
|
|
321
293
|
} catch (error) {
|
|
322
294
|
printError(error);
|
|
323
295
|
}
|
|
324
296
|
});
|
|
325
297
|
|
|
326
|
-
// skills
|
|
298
|
+
// skills installed 子命令
|
|
327
299
|
skillsCmd
|
|
328
|
-
.command('
|
|
329
|
-
.description('
|
|
330
|
-
.
|
|
300
|
+
.command('installed')
|
|
301
|
+
.description('List installed BytePlan skills')
|
|
302
|
+
.addHelpText('after', `
|
|
303
|
+
Options (from parent):
|
|
304
|
+
-p, --platform Target platform(s): claude-code, codex, openclaw
|
|
305
|
+
-d, --dir Custom target directory
|
|
306
|
+
|
|
307
|
+
Examples:
|
|
308
|
+
byteplan skills installed -p claude-code
|
|
309
|
+
byteplan skills installed -d ~/.claude/commands
|
|
310
|
+
`)
|
|
311
|
+
.action(() => {
|
|
331
312
|
try {
|
|
313
|
+
const config = checkConfig();
|
|
332
314
|
const parentOptions = skillsCmd.opts();
|
|
333
|
-
const
|
|
334
|
-
const dirArg = parentOptions.dir;
|
|
315
|
+
const resolved = resolveTargetDirs(parentOptions.platform, parentOptions.dir);
|
|
335
316
|
|
|
336
|
-
const resolved = resolveTargetDirs(platformArg, dirArg);
|
|
337
317
|
if (resolved.error) {
|
|
338
318
|
printJSON(resolved);
|
|
339
319
|
process.exit(1);
|
|
340
320
|
}
|
|
341
321
|
|
|
342
|
-
const sourceDir = getSkillsSourceDir();
|
|
343
|
-
|
|
344
|
-
// 获取所有 skills
|
|
345
|
-
const availableSkills = getAvailableSkills();
|
|
346
|
-
const toInstall = availableSkills.map(s => s.name);
|
|
347
|
-
|
|
348
|
-
// 安装到各目标目录
|
|
349
322
|
const results = {};
|
|
350
323
|
for (const target of resolved.targets) {
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
// 确保目标目录存在
|
|
354
|
-
if (!existsSync(targetDir)) {
|
|
355
|
-
mkdirSync(targetDir, { recursive: true });
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// 安装所有 skills
|
|
359
|
-
const installed = [];
|
|
360
|
-
for (const skillName of toInstall) {
|
|
361
|
-
const skillSourcePath = path.join(sourceDir, skillName);
|
|
362
|
-
const skillTargetPath = path.join(targetDir, skillName);
|
|
363
|
-
|
|
364
|
-
// 复制 skill 目录
|
|
365
|
-
cpSync(skillSourcePath, skillTargetPath, { recursive: true });
|
|
366
|
-
installed.push(skillName);
|
|
367
|
-
}
|
|
368
|
-
|
|
324
|
+
const skills = getInstalledSkills(target.targetDir);
|
|
369
325
|
results[target.platform] = {
|
|
370
|
-
targetDir: targetDir,
|
|
371
|
-
|
|
372
|
-
count:
|
|
326
|
+
targetDir: target.targetDir,
|
|
327
|
+
skills: skills.map(s => ({ name: s.name, description: s.description })),
|
|
328
|
+
count: skills.length,
|
|
373
329
|
};
|
|
374
330
|
}
|
|
375
331
|
|
|
376
332
|
printJSON({
|
|
377
|
-
|
|
333
|
+
config: config.configured ? { user: config.user, env: config.env } : { configured: false },
|
|
378
334
|
platforms: results,
|
|
379
335
|
totalPlatforms: resolved.targets.length,
|
|
380
|
-
totalSkills: toInstall.length,
|
|
381
336
|
});
|
|
382
337
|
} catch (error) {
|
|
383
338
|
printError(error);
|