@meet-im/lxcli 0.0.1
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/LICENSE +15 -0
- package/README.md +3 -0
- package/bin/lxcli.js +2 -0
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +45 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +28 -0
- package/dist/commands/generate-skills.d.ts +2 -0
- package/dist/commands/generate-skills.js +283 -0
- package/dist/commands/index.d.ts +9 -0
- package/dist/commands/index.js +43 -0
- package/dist/commands/schema.d.ts +2 -0
- package/dist/commands/schema.js +37 -0
- package/dist/commands/services/kb/config.d.ts +2 -0
- package/dist/commands/services/kb/config.js +32 -0
- package/dist/commands/services/kb/descriptions.d.ts +4 -0
- package/dist/commands/services/kb/descriptions.js +24 -0
- package/dist/commands/services/kb/handler.d.ts +12 -0
- package/dist/commands/services/kb/handler.js +179 -0
- package/dist/commands/services/kb/index.d.ts +3 -0
- package/dist/commands/services/kb/index.js +119 -0
- package/dist/commands/services/kb/params.d.ts +8 -0
- package/dist/commands/services/kb/params.js +73 -0
- package/dist/commands/services/kb/rpc.d.ts +1 -0
- package/dist/commands/services/kb/rpc.js +35 -0
- package/dist/commands/services/kb/types.d.ts +97 -0
- package/dist/commands/services/kb/types.js +2 -0
- package/dist/commands/services/kb/utils.d.ts +1 -0
- package/dist/commands/services/kb/utils.js +4 -0
- package/dist/commands/services/meet/api.d.ts +13 -0
- package/dist/commands/services/meet/api.js +38 -0
- package/dist/commands/services/meet/config.d.ts +4 -0
- package/dist/commands/services/meet/config.js +16 -0
- package/dist/commands/services/meet/descriptions.d.ts +3 -0
- package/dist/commands/services/meet/descriptions.js +7 -0
- package/dist/commands/services/meet/handler.d.ts +25 -0
- package/dist/commands/services/meet/handler.js +50 -0
- package/dist/commands/services/meet/index.d.ts +3 -0
- package/dist/commands/services/meet/index.js +43 -0
- package/dist/commands/services/meet/types.d.ts +65 -0
- package/dist/commands/services/meet/types.js +2 -0
- package/dist/commands/services/meetbot/api.d.ts +37 -0
- package/dist/commands/services/meetbot/api.js +130 -0
- package/dist/commands/services/meetbot/config.d.ts +4 -0
- package/dist/commands/services/meetbot/config.js +21 -0
- package/dist/commands/services/meetbot/descriptions.d.ts +3 -0
- package/dist/commands/services/meetbot/descriptions.js +10 -0
- package/dist/commands/services/meetbot/handler.d.ts +42 -0
- package/dist/commands/services/meetbot/handler.js +83 -0
- package/dist/commands/services/meetbot/index.d.ts +3 -0
- package/dist/commands/services/meetbot/index.js +74 -0
- package/dist/commands/services/meetbot/types.d.ts +101 -0
- package/dist/commands/services/meetbot/types.js +2 -0
- package/dist/commands/services/meetbot/utils.d.ts +28 -0
- package/dist/commands/services/meetbot/utils.js +78 -0
- package/dist/commands/services.d.ts +2 -0
- package/dist/commands/services.js +50 -0
- package/dist/commands/tenants.d.ts +2 -0
- package/dist/commands/tenants.js +46 -0
- package/dist/commands/upgrade.d.ts +2 -0
- package/dist/commands/upgrade.js +38 -0
- package/dist/commands/users.d.ts +2 -0
- package/dist/commands/users.js +182 -0
- package/dist/core/auth.d.ts +26 -0
- package/dist/core/auth.js +75 -0
- package/dist/core/config.d.ts +76 -0
- package/dist/core/config.js +255 -0
- package/dist/core/error.d.ts +128 -0
- package/dist/core/error.js +85 -0
- package/dist/core/help.d.ts +6 -0
- package/dist/core/help.js +15 -0
- package/dist/core/http.d.ts +28 -0
- package/dist/core/http.js +140 -0
- package/dist/core/logger.d.ts +24 -0
- package/dist/core/logger.js +53 -0
- package/dist/core/reserved.d.ts +13 -0
- package/dist/core/reserved.js +28 -0
- package/dist/core/tenants.d.ts +36 -0
- package/dist/core/tenants.js +50 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +152 -0
- package/dist/types/index.d.ts +59 -0
- package/dist/types/index.js +5 -0
- package/dist/utils/index.d.ts +20 -0
- package/dist/utils/index.js +133 -0
- package/dist/utils/loose-params.d.ts +9 -0
- package/dist/utils/loose-params.js +274 -0
- package/dist/utils/table.d.ts +4 -0
- package/dist/utils/table.js +25 -0
- package/package.json +46 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export interface MeetUser {
|
|
2
|
+
userID: number;
|
|
3
|
+
nickName: string;
|
|
4
|
+
}
|
|
5
|
+
export interface SyncCompanyUserResult {
|
|
6
|
+
users: Record<number, MeetUser>;
|
|
7
|
+
}
|
|
8
|
+
export interface SessionInfo {
|
|
9
|
+
firstID: number;
|
|
10
|
+
secondID: number;
|
|
11
|
+
sessionType: 1 | 3;
|
|
12
|
+
companyID?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface MsgContent {
|
|
15
|
+
content: string;
|
|
16
|
+
}
|
|
17
|
+
export interface SendMessageParams {
|
|
18
|
+
sessionInfo: SessionInfo;
|
|
19
|
+
msgContent: MsgContent;
|
|
20
|
+
}
|
|
21
|
+
export interface SendMessageResult {
|
|
22
|
+
seqId: number;
|
|
23
|
+
timestamp: number;
|
|
24
|
+
}
|
|
25
|
+
export interface BotMsgUpdate {
|
|
26
|
+
message: MsgContent;
|
|
27
|
+
}
|
|
28
|
+
export interface GetUpdatesV2Result {
|
|
29
|
+
msgs: BotMsgUpdate[];
|
|
30
|
+
quoteMsgMap: Record<string, MsgContent>;
|
|
31
|
+
}
|
|
32
|
+
export interface GetUploadURLParams {
|
|
33
|
+
originFileName: string;
|
|
34
|
+
contentType: string;
|
|
35
|
+
md5: string;
|
|
36
|
+
size: number;
|
|
37
|
+
fullImage?: boolean;
|
|
38
|
+
videoLength?: number;
|
|
39
|
+
bestDomain?: string;
|
|
40
|
+
uploadId?: string;
|
|
41
|
+
chunkNum?: number;
|
|
42
|
+
}
|
|
43
|
+
export interface UploadURLResult {
|
|
44
|
+
signedUrl: string;
|
|
45
|
+
callback?: string;
|
|
46
|
+
path: string;
|
|
47
|
+
id: number;
|
|
48
|
+
size: number;
|
|
49
|
+
}
|
|
50
|
+
export interface GetMultiPartUploadURLParams {
|
|
51
|
+
originFileName: string;
|
|
52
|
+
contentType: string;
|
|
53
|
+
md5: string;
|
|
54
|
+
size: number;
|
|
55
|
+
fullImage?: boolean;
|
|
56
|
+
videoLength?: number;
|
|
57
|
+
bestDomain?: string;
|
|
58
|
+
}
|
|
59
|
+
export interface MultiPartUploadURLResult {
|
|
60
|
+
chunkNum: number;
|
|
61
|
+
mapSignURLs: Record<string, string>;
|
|
62
|
+
ID?: number;
|
|
63
|
+
path?: string;
|
|
64
|
+
size: number;
|
|
65
|
+
}
|
|
66
|
+
export interface UploadPart {
|
|
67
|
+
partNumber: number;
|
|
68
|
+
eTag: string;
|
|
69
|
+
}
|
|
70
|
+
export interface CompleteMultipartUploadParams {
|
|
71
|
+
originFileName: string;
|
|
72
|
+
md5: string;
|
|
73
|
+
UploadParts: UploadPart[];
|
|
74
|
+
}
|
|
75
|
+
export interface CompleteMultipartUploadResult {
|
|
76
|
+
ID: number;
|
|
77
|
+
path: string;
|
|
78
|
+
}
|
|
79
|
+
export interface UploadFileResult {
|
|
80
|
+
fileID: number;
|
|
81
|
+
path: string;
|
|
82
|
+
size: number;
|
|
83
|
+
}
|
|
84
|
+
export interface SendUserTypingParams {
|
|
85
|
+
sessionInfo: SessionInfo;
|
|
86
|
+
/** 0=停止输入, 1=正在输入 */
|
|
87
|
+
action: 0 | 1;
|
|
88
|
+
}
|
|
89
|
+
export interface GetAccessURLParams {
|
|
90
|
+
firstId: number;
|
|
91
|
+
secondId: number;
|
|
92
|
+
sessionType: number;
|
|
93
|
+
seqId: number;
|
|
94
|
+
fileId: number;
|
|
95
|
+
companyId?: number;
|
|
96
|
+
/** OSS 处理参数,如图片缩放 */
|
|
97
|
+
'x-oss-process'?: string;
|
|
98
|
+
}
|
|
99
|
+
export interface AccessURLResult {
|
|
100
|
+
fileUrl: string;
|
|
101
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getCurrentUser } from '../../../core/auth.js';
|
|
2
|
+
import type { MeetUser, SyncCompanyUserResult, SessionInfo } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* 从 token 中提取 bot secret
|
|
5
|
+
* token 格式:bot_id:secret
|
|
6
|
+
*/
|
|
7
|
+
export declare function extractBotToken(token: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* 从 token 中提取 bot ID
|
|
10
|
+
* token 格式:bot_id:secret
|
|
11
|
+
*/
|
|
12
|
+
export declare function extractBotId(token: string): number | null;
|
|
13
|
+
/**
|
|
14
|
+
* 获取 Bot 鉴权信息
|
|
15
|
+
*/
|
|
16
|
+
export declare function getBotAuthHeader(userName?: string): {
|
|
17
|
+
user: NonNullable<ReturnType<typeof getCurrentUser>>;
|
|
18
|
+
headers: Record<string, string>;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* 构建 sessionInfo
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildSessionInfo(targetID: number, botId: number, isGroup: boolean, companyID: number): SessionInfo;
|
|
24
|
+
/**
|
|
25
|
+
* 规范化用户列表
|
|
26
|
+
*/
|
|
27
|
+
export declare function normalizeUsers(result: SyncCompanyUserResult): MeetUser[];
|
|
28
|
+
export declare function computeMD5(filePath: string): Promise<string>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/commands/services/meetbot/utils.ts
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { getCurrentUser } from '../../../core/auth.js';
|
|
5
|
+
import { outputCliError } from '../../../core/error.js';
|
|
6
|
+
/**
|
|
7
|
+
* 从 token 中提取 bot secret
|
|
8
|
+
* token 格式:bot_id:secret
|
|
9
|
+
*/
|
|
10
|
+
export function extractBotToken(token) {
|
|
11
|
+
if (token.includes(':')) {
|
|
12
|
+
const parts = token.split(':');
|
|
13
|
+
return parts.length >= 2 ? parts.slice(1).join(':') : token;
|
|
14
|
+
}
|
|
15
|
+
return token;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 从 token 中提取 bot ID
|
|
19
|
+
* token 格式:bot_id:secret
|
|
20
|
+
*/
|
|
21
|
+
export function extractBotId(token) {
|
|
22
|
+
if (token.includes(':')) {
|
|
23
|
+
const parts = token.split(':');
|
|
24
|
+
const id = Number(parts[0]);
|
|
25
|
+
return Number.isNaN(id) ? null : id;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 获取 Bot 鉴权信息
|
|
31
|
+
*/
|
|
32
|
+
export function getBotAuthHeader(userName) {
|
|
33
|
+
const user = getCurrentUser(userName);
|
|
34
|
+
if (!user) {
|
|
35
|
+
outputCliError('USER_NOT_FOUND', 'No user configured. Run "lxcli auth" first.');
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
user: user,
|
|
39
|
+
headers: {
|
|
40
|
+
Authorization: `Bot ${extractBotToken(user.token)}`,
|
|
41
|
+
Accept: 'application/json',
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 构建 sessionInfo
|
|
47
|
+
*/
|
|
48
|
+
export function buildSessionInfo(targetID, botId, isGroup, companyID) {
|
|
49
|
+
if (isGroup) {
|
|
50
|
+
return {
|
|
51
|
+
firstID: 1,
|
|
52
|
+
secondID: targetID,
|
|
53
|
+
sessionType: 3,
|
|
54
|
+
companyID,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const firstID = Math.min(targetID, botId);
|
|
58
|
+
const secondID = Math.max(targetID, botId);
|
|
59
|
+
return {
|
|
60
|
+
firstID,
|
|
61
|
+
secondID,
|
|
62
|
+
sessionType: 1,
|
|
63
|
+
companyID,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 规范化用户列表
|
|
68
|
+
*/
|
|
69
|
+
export function normalizeUsers(result) {
|
|
70
|
+
return Object.values(result.users || {}).map(user => ({
|
|
71
|
+
userID: user.userID,
|
|
72
|
+
nickName: user.nickName,
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
export async function computeMD5(filePath) {
|
|
76
|
+
const buffer = readFileSync(filePath);
|
|
77
|
+
return createHash('md5').update(buffer).digest('hex');
|
|
78
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/commands/services.ts
|
|
2
|
+
import { outputCliError } from '../core/error.js';
|
|
3
|
+
import { outputJson, parseListFormat } from '../utils/index.js';
|
|
4
|
+
import { formatTable } from '../utils/table.js';
|
|
5
|
+
import { externalServices } from './index.js';
|
|
6
|
+
// 服务列表(从 externalServices 动态获取)
|
|
7
|
+
const getServices = () => externalServices
|
|
8
|
+
.filter(s => s.enabled !== false)
|
|
9
|
+
.map(s => ({ name: s.name, description: s.description }));
|
|
10
|
+
export function registerServicesCommand(program) {
|
|
11
|
+
const servicesCmd = program
|
|
12
|
+
.command('services')
|
|
13
|
+
.description('List available services');
|
|
14
|
+
// services list
|
|
15
|
+
servicesCmd
|
|
16
|
+
.command('list')
|
|
17
|
+
.description('List all services')
|
|
18
|
+
.option('--format <format>', 'Output format (json, table)', 'json')
|
|
19
|
+
.action((options) => {
|
|
20
|
+
const format = parseListFormat(options.format);
|
|
21
|
+
const data = getServices();
|
|
22
|
+
if (format === 'table') {
|
|
23
|
+
console.log(formatTable(data, ['name', 'description']));
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
outputJson(data);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
// 查看服务详情(通过位置参数)
|
|
30
|
+
servicesCmd
|
|
31
|
+
.argument('[name]', 'Service name')
|
|
32
|
+
.action((name) => {
|
|
33
|
+
const SERVICES = getServices();
|
|
34
|
+
if (name) {
|
|
35
|
+
const service = SERVICES.find(s => s.name === name);
|
|
36
|
+
if (!service) {
|
|
37
|
+
outputCliError('SERVICE_NOT_FOUND', `Service "${name}" not found`, { name });
|
|
38
|
+
}
|
|
39
|
+
outputJson({
|
|
40
|
+
name: service.name,
|
|
41
|
+
description: service.description,
|
|
42
|
+
usage: `lxcli ${service.name} <resource> <method> [flags]`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// 列出所有服务
|
|
47
|
+
outputJson(SERVICES);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/commands/tenants.ts
|
|
2
|
+
import { outputCliError } from '../core/error.js';
|
|
3
|
+
import { getTenant, listTenants } from '../core/tenants.js';
|
|
4
|
+
import { outputJson, parseListFormat } from '../utils/index.js';
|
|
5
|
+
import { formatTable } from '../utils/table.js';
|
|
6
|
+
export function registerTenantsCommand(program) {
|
|
7
|
+
const tenantsCmd = program
|
|
8
|
+
.command('tenants')
|
|
9
|
+
.description('Manage tenants')
|
|
10
|
+
.addHelpCommand();
|
|
11
|
+
// tenants list
|
|
12
|
+
tenantsCmd
|
|
13
|
+
.command('list')
|
|
14
|
+
.description('List all tenants')
|
|
15
|
+
.option('--format <format>', 'Output format (json, table)', 'json')
|
|
16
|
+
.action((options) => {
|
|
17
|
+
const format = parseListFormat(options.format);
|
|
18
|
+
const tenants = listTenants();
|
|
19
|
+
const data = tenants.map(t => ({
|
|
20
|
+
name: t.name,
|
|
21
|
+
}));
|
|
22
|
+
if (format === 'table') {
|
|
23
|
+
console.log(formatTable(data, ['name']));
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
outputJson(data);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
// tenants <name> - 查看详情
|
|
30
|
+
tenantsCmd
|
|
31
|
+
.argument('[name]', 'Tenant name to show details')
|
|
32
|
+
.action((name) => {
|
|
33
|
+
if (name) {
|
|
34
|
+
const tenant = getTenant(name);
|
|
35
|
+
if (!tenant) {
|
|
36
|
+
outputCliError('TENANT_NOT_FOUND', `Tenant "${name}" not found`, { name });
|
|
37
|
+
}
|
|
38
|
+
outputJson(tenant);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// 无参数时默认列出租户
|
|
42
|
+
const tenants = listTenants();
|
|
43
|
+
outputJson(tenants.map(t => ({ name: t.name })));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// src/commands/upgrade.ts
|
|
2
|
+
import { outputCliError } from '../core/error.js';
|
|
3
|
+
import { outputJson } from '../utils/index.js';
|
|
4
|
+
const PACKAGE_NAME = '@meet-im/lxcli';
|
|
5
|
+
const NPM_REGISTRY = 'https://registry.npmjs.org';
|
|
6
|
+
async function getLatestVersion() {
|
|
7
|
+
const res = await fetch(`${NPM_REGISTRY}/${PACKAGE_NAME}/latest`);
|
|
8
|
+
if (!res.ok) {
|
|
9
|
+
throw new Error(`Registry returned ${res.status}`);
|
|
10
|
+
}
|
|
11
|
+
const data = await res.json();
|
|
12
|
+
return data.version;
|
|
13
|
+
}
|
|
14
|
+
export function registerUpgradeCommand(program) {
|
|
15
|
+
program
|
|
16
|
+
.command('upgrade')
|
|
17
|
+
.description('Check for updates and show upgrade instructions')
|
|
18
|
+
.action(async () => {
|
|
19
|
+
try {
|
|
20
|
+
const currentVersion = program.version() || '1.0.0';
|
|
21
|
+
const latestVersion = await getLatestVersion();
|
|
22
|
+
const isLatest = currentVersion === latestVersion;
|
|
23
|
+
outputJson({
|
|
24
|
+
currentVersion,
|
|
25
|
+
latestVersion,
|
|
26
|
+
isLatest,
|
|
27
|
+
message: isLatest
|
|
28
|
+
? 'Already on the latest version'
|
|
29
|
+
: `Run "npm install -g ${PACKAGE_NAME}@latest" to upgrade`,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
outputCliError('UPGRADE_CHECK_FAILED', 'Failed to check for updates', {
|
|
34
|
+
cause: error instanceof Error ? error.message : 'Unknown error',
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// src/commands/users.ts
|
|
2
|
+
import { listUsers, getUser, removeUser, setDefaultUser, setCurrentUser, disableUser, enableUser, updateUser, findUsersByName } from '../core/config.js';
|
|
3
|
+
import { outputCliError } from '../core/error.js';
|
|
4
|
+
import { outputJson, formatDate, confirm, parseListFormat } from '../utils/index.js';
|
|
5
|
+
import { formatTable } from '../utils/table.js';
|
|
6
|
+
import { ENVIRONMENTS } from '../types/index.js';
|
|
7
|
+
export function registerUsersCommand(program) {
|
|
8
|
+
const usersCmd = program
|
|
9
|
+
.command('users')
|
|
10
|
+
.description('Manage users')
|
|
11
|
+
.addHelpCommand();
|
|
12
|
+
// users list
|
|
13
|
+
usersCmd
|
|
14
|
+
.command('list')
|
|
15
|
+
.description('List all users')
|
|
16
|
+
.option('--format <format>', 'Output format (json, table)', 'json')
|
|
17
|
+
.action((options) => {
|
|
18
|
+
const format = parseListFormat(options.format);
|
|
19
|
+
const users = listUsers();
|
|
20
|
+
const data = users.map(user => ({
|
|
21
|
+
id: user.id,
|
|
22
|
+
name: user.name,
|
|
23
|
+
tenant: user.tenant,
|
|
24
|
+
env: user.env,
|
|
25
|
+
isDefault: user.isDefault || false,
|
|
26
|
+
disabled: user.disabled || false,
|
|
27
|
+
}));
|
|
28
|
+
if (format === 'table') {
|
|
29
|
+
if (data.length === 0) {
|
|
30
|
+
console.log('No users configured');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const columns = ['id', 'name', 'tenant', 'env', 'isDefault', 'disabled'];
|
|
34
|
+
console.log(formatTable(data, columns));
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
outputJson(data.length > 0 ? data : { message: 'No users configured' });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
// users <id> - 查看详情
|
|
41
|
+
usersCmd
|
|
42
|
+
.argument('[id]', 'User id to show details (name or name@tenant)')
|
|
43
|
+
.action((id) => {
|
|
44
|
+
if (!id) {
|
|
45
|
+
// 无参数时默认列出用户
|
|
46
|
+
const users = listUsers();
|
|
47
|
+
const result = users.map(user => ({
|
|
48
|
+
id: user.id,
|
|
49
|
+
name: user.name,
|
|
50
|
+
tenant: user.tenant,
|
|
51
|
+
env: user.env,
|
|
52
|
+
isDefault: user.isDefault || false,
|
|
53
|
+
disabled: user.disabled || false,
|
|
54
|
+
}));
|
|
55
|
+
outputJson(result.length > 0 ? result : { message: 'No users configured' });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const user = getUser(id);
|
|
59
|
+
if (!user) {
|
|
60
|
+
outputCliError('USER_NOT_FOUND', `User "${id}" not found`, { id });
|
|
61
|
+
}
|
|
62
|
+
outputJson({
|
|
63
|
+
id: user.id,
|
|
64
|
+
name: user.name,
|
|
65
|
+
tenant: user.tenant,
|
|
66
|
+
env: user.env,
|
|
67
|
+
isDefault: user.isDefault || false,
|
|
68
|
+
disabled: user.disabled || false,
|
|
69
|
+
createdAt: formatDate(user.createdAt),
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
// users remove <id>
|
|
73
|
+
usersCmd
|
|
74
|
+
.command('remove <id>')
|
|
75
|
+
.description('Remove user (id: name or name@tenant)')
|
|
76
|
+
.action(async (id) => {
|
|
77
|
+
const user = getUser(id);
|
|
78
|
+
if (!user) {
|
|
79
|
+
outputCliError('USER_NOT_FOUND', `User "${id}" not found`, { id });
|
|
80
|
+
}
|
|
81
|
+
const confirmed = await confirm(`Remove user "${user.id}"?`);
|
|
82
|
+
if (confirmed) {
|
|
83
|
+
removeUser(user.id);
|
|
84
|
+
outputJson({ success: true, message: `User "${user.id}" removed` });
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
// users use <id>
|
|
88
|
+
usersCmd
|
|
89
|
+
.command('use <id>')
|
|
90
|
+
.description('Switch current user (id: name or name@tenant)')
|
|
91
|
+
.action((id) => {
|
|
92
|
+
const user = getUser(id);
|
|
93
|
+
if (!user) {
|
|
94
|
+
outputCliError('USER_NOT_FOUND', `User "${id}" not found`, { id });
|
|
95
|
+
}
|
|
96
|
+
setCurrentUser(user.id);
|
|
97
|
+
outputJson({ success: true, message: `Switched to user "${user.id}"` });
|
|
98
|
+
});
|
|
99
|
+
// users default <id>
|
|
100
|
+
usersCmd
|
|
101
|
+
.command('default <id>')
|
|
102
|
+
.description('Set default user (id: name or name@tenant)')
|
|
103
|
+
.action((id) => {
|
|
104
|
+
const user = getUser(id);
|
|
105
|
+
if (!user) {
|
|
106
|
+
outputCliError('USER_NOT_FOUND', `User "${id}" not found`, { id });
|
|
107
|
+
}
|
|
108
|
+
setDefaultUser(user.id);
|
|
109
|
+
outputJson({ success: true, message: `User "${user.id}" set as default` });
|
|
110
|
+
});
|
|
111
|
+
// users disable <id>
|
|
112
|
+
usersCmd
|
|
113
|
+
.command('disable <id>')
|
|
114
|
+
.description('Disable user (id: name or name@tenant)')
|
|
115
|
+
.action((id) => {
|
|
116
|
+
const user = getUser(id);
|
|
117
|
+
if (!user) {
|
|
118
|
+
outputCliError('USER_NOT_FOUND', `User "${id}" not found`, { id });
|
|
119
|
+
}
|
|
120
|
+
disableUser(user.id);
|
|
121
|
+
outputJson({ success: true, message: `User "${user.id}" disabled` });
|
|
122
|
+
});
|
|
123
|
+
// users enable <id>
|
|
124
|
+
usersCmd
|
|
125
|
+
.command('enable <id>')
|
|
126
|
+
.description('Enable user (id: name or name@tenant)')
|
|
127
|
+
.action((id) => {
|
|
128
|
+
const user = getUser(id);
|
|
129
|
+
if (!user) {
|
|
130
|
+
outputCliError('USER_NOT_FOUND', `User "${id}" not found`, { id });
|
|
131
|
+
}
|
|
132
|
+
enableUser(user.id);
|
|
133
|
+
outputJson({ success: true, message: `User "${user.id}" enabled` });
|
|
134
|
+
});
|
|
135
|
+
// users update <id>
|
|
136
|
+
usersCmd
|
|
137
|
+
.command('update <id>')
|
|
138
|
+
.description('Update user configuration (id: name or name@tenant)')
|
|
139
|
+
.option('--token <token>', 'Update authentication token')
|
|
140
|
+
.option('-e, --env <env>', `Update environment (${ENVIRONMENTS.join(', ')})`)
|
|
141
|
+
.action((id, options) => {
|
|
142
|
+
if (!id.includes('@')) {
|
|
143
|
+
const matches = findUsersByName(id);
|
|
144
|
+
if (matches.length > 1) {
|
|
145
|
+
outputCliError('PARAM_INVALID', `User name "${id}" is ambiguous. Use name@tenant instead.`, {
|
|
146
|
+
id,
|
|
147
|
+
matches: matches.map(user => user.id),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const user = getUser(id);
|
|
152
|
+
if (!user) {
|
|
153
|
+
outputCliError('USER_NOT_FOUND', `User "${id}" not found`, { id });
|
|
154
|
+
}
|
|
155
|
+
if (options.token !== undefined && !options.token.trim()) {
|
|
156
|
+
outputCliError('PARAM_INVALID', 'Token cannot be empty', { value: options.token });
|
|
157
|
+
}
|
|
158
|
+
if (options.env !== undefined && !options.env.trim()) {
|
|
159
|
+
outputCliError('PARAM_INVALID', 'Env cannot be empty', { value: options.env });
|
|
160
|
+
}
|
|
161
|
+
if (options.env !== undefined && !ENVIRONMENTS.includes(options.env)) {
|
|
162
|
+
outputCliError('PARAM_INVALID', `Invalid env: ${options.env}. Must be one of: ${ENVIRONMENTS.join(', ')}`, {
|
|
163
|
+
value: options.env,
|
|
164
|
+
validEnvs: [...ENVIRONMENTS],
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
const updates = {};
|
|
168
|
+
if (options.token !== undefined)
|
|
169
|
+
updates.token = options.token;
|
|
170
|
+
if (options.env !== undefined)
|
|
171
|
+
updates.env = options.env;
|
|
172
|
+
if (Object.keys(updates).length === 0) {
|
|
173
|
+
outputCliError('PARAM_MISSING', 'No update options provided. Use --token or --env', {});
|
|
174
|
+
}
|
|
175
|
+
const updated = updateUser(user.id, updates);
|
|
176
|
+
outputJson({
|
|
177
|
+
success: true,
|
|
178
|
+
message: `User "${updated.id}" updated`,
|
|
179
|
+
updates,
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { UserConfig } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* 获取当前用户
|
|
4
|
+
* 优先级:--user 参数 > currentUser > isDefault > 第一个用户
|
|
5
|
+
*/
|
|
6
|
+
export declare function getCurrentUser(userName?: string): UserConfig | null;
|
|
7
|
+
/**
|
|
8
|
+
* 获取用户的网关地址
|
|
9
|
+
*/
|
|
10
|
+
export declare function getGatewayForUser(user: UserConfig, serviceName: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* 获取认证 header
|
|
13
|
+
*/
|
|
14
|
+
export declare function getAuthHeader(user: UserConfig): Record<string, string>;
|
|
15
|
+
/**
|
|
16
|
+
* 验证用户是否存在
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateUser(name: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* 切换默认用户
|
|
21
|
+
*/
|
|
22
|
+
export declare function switchDefaultUser(name: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* 切换当前用户
|
|
25
|
+
*/
|
|
26
|
+
export declare function switchCurrentUser(name: string): boolean;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// src/core/auth.ts
|
|
2
|
+
import { getUser, getDefaultUser, listUsers, setCurrentUser, setDefaultUser } from './config.js';
|
|
3
|
+
import { getGateway } from './tenants.js';
|
|
4
|
+
import { outputCliError } from './error.js';
|
|
5
|
+
function getUserByNameOrFail(userName) {
|
|
6
|
+
const user = getUser(userName);
|
|
7
|
+
if (!user) {
|
|
8
|
+
outputCliError('USER_NOT_FOUND', `User "${userName}" not found`, {
|
|
9
|
+
availableUsers: listUsers().map(u => u.id),
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
return user;
|
|
13
|
+
}
|
|
14
|
+
function getConfiguredDefaultUser() {
|
|
15
|
+
return getDefaultUser();
|
|
16
|
+
}
|
|
17
|
+
function resolveBuiltInGateway(user, serviceName) {
|
|
18
|
+
const gateway = getGateway(user.tenant, user.env, serviceName);
|
|
19
|
+
if (!gateway) {
|
|
20
|
+
outputCliError('CONFIG_READ_ERROR', `Gateway not found for tenant "${user.tenant}" service "${serviceName}" and env "${user.env}"`, {
|
|
21
|
+
tenant: user.tenant,
|
|
22
|
+
env: user.env,
|
|
23
|
+
service: serviceName,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return gateway;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 获取当前用户
|
|
30
|
+
* 优先级:--user 参数 > currentUser > isDefault > 第一个用户
|
|
31
|
+
*/
|
|
32
|
+
export function getCurrentUser(userName) {
|
|
33
|
+
if (userName) {
|
|
34
|
+
return getUserByNameOrFail(userName);
|
|
35
|
+
}
|
|
36
|
+
return getConfiguredDefaultUser();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 获取用户的网关地址
|
|
40
|
+
*/
|
|
41
|
+
export function getGatewayForUser(user, serviceName) {
|
|
42
|
+
return resolveBuiltInGateway(user, serviceName);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 获取认证 header
|
|
46
|
+
*/
|
|
47
|
+
export function getAuthHeader(user) {
|
|
48
|
+
return {
|
|
49
|
+
Authorization: `Bearer ${user.token}`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 验证用户是否存在
|
|
54
|
+
*/
|
|
55
|
+
export function validateUser(name) {
|
|
56
|
+
return getUser(name) !== undefined;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 切换默认用户
|
|
60
|
+
*/
|
|
61
|
+
export function switchDefaultUser(name) {
|
|
62
|
+
const user = getUser(name);
|
|
63
|
+
if (!user)
|
|
64
|
+
return false;
|
|
65
|
+
return setDefaultUser(user.id);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 切换当前用户
|
|
69
|
+
*/
|
|
70
|
+
export function switchCurrentUser(name) {
|
|
71
|
+
const user = getUser(name);
|
|
72
|
+
if (!user)
|
|
73
|
+
return false;
|
|
74
|
+
return setCurrentUser(user.id);
|
|
75
|
+
}
|