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 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
- Credentials are stored in `~/.byteplan/.env`:
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "byteplan-cli",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "description": "BytePlan CLI - Command line tool for BytePlan API",
5
5
  "keywords": [
6
6
  "byteplan",
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
- .requiredOption('-u, --user <phone>', 'Phone number')
48
- .requiredOption('-p, --password <password>', 'Password')
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);
@@ -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 (install, list, check installed)')
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 install -p claude-code
192
- byteplan skills install -p claude-code,codex,openclaw
193
- byteplan skills list
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 Target platform(s): claude-code, codex, openclaw
203
- -d, --dir Custom target directory
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
  });