@optima-chat/bi-cli 0.2.0 → 0.3.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/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +51 -59
- package/dist/commands/auth.js.map +1 -1
- package/dist/config/index.d.ts +79 -11
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +250 -26
- package/dist/config/index.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/auth.ts +68 -65
- package/src/config/index.ts +320 -31
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuCpC,wBAAgB,iBAAiB,IAAI,OAAO,CAgP3C"}
|
package/dist/commands/auth.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Command } from 'commander';
|
|
|
2
2
|
import axios from 'axios';
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
-
import {
|
|
5
|
+
import { clearAuth, getConfig, saveTokens, saveUser, getConfigPath, ENV_CONFIG, } from '../config/index.js';
|
|
6
6
|
import { success, error, info } from '../utils/output.js';
|
|
7
7
|
export function createAuthCommand() {
|
|
8
8
|
const auth = new Command('auth').description('Authentication commands');
|
|
@@ -12,38 +12,26 @@ export function createAuthCommand() {
|
|
|
12
12
|
.description('Login with OAuth 2.0 Device Flow')
|
|
13
13
|
.option('--env <environment>', 'Environment (production|stage|development)', 'production')
|
|
14
14
|
.action(async (options) => {
|
|
15
|
-
const
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
production: 'https://auth.optima.chat',
|
|
19
|
-
stage: 'https://auth-stage.optima.chat',
|
|
20
|
-
development: 'http://localhost:4000',
|
|
21
|
-
};
|
|
22
|
-
const backendUrls = {
|
|
23
|
-
production: 'https://bi-api.optima.chat',
|
|
24
|
-
stage: 'https://bi-api-stage.optima.chat',
|
|
25
|
-
development: 'http://localhost:3001',
|
|
26
|
-
};
|
|
27
|
-
const authUrl = authUrls[env];
|
|
28
|
-
const backendUrl = backendUrls[env];
|
|
29
|
-
if (!authUrl || !backendUrl) {
|
|
15
|
+
const env = options.env;
|
|
16
|
+
// Validate environment
|
|
17
|
+
if (!['production', 'stage', 'development'].includes(env)) {
|
|
30
18
|
error(`Invalid environment: ${env}`);
|
|
31
19
|
process.exit(1);
|
|
32
20
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
setConfig('backendUrl', backendUrl);
|
|
21
|
+
const envConfig = ENV_CONFIG[env];
|
|
22
|
+
const { authUrl, clientId } = envConfig;
|
|
36
23
|
info(`Logging in to ${env} environment...`);
|
|
24
|
+
info(`Auth URL: ${authUrl}`);
|
|
37
25
|
try {
|
|
38
26
|
// Step 1: Request device code
|
|
39
27
|
const spinner = ora('Requesting device code...').start();
|
|
40
|
-
const deviceCodeRes = await axios.post(`${authUrl}/api/v1/oauth/device/authorize`, { client_id:
|
|
28
|
+
const deviceCodeRes = await axios.post(`${authUrl}/api/v1/oauth/device/authorize`, { client_id: clientId });
|
|
41
29
|
spinner.succeed('Device code received');
|
|
42
30
|
const { device_code, user_code, verification_uri, verification_uri_complete, expires_in, interval, } = deviceCodeRes.data;
|
|
43
31
|
// Use verification_uri_complete if available (includes code pre-filled)
|
|
44
32
|
const browserUrl = verification_uri_complete || verification_uri;
|
|
45
33
|
// Step 2: Display authorization instructions
|
|
46
|
-
console.log(chalk.bold('\n
|
|
34
|
+
console.log(chalk.bold('\n Authorization Required:\n'));
|
|
47
35
|
if (verification_uri_complete) {
|
|
48
36
|
console.log(` Opening browser with pre-filled code: ${chalk.yellow.bold(user_code)}`);
|
|
49
37
|
console.log(` URL: ${chalk.cyan(verification_uri_complete)}\n`);
|
|
@@ -68,7 +56,7 @@ export function createAuthCommand() {
|
|
|
68
56
|
try {
|
|
69
57
|
const tokenRes = await axios.post(`${authUrl}/api/v1/oauth/device/token`, new URLSearchParams({
|
|
70
58
|
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
71
|
-
client_id:
|
|
59
|
+
client_id: clientId,
|
|
72
60
|
device_code,
|
|
73
61
|
}), {
|
|
74
62
|
headers: {
|
|
@@ -97,21 +85,18 @@ export function createAuthCommand() {
|
|
|
97
85
|
break;
|
|
98
86
|
}
|
|
99
87
|
catch (err) {
|
|
100
|
-
const
|
|
101
|
-
const errorCode =
|
|
88
|
+
const axiosErr = err;
|
|
89
|
+
const errorCode = axiosErr.response?.data?.error;
|
|
102
90
|
if (errorCode === 'authorization_pending') {
|
|
103
|
-
// Continue polling
|
|
104
91
|
pollSpinner.text = `Waiting for authorization... (attempt ${pollCount})`;
|
|
105
92
|
continue;
|
|
106
93
|
}
|
|
107
94
|
else if (errorCode === 'slow_down') {
|
|
108
|
-
// Increase interval
|
|
109
95
|
pollSpinner.text = `Slowing down polling... (attempt ${pollCount})`;
|
|
110
96
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
111
97
|
continue;
|
|
112
98
|
}
|
|
113
99
|
else {
|
|
114
|
-
// Unexpected error
|
|
115
100
|
pollSpinner.fail(`Polling failed: ${errorCode || 'unknown error'}`);
|
|
116
101
|
throw err;
|
|
117
102
|
}
|
|
@@ -123,20 +108,26 @@ export function createAuthCommand() {
|
|
|
123
108
|
process.exit(1);
|
|
124
109
|
}
|
|
125
110
|
pollSpinner.succeed();
|
|
126
|
-
// Step 5:
|
|
127
|
-
|
|
128
|
-
setConfig('refreshToken', token.refresh_token);
|
|
129
|
-
// Step 6: Fetch user info (optional - just for display)
|
|
111
|
+
// Step 5: Fetch user info and save tokens
|
|
112
|
+
let userInfo;
|
|
130
113
|
try {
|
|
131
|
-
const
|
|
114
|
+
const userRes = await axios.get(`${authUrl}/api/v1/users/me`, {
|
|
132
115
|
headers: { Authorization: `Bearer ${token.access_token}` },
|
|
133
116
|
});
|
|
134
|
-
|
|
117
|
+
userInfo = userRes.data;
|
|
135
118
|
}
|
|
136
|
-
catch
|
|
137
|
-
//
|
|
138
|
-
success('Login successful! Token saved.');
|
|
119
|
+
catch {
|
|
120
|
+
// User info fetch failed, continue without it
|
|
139
121
|
}
|
|
122
|
+
// Step 6: Save tokens to ~/.optima/token.json
|
|
123
|
+
saveTokens(token.access_token, token.refresh_token, token.expires_in, env, userInfo ? { id: userInfo.id, email: userInfo.email, name: userInfo.name } : undefined);
|
|
124
|
+
if (userInfo) {
|
|
125
|
+
success(`Logged in as ${chalk.bold(userInfo.email)} (${userInfo.role})`);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
success('Login successful!');
|
|
129
|
+
}
|
|
130
|
+
info(`Token saved to ${getConfigPath()}`);
|
|
140
131
|
}
|
|
141
132
|
catch (err) {
|
|
142
133
|
const errorMsg = err instanceof Error ? err.message : 'Unknown error';
|
|
@@ -147,10 +138,11 @@ export function createAuthCommand() {
|
|
|
147
138
|
// auth logout
|
|
148
139
|
auth
|
|
149
140
|
.command('logout')
|
|
150
|
-
.description('Logout and
|
|
141
|
+
.description('Logout and delete ~/.optima/token.json')
|
|
151
142
|
.action(() => {
|
|
152
143
|
clearAuth();
|
|
153
144
|
success('Logged out successfully');
|
|
145
|
+
info(`Deleted ${getConfigPath()}`);
|
|
154
146
|
});
|
|
155
147
|
// auth whoami
|
|
156
148
|
auth
|
|
@@ -166,13 +158,20 @@ export function createAuthCommand() {
|
|
|
166
158
|
const userInfo = await axios.get(`${cfg.authUrl}/api/v1/users/me`, {
|
|
167
159
|
headers: { Authorization: `Bearer ${cfg.accessToken}` },
|
|
168
160
|
});
|
|
169
|
-
|
|
161
|
+
// Update user info in token.json
|
|
162
|
+
saveUser({
|
|
163
|
+
id: userInfo.data.id,
|
|
164
|
+
email: userInfo.data.email,
|
|
165
|
+
name: userInfo.data.name,
|
|
166
|
+
});
|
|
167
|
+
console.log(chalk.bold('\n Current User:\n'));
|
|
170
168
|
console.log(` Email: ${chalk.cyan(userInfo.data.email)}`);
|
|
171
169
|
console.log(` Role: ${chalk.yellow(userInfo.data.role)}`);
|
|
172
170
|
if (userInfo.data.merchant_id) {
|
|
173
171
|
console.log(` Merchant ID: ${chalk.gray(userInfo.data.merchant_id)}`);
|
|
174
172
|
}
|
|
175
|
-
console.log(` Environment: ${chalk.green(cfg.environment)}
|
|
173
|
+
console.log(` Environment: ${chalk.green(cfg.environment)}`);
|
|
174
|
+
console.log(` Config: ${chalk.gray(getConfigPath())}\n`);
|
|
176
175
|
}
|
|
177
176
|
catch (err) {
|
|
178
177
|
const axiosError = err;
|
|
@@ -186,28 +185,21 @@ export function createAuthCommand() {
|
|
|
186
185
|
process.exit(1);
|
|
187
186
|
}
|
|
188
187
|
});
|
|
189
|
-
// auth
|
|
188
|
+
// auth status
|
|
190
189
|
auth
|
|
191
|
-
.command('
|
|
192
|
-
.description('
|
|
193
|
-
.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
error(`Invalid environment: ${env}`);
|
|
204
|
-
process.exit(1);
|
|
190
|
+
.command('status')
|
|
191
|
+
.description('Show authentication status')
|
|
192
|
+
.action(() => {
|
|
193
|
+
const cfg = getConfig();
|
|
194
|
+
console.log(chalk.bold('\n Authentication Status:\n'));
|
|
195
|
+
console.log(` Environment: ${chalk.green(cfg.environment)}`);
|
|
196
|
+
console.log(` Auth URL: ${chalk.cyan(cfg.authUrl)}`);
|
|
197
|
+
console.log(` Backend URL: ${chalk.cyan(cfg.backendUrl)}`);
|
|
198
|
+
console.log(` Config file: ${chalk.gray(getConfigPath())}`);
|
|
199
|
+
console.log(` Authenticated: ${cfg.accessToken ? chalk.green('Yes') : chalk.red('No')}\n`);
|
|
200
|
+
if (!cfg.accessToken) {
|
|
201
|
+
info('Run "bi-cli auth login" to authenticate');
|
|
205
202
|
}
|
|
206
|
-
setConfig('environment', env);
|
|
207
|
-
setConfig('backendUrl', backendUrl);
|
|
208
|
-
clearAuth(); // Clear tokens when switching environment
|
|
209
|
-
success(`Switched to ${env} environment`);
|
|
210
|
-
info('Please login again: bi-cli auth login');
|
|
211
203
|
});
|
|
212
204
|
return auth;
|
|
213
205
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/config/index.d.ts
CHANGED
|
@@ -1,21 +1,89 @@
|
|
|
1
|
-
|
|
1
|
+
export type Environment = 'production' | 'stage' | 'development';
|
|
2
|
+
interface UserData {
|
|
3
|
+
id: string;
|
|
4
|
+
email: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
}
|
|
7
|
+
interface EnvironmentConfig {
|
|
8
|
+
authUrl: string;
|
|
9
|
+
backendUrl: string;
|
|
10
|
+
clientId: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const ENV_CONFIG: Record<Environment, EnvironmentConfig>;
|
|
13
|
+
/**
|
|
14
|
+
* 获取当前环境
|
|
15
|
+
* 优先级:BI_CLI_ENV 环境变量 > token.json 的 env 字段 > 默认 production
|
|
16
|
+
*/
|
|
17
|
+
export declare function getCurrentEnvironment(): Environment;
|
|
18
|
+
/**
|
|
19
|
+
* 获取当前环境的配置信息
|
|
20
|
+
*/
|
|
21
|
+
export declare function getCurrentEnvConfig(): EnvironmentConfig;
|
|
22
|
+
/**
|
|
23
|
+
* 保存认证令牌到 ~/.optima/token.json
|
|
24
|
+
*/
|
|
25
|
+
export declare function saveTokens(access_token: string, refresh_token: string, expires_in: number, env: Environment, user?: UserData): void;
|
|
26
|
+
/**
|
|
27
|
+
* 获取访问令牌
|
|
28
|
+
* 优先级:BI_CLI_TOKEN > OPTIMA_TOKEN > ~/.optima/token.json
|
|
29
|
+
*/
|
|
30
|
+
export declare function getAccessToken(): string | null;
|
|
31
|
+
/**
|
|
32
|
+
* 获取 refresh token
|
|
33
|
+
*/
|
|
34
|
+
export declare function getRefreshToken(): string | null;
|
|
35
|
+
/**
|
|
36
|
+
* 获取 token 过期时间
|
|
37
|
+
*/
|
|
38
|
+
export declare function getTokenExpiresAt(): number | null;
|
|
39
|
+
/**
|
|
40
|
+
* 检查 token 是否过期
|
|
41
|
+
*/
|
|
42
|
+
export declare function isTokenExpired(): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* 检查是否已认证
|
|
45
|
+
*/
|
|
46
|
+
export declare function isAuthenticated(): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* 保存用户信息到 token.json
|
|
49
|
+
*/
|
|
50
|
+
export declare function saveUser(user: UserData): void;
|
|
51
|
+
/**
|
|
52
|
+
* 获取当前用户信息
|
|
53
|
+
*/
|
|
54
|
+
export declare function getUser(): UserData | null;
|
|
55
|
+
/**
|
|
56
|
+
* 清除配置(登出)- 删除 token.json
|
|
57
|
+
*/
|
|
58
|
+
export declare function clearAuth(): void;
|
|
59
|
+
/**
|
|
60
|
+
* 获取配置文件路径
|
|
61
|
+
*/
|
|
62
|
+
export declare function getConfigPath(): string;
|
|
63
|
+
/**
|
|
64
|
+
* 获取 BI API URL
|
|
65
|
+
* 优先级:BI_CLI_BACKEND_URL 环境变量 > 当前环境配置
|
|
66
|
+
*/
|
|
67
|
+
export declare function getBackendUrl(): string;
|
|
68
|
+
/**
|
|
69
|
+
* 获取 Auth API URL
|
|
70
|
+
* 优先级:BI_CLI_AUTH_URL 环境变量 > 当前环境配置
|
|
71
|
+
*/
|
|
72
|
+
export declare function getAuthUrl(): string;
|
|
2
73
|
export interface CliConfig {
|
|
3
|
-
environment:
|
|
74
|
+
environment: Environment;
|
|
4
75
|
authUrl: string;
|
|
5
76
|
backendUrl: string;
|
|
6
77
|
accessToken?: string;
|
|
7
78
|
refreshToken?: string;
|
|
8
79
|
}
|
|
9
|
-
export declare const config: Conf<CliConfig>;
|
|
10
80
|
/**
|
|
11
|
-
* Get CLI configuration
|
|
12
|
-
*
|
|
13
|
-
* Supports environment variables for CI/development:
|
|
14
|
-
* - BI_CLI_TOKEN: Access token (overrides stored token)
|
|
15
|
-
* - BI_CLI_BACKEND_URL: Backend URL (overrides stored URL)
|
|
16
|
-
* - BI_CLI_AUTH_URL: Auth URL (overrides stored URL)
|
|
81
|
+
* Get CLI configuration (兼容旧接口)
|
|
17
82
|
*/
|
|
18
83
|
export declare function getConfig(): CliConfig;
|
|
19
|
-
|
|
20
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Set config (兼容旧接口 - 现在只支持保存 token)
|
|
86
|
+
*/
|
|
87
|
+
export declare function setConfig(key: keyof CliConfig, value: string): void;
|
|
88
|
+
export {};
|
|
21
89
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,OAAO,GAAG,aAAa,CAAC;AAKjE,UAAU,QAAQ;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAcD,UAAU,iBAAiB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAID,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,WAAW,EAAE,iBAAiB,CAgB7D,CAAC;AAmFF;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,WAAW,CAenD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,iBAAiB,CAGvD;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,WAAW,EAChB,IAAI,CAAC,EAAE,QAAQ,GACd,IAAI,CAWN;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAc9C;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAG/C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,GAAG,IAAI,CAGjD;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,OAAO,CASxC;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAM7C;AAED;;GAEG;AACH,wBAAgB,OAAO,IAAI,QAAQ,GAAG,IAAI,CAGzC;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,IAAI,CAEhC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAKtC;AAED;;;GAGG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAKnC;AAID,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,SAAS,CAQrC;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAanE"}
|
package/dist/config/index.js
CHANGED
|
@@ -1,39 +1,263 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { readFileSync, existsSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
// ============== 环境配置常量 ==============
|
|
5
|
+
export const ENV_CONFIG = {
|
|
6
|
+
production: {
|
|
7
|
+
authUrl: 'https://auth.optima.onl',
|
|
8
|
+
backendUrl: 'https://bi-api.optima.onl',
|
|
9
|
+
clientId: 'bi-cli-wl18yjdk',
|
|
10
|
+
},
|
|
11
|
+
stage: {
|
|
12
|
+
authUrl: 'https://auth.stage.optima.onl',
|
|
13
|
+
backendUrl: 'https://bi-api.stage.optima.onl',
|
|
14
|
+
clientId: 'bi-cli-gqqcui4k',
|
|
15
|
+
},
|
|
16
|
+
development: {
|
|
6
17
|
authUrl: 'https://auth.optima.chat',
|
|
7
18
|
backendUrl: 'https://bi-api.optima.chat',
|
|
19
|
+
clientId: 'bi-cli-aqkutatj',
|
|
8
20
|
},
|
|
9
|
-
|
|
10
|
-
|
|
21
|
+
};
|
|
22
|
+
// ============== Token 文件路径 ==============
|
|
23
|
+
const OPTIMA_DIR = join(homedir(), '.optima');
|
|
24
|
+
const TOKEN_FILE = join(OPTIMA_DIR, 'token.json');
|
|
25
|
+
// ============== 环境映射 ==============
|
|
26
|
+
/**
|
|
27
|
+
* 将 token.json 的 env 字段映射到 Environment 类型
|
|
28
|
+
*/
|
|
29
|
+
function tokenEnvToEnvironment(tokenEnv) {
|
|
30
|
+
switch (tokenEnv) {
|
|
31
|
+
case 'prod':
|
|
32
|
+
return 'production';
|
|
33
|
+
case 'stage':
|
|
34
|
+
return 'stage';
|
|
35
|
+
case 'ci':
|
|
36
|
+
return 'development';
|
|
37
|
+
default:
|
|
38
|
+
return 'production';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 将 Environment 类型映射到 token.json 的 env 字段
|
|
43
|
+
*/
|
|
44
|
+
function environmentToTokenEnv(env) {
|
|
45
|
+
switch (env) {
|
|
46
|
+
case 'production':
|
|
47
|
+
return 'prod';
|
|
48
|
+
case 'stage':
|
|
49
|
+
return 'stage';
|
|
50
|
+
case 'development':
|
|
51
|
+
return 'ci';
|
|
52
|
+
default:
|
|
53
|
+
return 'prod';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// ============== Token 文件操作 ==============
|
|
57
|
+
/**
|
|
58
|
+
* 从 ~/.optima/token.json 读取 token 数据
|
|
59
|
+
*/
|
|
60
|
+
function getOptimaToken() {
|
|
61
|
+
if (!existsSync(TOKEN_FILE)) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const content = readFileSync(TOKEN_FILE, 'utf-8');
|
|
66
|
+
return JSON.parse(content);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// 文件读取或解析失败
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 保存 token 到 ~/.optima/token.json
|
|
75
|
+
*/
|
|
76
|
+
function saveOptimaToken(token) {
|
|
77
|
+
mkdirSync(OPTIMA_DIR, { recursive: true });
|
|
78
|
+
writeFileSync(TOKEN_FILE, JSON.stringify(token, null, 2), 'utf-8');
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 删除 ~/.optima/token.json
|
|
82
|
+
*/
|
|
83
|
+
function deleteOptimaToken() {
|
|
84
|
+
try {
|
|
85
|
+
if (existsSync(TOKEN_FILE)) {
|
|
86
|
+
unlinkSync(TOKEN_FILE);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// 忽略删除失败
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// ============== 公开 API ==============
|
|
94
|
+
/**
|
|
95
|
+
* 获取当前环境
|
|
96
|
+
* 优先级:BI_CLI_ENV 环境变量 > token.json 的 env 字段 > 默认 production
|
|
97
|
+
*/
|
|
98
|
+
export function getCurrentEnvironment() {
|
|
99
|
+
// 1. 优先从环境变量读取
|
|
100
|
+
const envFromEnv = process.env.BI_CLI_ENV;
|
|
101
|
+
if (envFromEnv && ['production', 'stage', 'development'].includes(envFromEnv)) {
|
|
102
|
+
return envFromEnv;
|
|
103
|
+
}
|
|
104
|
+
// 2. 从 token.json 读取
|
|
105
|
+
const token = getOptimaToken();
|
|
106
|
+
if (token?.env) {
|
|
107
|
+
return tokenEnvToEnvironment(token.env);
|
|
108
|
+
}
|
|
109
|
+
// 3. 默认使用生产环境
|
|
110
|
+
return 'production';
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 获取当前环境的配置信息
|
|
114
|
+
*/
|
|
115
|
+
export function getCurrentEnvConfig() {
|
|
116
|
+
const env = getCurrentEnvironment();
|
|
117
|
+
return ENV_CONFIG[env];
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 保存认证令牌到 ~/.optima/token.json
|
|
121
|
+
*/
|
|
122
|
+
export function saveTokens(access_token, refresh_token, expires_in, env, user) {
|
|
123
|
+
const expires_at = Date.now() + expires_in * 1000;
|
|
124
|
+
const token = {
|
|
125
|
+
env: environmentToTokenEnv(env),
|
|
126
|
+
access_token,
|
|
127
|
+
refresh_token,
|
|
128
|
+
token_type: 'Bearer',
|
|
129
|
+
expires_at,
|
|
130
|
+
user,
|
|
131
|
+
};
|
|
132
|
+
saveOptimaToken(token);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* 获取访问令牌
|
|
136
|
+
* 优先级:BI_CLI_TOKEN > OPTIMA_TOKEN > ~/.optima/token.json
|
|
137
|
+
*/
|
|
138
|
+
export function getAccessToken() {
|
|
139
|
+
// 1. 优先从 BI_CLI_TOKEN 环境变量读取
|
|
140
|
+
if (process.env.BI_CLI_TOKEN) {
|
|
141
|
+
return process.env.BI_CLI_TOKEN;
|
|
142
|
+
}
|
|
143
|
+
// 2. 从 OPTIMA_TOKEN 环境变量读取(兼容 optima-agent)
|
|
144
|
+
if (process.env.OPTIMA_TOKEN) {
|
|
145
|
+
return process.env.OPTIMA_TOKEN;
|
|
146
|
+
}
|
|
147
|
+
// 3. 从 token.json 读取
|
|
148
|
+
const token = getOptimaToken();
|
|
149
|
+
return token?.access_token ?? null;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* 获取 refresh token
|
|
153
|
+
*/
|
|
154
|
+
export function getRefreshToken() {
|
|
155
|
+
const token = getOptimaToken();
|
|
156
|
+
return token?.refresh_token ?? null;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* 获取 token 过期时间
|
|
160
|
+
*/
|
|
161
|
+
export function getTokenExpiresAt() {
|
|
162
|
+
const token = getOptimaToken();
|
|
163
|
+
return token?.expires_at ?? null;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 检查 token 是否过期
|
|
167
|
+
*/
|
|
168
|
+
export function isTokenExpired() {
|
|
169
|
+
// 环境变量 token 不检查过期
|
|
170
|
+
if (process.env.BI_CLI_TOKEN || process.env.OPTIMA_TOKEN) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
const expiresAt = getTokenExpiresAt();
|
|
174
|
+
if (!expiresAt)
|
|
175
|
+
return true;
|
|
176
|
+
return Date.now() >= expiresAt;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* 检查是否已认证
|
|
180
|
+
*/
|
|
181
|
+
export function isAuthenticated() {
|
|
182
|
+
return getAccessToken() !== null;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* 保存用户信息到 token.json
|
|
186
|
+
*/
|
|
187
|
+
export function saveUser(user) {
|
|
188
|
+
const token = getOptimaToken();
|
|
189
|
+
if (token) {
|
|
190
|
+
token.user = user;
|
|
191
|
+
saveOptimaToken(token);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* 获取当前用户信息
|
|
196
|
+
*/
|
|
197
|
+
export function getUser() {
|
|
198
|
+
const token = getOptimaToken();
|
|
199
|
+
return token?.user ?? null;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* 清除配置(登出)- 删除 token.json
|
|
203
|
+
*/
|
|
204
|
+
export function clearAuth() {
|
|
205
|
+
deleteOptimaToken();
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* 获取配置文件路径
|
|
209
|
+
*/
|
|
210
|
+
export function getConfigPath() {
|
|
211
|
+
return TOKEN_FILE;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* 获取 BI API URL
|
|
215
|
+
* 优先级:BI_CLI_BACKEND_URL 环境变量 > 当前环境配置
|
|
216
|
+
*/
|
|
217
|
+
export function getBackendUrl() {
|
|
218
|
+
if (process.env.BI_CLI_BACKEND_URL) {
|
|
219
|
+
return process.env.BI_CLI_BACKEND_URL;
|
|
220
|
+
}
|
|
221
|
+
return getCurrentEnvConfig().backendUrl;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* 获取 Auth API URL
|
|
225
|
+
* 优先级:BI_CLI_AUTH_URL 环境变量 > 当前环境配置
|
|
226
|
+
*/
|
|
227
|
+
export function getAuthUrl() {
|
|
228
|
+
if (process.env.BI_CLI_AUTH_URL) {
|
|
229
|
+
return process.env.BI_CLI_AUTH_URL;
|
|
230
|
+
}
|
|
231
|
+
return getCurrentEnvConfig().authUrl;
|
|
232
|
+
}
|
|
11
233
|
/**
|
|
12
|
-
* Get CLI configuration
|
|
13
|
-
*
|
|
14
|
-
* Supports environment variables for CI/development:
|
|
15
|
-
* - BI_CLI_TOKEN: Access token (overrides stored token)
|
|
16
|
-
* - BI_CLI_BACKEND_URL: Backend URL (overrides stored URL)
|
|
17
|
-
* - BI_CLI_AUTH_URL: Auth URL (overrides stored URL)
|
|
234
|
+
* Get CLI configuration (兼容旧接口)
|
|
18
235
|
*/
|
|
19
236
|
export function getConfig() {
|
|
20
237
|
return {
|
|
21
|
-
environment:
|
|
22
|
-
authUrl:
|
|
23
|
-
backendUrl:
|
|
24
|
-
accessToken:
|
|
25
|
-
refreshToken:
|
|
238
|
+
environment: getCurrentEnvironment(),
|
|
239
|
+
authUrl: getAuthUrl(),
|
|
240
|
+
backendUrl: getBackendUrl(),
|
|
241
|
+
accessToken: getAccessToken() ?? undefined,
|
|
242
|
+
refreshToken: getRefreshToken() ?? undefined,
|
|
26
243
|
};
|
|
27
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Set config (兼容旧接口 - 现在只支持保存 token)
|
|
247
|
+
*/
|
|
28
248
|
export function setConfig(key, value) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
249
|
+
if (key === 'accessToken' || key === 'refreshToken') {
|
|
250
|
+
const token = getOptimaToken();
|
|
251
|
+
if (token) {
|
|
252
|
+
if (key === 'accessToken') {
|
|
253
|
+
token.access_token = value;
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
token.refresh_token = value;
|
|
257
|
+
}
|
|
258
|
+
saveOptimaToken(token);
|
|
259
|
+
}
|
|
32
260
|
}
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
export function clearAuth() {
|
|
36
|
-
config.delete('accessToken');
|
|
37
|
-
config.delete('refreshToken');
|
|
261
|
+
// environment, authUrl, backendUrl 现在通过环境变量或 token.json 的 env 字段控制
|
|
38
262
|
}
|
|
39
263
|
//# sourceMappingURL=index.js.map
|
package/dist/config/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AACpF,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAiC7B,uCAAuC;AAEvC,MAAM,CAAC,MAAM,UAAU,GAA2C;IAChE,UAAU,EAAE;QACV,OAAO,EAAE,yBAAyB;QAClC,UAAU,EAAE,2BAA2B;QACvC,QAAQ,EAAE,iBAAiB;KAC5B;IACD,KAAK,EAAE;QACL,OAAO,EAAE,+BAA+B;QACxC,UAAU,EAAE,iCAAiC;QAC7C,QAAQ,EAAE,iBAAiB;KAC5B;IACD,WAAW,EAAE;QACX,OAAO,EAAE,0BAA0B;QACnC,UAAU,EAAE,4BAA4B;QACxC,QAAQ,EAAE,iBAAiB;KAC5B;CACF,CAAC;AAEF,2CAA2C;AAE3C,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;AAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;AAElD,qCAAqC;AAErC;;GAEG;AACH,SAAS,qBAAqB,CAAC,QAAkB;IAC/C,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,MAAM;YACT,OAAO,YAAY,CAAC;QACtB,KAAK,OAAO;YACV,OAAO,OAAO,CAAC;QACjB,KAAK,IAAI;YACP,OAAO,aAAa,CAAC;QACvB;YACE,OAAO,YAAY,CAAC;IACxB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,qBAAqB,CAAC,GAAgB;IAC7C,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,YAAY;YACf,OAAO,MAAM,CAAC;QAChB,KAAK,OAAO;YACV,OAAO,OAAO,CAAC;QACjB,KAAK,aAAa;YAChB,OAAO,IAAI,CAAC;QACd;YACE,OAAO,MAAM,CAAC;IAClB,CAAC;AACH,CAAC;AAED,2CAA2C;AAE3C;;GAEG;AACH,SAAS,cAAc;IACrB,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAgB,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,YAAY;QACZ,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,KAAkB;IACzC,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AACrE,CAAC;AAED;;GAEG;AACH,SAAS,iBAAiB;IACxB,IAAI,CAAC;QACH,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,UAAU,CAAC,UAAU,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;AACH,CAAC;AAED,uCAAuC;AAEvC;;;GAGG;AACH,MAAM,UAAU,qBAAqB;IACnC,eAAe;IACf,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,UAAyB,CAAC;IACzD,IAAI,UAAU,IAAI,CAAC,YAAY,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAC9E,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,qBAAqB;IACrB,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;IAC/B,IAAI,KAAK,EAAE,GAAG,EAAE,CAAC;QACf,OAAO,qBAAqB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC1C,CAAC;IAED,cAAc;IACd,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,GAAG,GAAG,qBAAqB,EAAE,CAAC;IACpC,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CACxB,YAAoB,EACpB,aAAqB,EACrB,UAAkB,EAClB,GAAgB,EAChB,IAAe;IAEf,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,GAAG,IAAI,CAAC;IAClD,MAAM,KAAK,GAAgB;QACzB,GAAG,EAAE,qBAAqB,CAAC,GAAG,CAAC;QAC/B,YAAY;QACZ,aAAa;QACb,UAAU,EAAE,QAAQ;QACpB,UAAU;QACV,IAAI;KACL,CAAC;IACF,eAAe,CAAC,KAAK,CAAC,CAAC;AACzB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,6BAA6B;IAC7B,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC7B,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IAClC,CAAC;IAED,4CAA4C;IAC5C,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC7B,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IAClC,CAAC;IAED,qBAAqB;IACrB,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;IAC/B,OAAO,KAAK,EAAE,YAAY,IAAI,IAAI,CAAC;AACrC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;IAC/B,OAAO,KAAK,EAAE,aAAa,IAAI,IAAI,CAAC;AACtC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;IAC/B,OAAO,KAAK,EAAE,UAAU,IAAI,IAAI,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,mBAAmB;IACnB,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QACzD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,SAAS,GAAG,iBAAiB,EAAE,CAAC;IACtC,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAC5B,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,SAAS,CAAC;AACjC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,cAAc,EAAE,KAAK,IAAI,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAc;IACrC,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;IAC/B,IAAI,KAAK,EAAE,CAAC;QACV,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;QAClB,eAAe,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO;IACrB,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;IAC/B,OAAO,KAAK,EAAE,IAAI,IAAI,IAAI,CAAC;AAC7B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS;IACvB,iBAAiB,EAAE,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa;IAC3B,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC;QACnC,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IACxC,CAAC;IACD,OAAO,mBAAmB,EAAE,CAAC,UAAU,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU;IACxB,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC;QAChC,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IACrC,CAAC;IACD,OAAO,mBAAmB,EAAE,CAAC,OAAO,CAAC;AACvC,CAAC;AAYD;;GAEG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO;QACL,WAAW,EAAE,qBAAqB,EAAE;QACpC,OAAO,EAAE,UAAU,EAAE;QACrB,UAAU,EAAE,aAAa,EAAE;QAC3B,WAAW,EAAE,cAAc,EAAE,IAAI,SAAS;QAC1C,YAAY,EAAE,eAAe,EAAE,IAAI,SAAS;KAC7C,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,GAAoB,EAAE,KAAa;IAC3D,IAAI,GAAG,KAAK,aAAa,IAAI,GAAG,KAAK,cAAc,EAAE,CAAC;QACpD,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;QAC/B,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,GAAG,KAAK,aAAa,EAAE,CAAC;gBAC1B,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC;YAC7B,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,aAAa,GAAG,KAAK,CAAC;YAC9B,CAAC;YACD,eAAe,CAAC,KAAK,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IACD,mEAAmE;AACrE,CAAC"}
|
package/package.json
CHANGED
package/src/commands/auth.ts
CHANGED
|
@@ -2,7 +2,15 @@ import { Command } from 'commander';
|
|
|
2
2
|
import axios from 'axios';
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
clearAuth,
|
|
7
|
+
getConfig,
|
|
8
|
+
saveTokens,
|
|
9
|
+
saveUser,
|
|
10
|
+
getConfigPath,
|
|
11
|
+
ENV_CONFIG,
|
|
12
|
+
type Environment,
|
|
13
|
+
} from '../config/index.js';
|
|
6
14
|
import { success, error, info } from '../utils/output.js';
|
|
7
15
|
|
|
8
16
|
interface DeviceCodeResponse {
|
|
@@ -24,6 +32,7 @@ interface TokenResponse {
|
|
|
24
32
|
interface UserInfo {
|
|
25
33
|
id: string;
|
|
26
34
|
email: string;
|
|
35
|
+
name?: string;
|
|
27
36
|
role: 'merchant' | 'admin';
|
|
28
37
|
merchant_id?: string;
|
|
29
38
|
}
|
|
@@ -37,41 +46,26 @@ export function createAuthCommand(): Command {
|
|
|
37
46
|
.description('Login with OAuth 2.0 Device Flow')
|
|
38
47
|
.option('--env <environment>', 'Environment (production|stage|development)', 'production')
|
|
39
48
|
.action(async (options) => {
|
|
40
|
-
const
|
|
49
|
+
const env = options.env as Environment;
|
|
41
50
|
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
production: 'https://auth.optima.chat',
|
|
45
|
-
stage: 'https://auth-stage.optima.chat',
|
|
46
|
-
development: 'http://localhost:4000',
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const backendUrls = {
|
|
50
|
-
production: 'https://bi-api.optima.chat',
|
|
51
|
-
stage: 'https://bi-api-stage.optima.chat',
|
|
52
|
-
development: 'http://localhost:3001',
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const authUrl = authUrls[env as keyof typeof authUrls];
|
|
56
|
-
const backendUrl = backendUrls[env as keyof typeof backendUrls];
|
|
57
|
-
|
|
58
|
-
if (!authUrl || !backendUrl) {
|
|
51
|
+
// Validate environment
|
|
52
|
+
if (!['production', 'stage', 'development'].includes(env)) {
|
|
59
53
|
error(`Invalid environment: ${env}`);
|
|
60
54
|
process.exit(1);
|
|
61
55
|
}
|
|
62
56
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
setConfig('backendUrl', backendUrl);
|
|
57
|
+
const envConfig = ENV_CONFIG[env];
|
|
58
|
+
const { authUrl, clientId } = envConfig;
|
|
66
59
|
|
|
67
60
|
info(`Logging in to ${env} environment...`);
|
|
61
|
+
info(`Auth URL: ${authUrl}`);
|
|
68
62
|
|
|
69
63
|
try {
|
|
70
64
|
// Step 1: Request device code
|
|
71
65
|
const spinner = ora('Requesting device code...').start();
|
|
72
66
|
const deviceCodeRes = await axios.post<DeviceCodeResponse>(
|
|
73
67
|
`${authUrl}/api/v1/oauth/device/authorize`,
|
|
74
|
-
{ client_id:
|
|
68
|
+
{ client_id: clientId }
|
|
75
69
|
);
|
|
76
70
|
spinner.succeed('Device code received');
|
|
77
71
|
|
|
@@ -88,7 +82,7 @@ export function createAuthCommand(): Command {
|
|
|
88
82
|
const browserUrl = verification_uri_complete || verification_uri;
|
|
89
83
|
|
|
90
84
|
// Step 2: Display authorization instructions
|
|
91
|
-
console.log(chalk.bold('\n
|
|
85
|
+
console.log(chalk.bold('\n Authorization Required:\n'));
|
|
92
86
|
if (verification_uri_complete) {
|
|
93
87
|
console.log(` Opening browser with pre-filled code: ${chalk.yellow.bold(user_code)}`);
|
|
94
88
|
console.log(` URL: ${chalk.cyan(verification_uri_complete)}\n`);
|
|
@@ -121,7 +115,7 @@ export function createAuthCommand(): Command {
|
|
|
121
115
|
`${authUrl}/api/v1/oauth/device/token`,
|
|
122
116
|
new URLSearchParams({
|
|
123
117
|
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
124
|
-
client_id:
|
|
118
|
+
client_id: clientId,
|
|
125
119
|
device_code,
|
|
126
120
|
}),
|
|
127
121
|
{
|
|
@@ -151,20 +145,17 @@ export function createAuthCommand(): Command {
|
|
|
151
145
|
pollSpinner.text = `Authorization successful after ${pollCount} attempts`;
|
|
152
146
|
break;
|
|
153
147
|
} catch (err: unknown) {
|
|
154
|
-
const
|
|
155
|
-
const errorCode =
|
|
148
|
+
const axiosErr = err as { response?: { data?: { error?: string } } };
|
|
149
|
+
const errorCode = axiosErr.response?.data?.error;
|
|
156
150
|
|
|
157
151
|
if (errorCode === 'authorization_pending') {
|
|
158
|
-
// Continue polling
|
|
159
152
|
pollSpinner.text = `Waiting for authorization... (attempt ${pollCount})`;
|
|
160
153
|
continue;
|
|
161
154
|
} else if (errorCode === 'slow_down') {
|
|
162
|
-
// Increase interval
|
|
163
155
|
pollSpinner.text = `Slowing down polling... (attempt ${pollCount})`;
|
|
164
156
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
165
157
|
continue;
|
|
166
158
|
} else {
|
|
167
|
-
// Unexpected error
|
|
168
159
|
pollSpinner.fail(`Polling failed: ${errorCode || 'unknown error'}`);
|
|
169
160
|
throw err;
|
|
170
161
|
}
|
|
@@ -179,20 +170,32 @@ export function createAuthCommand(): Command {
|
|
|
179
170
|
|
|
180
171
|
pollSpinner.succeed();
|
|
181
172
|
|
|
182
|
-
// Step 5:
|
|
183
|
-
|
|
184
|
-
setConfig('refreshToken', token.refresh_token);
|
|
185
|
-
|
|
186
|
-
// Step 6: Fetch user info (optional - just for display)
|
|
173
|
+
// Step 5: Fetch user info and save tokens
|
|
174
|
+
let userInfo: UserInfo | undefined;
|
|
187
175
|
try {
|
|
188
|
-
const
|
|
176
|
+
const userRes = await axios.get<UserInfo>(`${authUrl}/api/v1/users/me`, {
|
|
189
177
|
headers: { Authorization: `Bearer ${token.access_token}` },
|
|
190
178
|
});
|
|
191
|
-
|
|
192
|
-
} catch
|
|
193
|
-
//
|
|
194
|
-
|
|
179
|
+
userInfo = userRes.data;
|
|
180
|
+
} catch {
|
|
181
|
+
// User info fetch failed, continue without it
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Step 6: Save tokens to ~/.optima/token.json
|
|
185
|
+
saveTokens(
|
|
186
|
+
token.access_token,
|
|
187
|
+
token.refresh_token,
|
|
188
|
+
token.expires_in,
|
|
189
|
+
env,
|
|
190
|
+
userInfo ? { id: userInfo.id, email: userInfo.email, name: userInfo.name } : undefined
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
if (userInfo) {
|
|
194
|
+
success(`Logged in as ${chalk.bold(userInfo.email)} (${userInfo.role})`);
|
|
195
|
+
} else {
|
|
196
|
+
success('Login successful!');
|
|
195
197
|
}
|
|
198
|
+
info(`Token saved to ${getConfigPath()}`);
|
|
196
199
|
} catch (err: unknown) {
|
|
197
200
|
const errorMsg = err instanceof Error ? err.message : 'Unknown error';
|
|
198
201
|
error(`Login failed: ${errorMsg}`);
|
|
@@ -203,10 +206,11 @@ export function createAuthCommand(): Command {
|
|
|
203
206
|
// auth logout
|
|
204
207
|
auth
|
|
205
208
|
.command('logout')
|
|
206
|
-
.description('Logout and
|
|
209
|
+
.description('Logout and delete ~/.optima/token.json')
|
|
207
210
|
.action(() => {
|
|
208
211
|
clearAuth();
|
|
209
212
|
success('Logged out successfully');
|
|
213
|
+
info(`Deleted ${getConfigPath()}`);
|
|
210
214
|
});
|
|
211
215
|
|
|
212
216
|
// auth whoami
|
|
@@ -226,13 +230,21 @@ export function createAuthCommand(): Command {
|
|
|
226
230
|
headers: { Authorization: `Bearer ${cfg.accessToken}` },
|
|
227
231
|
});
|
|
228
232
|
|
|
229
|
-
|
|
233
|
+
// Update user info in token.json
|
|
234
|
+
saveUser({
|
|
235
|
+
id: userInfo.data.id,
|
|
236
|
+
email: userInfo.data.email,
|
|
237
|
+
name: userInfo.data.name,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
console.log(chalk.bold('\n Current User:\n'));
|
|
230
241
|
console.log(` Email: ${chalk.cyan(userInfo.data.email)}`);
|
|
231
242
|
console.log(` Role: ${chalk.yellow(userInfo.data.role)}`);
|
|
232
243
|
if (userInfo.data.merchant_id) {
|
|
233
244
|
console.log(` Merchant ID: ${chalk.gray(userInfo.data.merchant_id)}`);
|
|
234
245
|
}
|
|
235
|
-
console.log(` Environment: ${chalk.green(cfg.environment)}
|
|
246
|
+
console.log(` Environment: ${chalk.green(cfg.environment)}`);
|
|
247
|
+
console.log(` Config: ${chalk.gray(getConfigPath())}\n`);
|
|
236
248
|
} catch (err: unknown) {
|
|
237
249
|
const axiosError = err as { response?: { status?: number } };
|
|
238
250
|
if (axiosError.response?.status === 401) {
|
|
@@ -245,32 +257,23 @@ export function createAuthCommand(): Command {
|
|
|
245
257
|
}
|
|
246
258
|
});
|
|
247
259
|
|
|
248
|
-
// auth
|
|
260
|
+
// auth status
|
|
249
261
|
auth
|
|
250
|
-
.command('
|
|
251
|
-
.description('
|
|
252
|
-
.
|
|
253
|
-
|
|
254
|
-
const { env } = options;
|
|
262
|
+
.command('status')
|
|
263
|
+
.description('Show authentication status')
|
|
264
|
+
.action(() => {
|
|
265
|
+
const cfg = getConfig();
|
|
255
266
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
};
|
|
267
|
+
console.log(chalk.bold('\n Authentication Status:\n'));
|
|
268
|
+
console.log(` Environment: ${chalk.green(cfg.environment)}`);
|
|
269
|
+
console.log(` Auth URL: ${chalk.cyan(cfg.authUrl)}`);
|
|
270
|
+
console.log(` Backend URL: ${chalk.cyan(cfg.backendUrl)}`);
|
|
271
|
+
console.log(` Config file: ${chalk.gray(getConfigPath())}`);
|
|
272
|
+
console.log(` Authenticated: ${cfg.accessToken ? chalk.green('Yes') : chalk.red('No')}\n`);
|
|
261
273
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
error(`Invalid environment: ${env}`);
|
|
265
|
-
process.exit(1);
|
|
274
|
+
if (!cfg.accessToken) {
|
|
275
|
+
info('Run "bi-cli auth login" to authenticate');
|
|
266
276
|
}
|
|
267
|
-
|
|
268
|
-
setConfig('environment', env);
|
|
269
|
-
setConfig('backendUrl', backendUrl);
|
|
270
|
-
clearAuth(); // Clear tokens when switching environment
|
|
271
|
-
|
|
272
|
-
success(`Switched to ${env} environment`);
|
|
273
|
-
info('Please login again: bi-cli auth login');
|
|
274
277
|
});
|
|
275
278
|
|
|
276
279
|
return auth;
|
package/src/config/index.ts
CHANGED
|
@@ -1,50 +1,339 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { readFileSync, existsSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
+
// ============== 类型定义 ==============
|
|
6
|
+
|
|
7
|
+
export type Environment = 'production' | 'stage' | 'development';
|
|
8
|
+
|
|
9
|
+
// token.json 中的 env 字段值
|
|
10
|
+
type TokenEnv = 'prod' | 'stage' | 'ci';
|
|
11
|
+
|
|
12
|
+
interface UserData {
|
|
13
|
+
id: string;
|
|
14
|
+
email: string;
|
|
15
|
+
name?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* ~/.optima/token.json 文件结构(与 optima-agent/commerce-cli 共享)
|
|
20
|
+
*/
|
|
21
|
+
interface OptimaToken {
|
|
22
|
+
env: TokenEnv;
|
|
23
|
+
access_token: string;
|
|
24
|
+
refresh_token?: string;
|
|
25
|
+
token_type: string;
|
|
26
|
+
expires_at: number;
|
|
27
|
+
user?: UserData;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface EnvironmentConfig {
|
|
5
31
|
authUrl: string;
|
|
6
32
|
backendUrl: string;
|
|
7
|
-
|
|
8
|
-
refreshToken?: string;
|
|
33
|
+
clientId: string;
|
|
9
34
|
}
|
|
10
35
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
36
|
+
// ============== 环境配置常量 ==============
|
|
37
|
+
|
|
38
|
+
export const ENV_CONFIG: Record<Environment, EnvironmentConfig> = {
|
|
39
|
+
production: {
|
|
40
|
+
authUrl: 'https://auth.optima.onl',
|
|
41
|
+
backendUrl: 'https://bi-api.optima.onl',
|
|
42
|
+
clientId: 'bi-cli-wl18yjdk',
|
|
43
|
+
},
|
|
44
|
+
stage: {
|
|
45
|
+
authUrl: 'https://auth.stage.optima.onl',
|
|
46
|
+
backendUrl: 'https://bi-api.stage.optima.onl',
|
|
47
|
+
clientId: 'bi-cli-gqqcui4k',
|
|
48
|
+
},
|
|
49
|
+
development: {
|
|
15
50
|
authUrl: 'https://auth.optima.chat',
|
|
16
51
|
backendUrl: 'https://bi-api.optima.chat',
|
|
52
|
+
clientId: 'bi-cli-aqkutatj',
|
|
17
53
|
},
|
|
18
|
-
|
|
19
|
-
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// ============== Token 文件路径 ==============
|
|
57
|
+
|
|
58
|
+
const OPTIMA_DIR = join(homedir(), '.optima');
|
|
59
|
+
const TOKEN_FILE = join(OPTIMA_DIR, 'token.json');
|
|
60
|
+
|
|
61
|
+
// ============== 环境映射 ==============
|
|
20
62
|
|
|
21
63
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* Supports environment variables for CI/development:
|
|
25
|
-
* - BI_CLI_TOKEN: Access token (overrides stored token)
|
|
26
|
-
* - BI_CLI_BACKEND_URL: Backend URL (overrides stored URL)
|
|
27
|
-
* - BI_CLI_AUTH_URL: Auth URL (overrides stored URL)
|
|
64
|
+
* 将 token.json 的 env 字段映射到 Environment 类型
|
|
28
65
|
*/
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
66
|
+
function tokenEnvToEnvironment(tokenEnv: TokenEnv): Environment {
|
|
67
|
+
switch (tokenEnv) {
|
|
68
|
+
case 'prod':
|
|
69
|
+
return 'production';
|
|
70
|
+
case 'stage':
|
|
71
|
+
return 'stage';
|
|
72
|
+
case 'ci':
|
|
73
|
+
return 'development';
|
|
74
|
+
default:
|
|
75
|
+
return 'production';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 将 Environment 类型映射到 token.json 的 env 字段
|
|
81
|
+
*/
|
|
82
|
+
function environmentToTokenEnv(env: Environment): TokenEnv {
|
|
83
|
+
switch (env) {
|
|
84
|
+
case 'production':
|
|
85
|
+
return 'prod';
|
|
86
|
+
case 'stage':
|
|
87
|
+
return 'stage';
|
|
88
|
+
case 'development':
|
|
89
|
+
return 'ci';
|
|
90
|
+
default:
|
|
91
|
+
return 'prod';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============== Token 文件操作 ==============
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 从 ~/.optima/token.json 读取 token 数据
|
|
99
|
+
*/
|
|
100
|
+
function getOptimaToken(): OptimaToken | null {
|
|
101
|
+
if (!existsSync(TOKEN_FILE)) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const content = readFileSync(TOKEN_FILE, 'utf-8');
|
|
107
|
+
return JSON.parse(content) as OptimaToken;
|
|
108
|
+
} catch {
|
|
109
|
+
// 文件读取或解析失败
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 保存 token 到 ~/.optima/token.json
|
|
116
|
+
*/
|
|
117
|
+
function saveOptimaToken(token: OptimaToken): void {
|
|
118
|
+
mkdirSync(OPTIMA_DIR, { recursive: true });
|
|
119
|
+
writeFileSync(TOKEN_FILE, JSON.stringify(token, null, 2), 'utf-8');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 删除 ~/.optima/token.json
|
|
124
|
+
*/
|
|
125
|
+
function deleteOptimaToken(): void {
|
|
126
|
+
try {
|
|
127
|
+
if (existsSync(TOKEN_FILE)) {
|
|
128
|
+
unlinkSync(TOKEN_FILE);
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
// 忽略删除失败
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ============== 公开 API ==============
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 获取当前环境
|
|
139
|
+
* 优先级:BI_CLI_ENV 环境变量 > token.json 的 env 字段 > 默认 production
|
|
140
|
+
*/
|
|
141
|
+
export function getCurrentEnvironment(): Environment {
|
|
142
|
+
// 1. 优先从环境变量读取
|
|
143
|
+
const envFromEnv = process.env.BI_CLI_ENV as Environment;
|
|
144
|
+
if (envFromEnv && ['production', 'stage', 'development'].includes(envFromEnv)) {
|
|
145
|
+
return envFromEnv;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 2. 从 token.json 读取
|
|
149
|
+
const token = getOptimaToken();
|
|
150
|
+
if (token?.env) {
|
|
151
|
+
return tokenEnvToEnvironment(token.env);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 3. 默认使用生产环境
|
|
155
|
+
return 'production';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 获取当前环境的配置信息
|
|
160
|
+
*/
|
|
161
|
+
export function getCurrentEnvConfig(): EnvironmentConfig {
|
|
162
|
+
const env = getCurrentEnvironment();
|
|
163
|
+
return ENV_CONFIG[env];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 保存认证令牌到 ~/.optima/token.json
|
|
168
|
+
*/
|
|
169
|
+
export function saveTokens(
|
|
170
|
+
access_token: string,
|
|
171
|
+
refresh_token: string,
|
|
172
|
+
expires_in: number,
|
|
173
|
+
env: Environment,
|
|
174
|
+
user?: UserData
|
|
175
|
+
): void {
|
|
176
|
+
const expires_at = Date.now() + expires_in * 1000;
|
|
177
|
+
const token: OptimaToken = {
|
|
178
|
+
env: environmentToTokenEnv(env),
|
|
179
|
+
access_token,
|
|
180
|
+
refresh_token,
|
|
181
|
+
token_type: 'Bearer',
|
|
182
|
+
expires_at,
|
|
183
|
+
user,
|
|
36
184
|
};
|
|
185
|
+
saveOptimaToken(token);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* 获取访问令牌
|
|
190
|
+
* 优先级:BI_CLI_TOKEN > OPTIMA_TOKEN > ~/.optima/token.json
|
|
191
|
+
*/
|
|
192
|
+
export function getAccessToken(): string | null {
|
|
193
|
+
// 1. 优先从 BI_CLI_TOKEN 环境变量读取
|
|
194
|
+
if (process.env.BI_CLI_TOKEN) {
|
|
195
|
+
return process.env.BI_CLI_TOKEN;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 2. 从 OPTIMA_TOKEN 环境变量读取(兼容 optima-agent)
|
|
199
|
+
if (process.env.OPTIMA_TOKEN) {
|
|
200
|
+
return process.env.OPTIMA_TOKEN;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 3. 从 token.json 读取
|
|
204
|
+
const token = getOptimaToken();
|
|
205
|
+
return token?.access_token ?? null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 获取 refresh token
|
|
210
|
+
*/
|
|
211
|
+
export function getRefreshToken(): string | null {
|
|
212
|
+
const token = getOptimaToken();
|
|
213
|
+
return token?.refresh_token ?? null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* 获取 token 过期时间
|
|
218
|
+
*/
|
|
219
|
+
export function getTokenExpiresAt(): number | null {
|
|
220
|
+
const token = getOptimaToken();
|
|
221
|
+
return token?.expires_at ?? null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* 检查 token 是否过期
|
|
226
|
+
*/
|
|
227
|
+
export function isTokenExpired(): boolean {
|
|
228
|
+
// 环境变量 token 不检查过期
|
|
229
|
+
if (process.env.BI_CLI_TOKEN || process.env.OPTIMA_TOKEN) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const expiresAt = getTokenExpiresAt();
|
|
234
|
+
if (!expiresAt) return true;
|
|
235
|
+
return Date.now() >= expiresAt;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* 检查是否已认证
|
|
240
|
+
*/
|
|
241
|
+
export function isAuthenticated(): boolean {
|
|
242
|
+
return getAccessToken() !== null;
|
|
37
243
|
}
|
|
38
244
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
245
|
+
/**
|
|
246
|
+
* 保存用户信息到 token.json
|
|
247
|
+
*/
|
|
248
|
+
export function saveUser(user: UserData): void {
|
|
249
|
+
const token = getOptimaToken();
|
|
250
|
+
if (token) {
|
|
251
|
+
token.user = user;
|
|
252
|
+
saveOptimaToken(token);
|
|
43
253
|
}
|
|
44
|
-
config.set(key, value);
|
|
45
254
|
}
|
|
46
255
|
|
|
256
|
+
/**
|
|
257
|
+
* 获取当前用户信息
|
|
258
|
+
*/
|
|
259
|
+
export function getUser(): UserData | null {
|
|
260
|
+
const token = getOptimaToken();
|
|
261
|
+
return token?.user ?? null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* 清除配置(登出)- 删除 token.json
|
|
266
|
+
*/
|
|
47
267
|
export function clearAuth(): void {
|
|
48
|
-
|
|
49
|
-
|
|
268
|
+
deleteOptimaToken();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* 获取配置文件路径
|
|
273
|
+
*/
|
|
274
|
+
export function getConfigPath(): string {
|
|
275
|
+
return TOKEN_FILE;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* 获取 BI API URL
|
|
280
|
+
* 优先级:BI_CLI_BACKEND_URL 环境变量 > 当前环境配置
|
|
281
|
+
*/
|
|
282
|
+
export function getBackendUrl(): string {
|
|
283
|
+
if (process.env.BI_CLI_BACKEND_URL) {
|
|
284
|
+
return process.env.BI_CLI_BACKEND_URL;
|
|
285
|
+
}
|
|
286
|
+
return getCurrentEnvConfig().backendUrl;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* 获取 Auth API URL
|
|
291
|
+
* 优先级:BI_CLI_AUTH_URL 环境变量 > 当前环境配置
|
|
292
|
+
*/
|
|
293
|
+
export function getAuthUrl(): string {
|
|
294
|
+
if (process.env.BI_CLI_AUTH_URL) {
|
|
295
|
+
return process.env.BI_CLI_AUTH_URL;
|
|
296
|
+
}
|
|
297
|
+
return getCurrentEnvConfig().authUrl;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ============== 兼容性 API(保留旧接口) ==============
|
|
301
|
+
|
|
302
|
+
export interface CliConfig {
|
|
303
|
+
environment: Environment;
|
|
304
|
+
authUrl: string;
|
|
305
|
+
backendUrl: string;
|
|
306
|
+
accessToken?: string;
|
|
307
|
+
refreshToken?: string;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Get CLI configuration (兼容旧接口)
|
|
312
|
+
*/
|
|
313
|
+
export function getConfig(): CliConfig {
|
|
314
|
+
return {
|
|
315
|
+
environment: getCurrentEnvironment(),
|
|
316
|
+
authUrl: getAuthUrl(),
|
|
317
|
+
backendUrl: getBackendUrl(),
|
|
318
|
+
accessToken: getAccessToken() ?? undefined,
|
|
319
|
+
refreshToken: getRefreshToken() ?? undefined,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Set config (兼容旧接口 - 现在只支持保存 token)
|
|
325
|
+
*/
|
|
326
|
+
export function setConfig(key: keyof CliConfig, value: string): void {
|
|
327
|
+
if (key === 'accessToken' || key === 'refreshToken') {
|
|
328
|
+
const token = getOptimaToken();
|
|
329
|
+
if (token) {
|
|
330
|
+
if (key === 'accessToken') {
|
|
331
|
+
token.access_token = value;
|
|
332
|
+
} else {
|
|
333
|
+
token.refresh_token = value;
|
|
334
|
+
}
|
|
335
|
+
saveOptimaToken(token);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// environment, authUrl, backendUrl 现在通过环境变量或 token.json 的 env 字段控制
|
|
50
339
|
}
|