byteplan-cli 1.3.3 → 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 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.4",
4
4
  "description": "BytePlan CLI - Command line tool for BytePlan API",
5
5
  "keywords": [
6
6
  "byteplan",
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
- .requiredOption('-u, --user <phone>', 'Phone number')
48
- .requiredOption('-p, --password <password>', 'Password')
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);
@@ -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,9 +194,10 @@ 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
@@ -199,8 +221,9 @@ skillsCmd
199
221
  .description('Install all BytePlan skills to target platform(s)')
200
222
  .addHelpText('after', `
201
223
  Options (from parent):
202
- -p, --platform Target platform(s): claude-code, codex, openclaw
203
- -d, --dir Custom target directory
224
+ -p, --platform Target platform(s): claude-code, codex, openclaw
225
+ -d, --dir Custom target directory
226
+ --check-config Check configuration status first
204
227
 
205
228
  Examples:
206
229
  byteplan skills install -p claude-code
@@ -209,7 +232,29 @@ Examples:
209
232
  `)
210
233
  .action(() => {
211
234
  try {
235
+ // 检查配置状态
236
+ const config = checkConfig();
212
237
  const parentOptions = skillsCmd.opts();
238
+
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
+
213
258
  const resolved = resolveTargetDirs(parentOptions.platform, parentOptions.dir);
214
259
 
215
260
  if (resolved.error) {
@@ -221,6 +266,7 @@ Examples:
221
266
 
222
267
  printJSON({
223
268
  success: true,
269
+ config: { user: config.user, env: config.env },
224
270
  platforms: results,
225
271
  totalPlatforms: resolved.targets.length,
226
272
  totalSkills: totalSkills,
@@ -236,8 +282,11 @@ skillsCmd
236
282
  .description('List all available BytePlan skills')
237
283
  .action(() => {
238
284
  try {
285
+ const config = checkConfig();
239
286
  const skills = getAvailableSkills();
287
+
240
288
  printJSON({
289
+ config: config.configured ? { user: config.user, env: config.env } : { configured: false },
241
290
  skills: skills.map(s => ({ name: s.name, description: s.description })),
242
291
  total: skills.length,
243
292
  });
@@ -261,6 +310,7 @@ Examples:
261
310
  `)
262
311
  .action(() => {
263
312
  try {
313
+ const config = checkConfig();
264
314
  const parentOptions = skillsCmd.opts();
265
315
  const resolved = resolveTargetDirs(parentOptions.platform, parentOptions.dir);
266
316
 
@@ -280,6 +330,7 @@ Examples:
280
330
  }
281
331
 
282
332
  printJSON({
333
+ config: config.configured ? { user: config.user, env: config.env } : { configured: false },
283
334
  platforms: results,
284
335
  totalPlatforms: resolved.targets.length,
285
336
  });