byteplan-cli 1.3.3 → 1.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -7
- package/package.json +1 -1
- package/src/api.js +1 -1
- package/src/cli.js +136 -3
- package/src/commands/skills.js +64 -6
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
|
|
@@ -89,17 +118,31 @@ byteplan skills install -d ~/.claude/commands
|
|
|
89
118
|
|
|
90
119
|
## Configuration
|
|
91
120
|
|
|
92
|
-
|
|
121
|
+
`byteplan login` 会将凭据持久化保存到 `~/.byteplan/.env`,后续命令无需重复登录。
|
|
122
|
+
|
|
123
|
+
**保存的内容**:
|
|
93
124
|
|
|
94
125
|
```env
|
|
95
|
-
BP_ENV=uat
|
|
96
|
-
BP_USER=<phone>
|
|
97
|
-
BP_PASSWORD=<password>
|
|
98
|
-
ACCESS_TOKEN=<token>
|
|
99
|
-
REFRESH_TOKEN=<refresh_token>
|
|
100
|
-
TOKEN_EXPIRES_IN=<timestamp>
|
|
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 过期时间戳
|
|
101
132
|
```
|
|
102
133
|
|
|
134
|
+
**Token 自动刷新**:
|
|
135
|
+
|
|
136
|
+
- Token 有效期默认 2 小时
|
|
137
|
+
- CLI 检测到 Token 即将过期(剩余 < 5 分钟)时,会自动用保存的账号密码重新登录
|
|
138
|
+
- 无需手动干预,命令会自动执行
|
|
139
|
+
|
|
140
|
+
**安全性说明**:
|
|
141
|
+
|
|
142
|
+
- 凭据以明文存储在本地 `.env` 文件中
|
|
143
|
+
- 请勿在共享环境中使用,或确保 `~/.byteplan/` 目录权限受限
|
|
144
|
+
- 如需清除凭据:`rm ~/.byteplan/.env`
|
|
145
|
+
|
|
103
146
|
## License
|
|
104
147
|
|
|
105
148
|
MIT
|
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -115,7 +115,7 @@ function saveCredentials(data) {
|
|
|
115
115
|
const lines = [];
|
|
116
116
|
lines.push('BP_ENV=' + currentEnv);
|
|
117
117
|
lines.push('BP_USER=' + data.username);
|
|
118
|
-
lines.push('BP_PASSWORD=' + data.password);
|
|
118
|
+
lines.push('BP_PASSWORD="' + data.password + '"'); // 用双引号包裹,防止 # 等特殊字符被解析为注释
|
|
119
119
|
if (data.accessToken) {
|
|
120
120
|
lines.push('ACCESS_TOKEN=' + data.accessToken);
|
|
121
121
|
}
|
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,116 @@ 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 and save credentials')
|
|
117
|
+
.option('-u, --user <phone>', 'Phone number')
|
|
118
|
+
.option('-p, --password <password>', 'Password')
|
|
119
|
+
.addHelpText('after', `
|
|
120
|
+
Usage:
|
|
121
|
+
byteplan login # 使用已保存的凭据刷新 Token(无需参数)
|
|
122
|
+
byteplan login -u <phone> -p <pwd> # 新登录或覆盖已有凭据
|
|
123
|
+
|
|
124
|
+
说明:
|
|
125
|
+
- 首次使用需要提供账号密码: byteplan login -u 18256485741 -p yourpassword
|
|
126
|
+
- 登录成功后凭据保存到 ~/.byteplan/.env
|
|
127
|
+
- 后续可直接运行 byteplan login 刷新 Token(无需参数)
|
|
128
|
+
- 提供新账号密码会覆盖已有配置
|
|
129
|
+
|
|
130
|
+
检查配置状态:
|
|
131
|
+
byteplan config
|
|
132
|
+
`)
|
|
49
133
|
.action(async (options) => {
|
|
50
134
|
try {
|
|
135
|
+
const existingConfig = checkExistingConfig();
|
|
51
136
|
const env = program.opts().env;
|
|
137
|
+
|
|
138
|
+
// 没有提供账号密码,使用已保存的配置
|
|
139
|
+
if (!options.user && !options.password) {
|
|
140
|
+
if (!existingConfig) {
|
|
141
|
+
printJSON({
|
|
142
|
+
error: true,
|
|
143
|
+
message: 'No saved credentials. Please provide user and password.',
|
|
144
|
+
hint: 'byteplan login -u <phone> -p <password>'
|
|
145
|
+
});
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 使用保存的凭据重新登录
|
|
150
|
+
const result = await loginWithEnv(true);
|
|
151
|
+
const userInfo = await getUserInfo();
|
|
152
|
+
|
|
153
|
+
printJSON({
|
|
154
|
+
success: true,
|
|
155
|
+
source: 'saved_credentials',
|
|
156
|
+
user: {
|
|
157
|
+
userId: userInfo.user?.id || '',
|
|
158
|
+
userName: userInfo.user?.userName || '',
|
|
159
|
+
userPhone: userInfo.user?.mobile || existingConfig.user,
|
|
160
|
+
email: userInfo.user?.email || '',
|
|
161
|
+
},
|
|
162
|
+
tenant: {
|
|
163
|
+
tenantId: userInfo.user?.tenantId || '',
|
|
164
|
+
tenantName: userInfo.user?.tenantName || '',
|
|
165
|
+
tenantCode: userInfo.user?.tenantCode || '',
|
|
166
|
+
},
|
|
167
|
+
expiresIn: result.expires_in,
|
|
168
|
+
});
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 提供了账号密码(直接覆盖)
|
|
173
|
+
if (!options.user || !options.password) {
|
|
174
|
+
printJSON({
|
|
175
|
+
error: true,
|
|
176
|
+
message: 'Both user and password are required when providing credentials.',
|
|
177
|
+
hint: 'byteplan login -u <phone> -p <password>'
|
|
178
|
+
});
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 新登录(覆盖已有配置)
|
|
52
183
|
const result = await login(options.user, options.password, env);
|
|
53
184
|
const userInfo = await getUserInfo(result.access_token);
|
|
54
185
|
|
|
55
186
|
printJSON({
|
|
56
187
|
success: true,
|
|
188
|
+
source: 'new_credentials',
|
|
57
189
|
user: {
|
|
58
190
|
userId: userInfo.user?.id || '',
|
|
59
191
|
userName: userInfo.user?.userName || '',
|
|
@@ -66,6 +198,7 @@ program
|
|
|
66
198
|
tenantCode: userInfo.user?.tenantCode || '',
|
|
67
199
|
},
|
|
68
200
|
expiresIn: result.expires_in,
|
|
201
|
+
overwritten: existingConfig ? true : false,
|
|
69
202
|
});
|
|
70
203
|
} catch (error) {
|
|
71
204
|
printError(error);
|
package/src/commands/skills.js
CHANGED
|
@@ -12,6 +12,27 @@ 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
|
+
|
|
15
36
|
// 展开 ~ 路径,支持跨平台
|
|
16
37
|
function expandHomeDir(dirPath) {
|
|
17
38
|
if (!dirPath) return dirPath;
|
|
@@ -173,24 +194,31 @@ function installAllSkills(targetDirs) {
|
|
|
173
194
|
|
|
174
195
|
// Skills 命令
|
|
175
196
|
const skillsCmd = new Command('skills')
|
|
176
|
-
.description('Manage BytePlan skills
|
|
197
|
+
.description('Manage BytePlan skills')
|
|
177
198
|
.option('-p, --platform <platforms>', 'Target platform(s): claude-code, codex, openclaw (comma-separated)')
|
|
178
199
|
.option('-d, --dir <directory>', 'Custom target directory')
|
|
200
|
+
.option('--check-config', 'Check configuration status before install', false)
|
|
179
201
|
.addHelpText('after', `
|
|
180
202
|
Commands:
|
|
181
203
|
skills install Install all BytePlan skills
|
|
182
204
|
skills list List all available skills
|
|
183
205
|
skills installed List installed skills
|
|
184
206
|
|
|
207
|
+
说明:
|
|
208
|
+
- 所有命令会自动检查配置状态(~/.byteplan/.env)
|
|
209
|
+
- 如未配置,会提示先运行: byteplan login -u <phone> -p <password>
|
|
210
|
+
- --check-config 仅检查配置不执行安装
|
|
211
|
+
|
|
185
212
|
Platforms:
|
|
186
213
|
claude-code (claude) ~/.claude/commands
|
|
187
214
|
codex ~/.codex
|
|
188
215
|
openclaw ~/.openclaw/skills
|
|
189
216
|
|
|
190
217
|
Examples:
|
|
191
|
-
byteplan skills
|
|
192
|
-
byteplan skills install -p claude-code
|
|
193
|
-
byteplan skills
|
|
218
|
+
byteplan skills list # 查看 skills 并显示配置状态
|
|
219
|
+
byteplan skills install -p claude-code # 安装到 claude-code
|
|
220
|
+
byteplan skills install -p claude-code,codex # 安装到多个平台
|
|
221
|
+
byteplan skills install --check-config # 仅检查配置
|
|
194
222
|
`);
|
|
195
223
|
|
|
196
224
|
// skills install 子命令 - 安装全部
|
|
@@ -199,17 +227,41 @@ skillsCmd
|
|
|
199
227
|
.description('Install all BytePlan skills to target platform(s)')
|
|
200
228
|
.addHelpText('after', `
|
|
201
229
|
Options (from parent):
|
|
202
|
-
-p, --platform
|
|
203
|
-
-d, --dir
|
|
230
|
+
-p, --platform Target platform(s): claude-code, codex, openclaw
|
|
231
|
+
-d, --dir Custom target directory
|
|
232
|
+
--check-config 仅检查配置状态,不执行安装
|
|
204
233
|
|
|
205
234
|
Examples:
|
|
206
235
|
byteplan skills install -p claude-code
|
|
207
236
|
byteplan skills install -p claude-code,codex
|
|
208
237
|
byteplan skills install -d ~/.claude/commands
|
|
238
|
+
byteplan skills install --check-config
|
|
209
239
|
`)
|
|
210
240
|
.action(() => {
|
|
211
241
|
try {
|
|
242
|
+
// 检查配置状态
|
|
243
|
+
const config = checkConfig();
|
|
212
244
|
const parentOptions = skillsCmd.opts();
|
|
245
|
+
|
|
246
|
+
// 如果请求检查配置或未配置,显示配置状态
|
|
247
|
+
if (parentOptions.checkConfig || !config.configured) {
|
|
248
|
+
printJSON({
|
|
249
|
+
config: config.configured ? {
|
|
250
|
+
configured: true,
|
|
251
|
+
user: config.user,
|
|
252
|
+
env: config.env
|
|
253
|
+
} : {
|
|
254
|
+
configured: false,
|
|
255
|
+
configFile: envFile
|
|
256
|
+
},
|
|
257
|
+
hint: config.configured
|
|
258
|
+
? 'Configuration found. Run without --check-config to install.'
|
|
259
|
+
: 'Please login first: byteplan login -u <phone> -p <password>'
|
|
260
|
+
});
|
|
261
|
+
if (!config.configured) process.exit(1);
|
|
262
|
+
if (parentOptions.checkConfig) process.exit(0);
|
|
263
|
+
}
|
|
264
|
+
|
|
213
265
|
const resolved = resolveTargetDirs(parentOptions.platform, parentOptions.dir);
|
|
214
266
|
|
|
215
267
|
if (resolved.error) {
|
|
@@ -221,6 +273,7 @@ Examples:
|
|
|
221
273
|
|
|
222
274
|
printJSON({
|
|
223
275
|
success: true,
|
|
276
|
+
config: { user: config.user, env: config.env },
|
|
224
277
|
platforms: results,
|
|
225
278
|
totalPlatforms: resolved.targets.length,
|
|
226
279
|
totalSkills: totalSkills,
|
|
@@ -236,8 +289,11 @@ skillsCmd
|
|
|
236
289
|
.description('List all available BytePlan skills')
|
|
237
290
|
.action(() => {
|
|
238
291
|
try {
|
|
292
|
+
const config = checkConfig();
|
|
239
293
|
const skills = getAvailableSkills();
|
|
294
|
+
|
|
240
295
|
printJSON({
|
|
296
|
+
config: config.configured ? { user: config.user, env: config.env } : { configured: false },
|
|
241
297
|
skills: skills.map(s => ({ name: s.name, description: s.description })),
|
|
242
298
|
total: skills.length,
|
|
243
299
|
});
|
|
@@ -261,6 +317,7 @@ Examples:
|
|
|
261
317
|
`)
|
|
262
318
|
.action(() => {
|
|
263
319
|
try {
|
|
320
|
+
const config = checkConfig();
|
|
264
321
|
const parentOptions = skillsCmd.opts();
|
|
265
322
|
const resolved = resolveTargetDirs(parentOptions.platform, parentOptions.dir);
|
|
266
323
|
|
|
@@ -280,6 +337,7 @@ Examples:
|
|
|
280
337
|
}
|
|
281
338
|
|
|
282
339
|
printJSON({
|
|
340
|
+
config: config.configured ? { user: config.user, env: config.env } : { configured: false },
|
|
283
341
|
platforms: results,
|
|
284
342
|
totalPlatforms: resolved.targets.length,
|
|
285
343
|
});
|