@honor-claw/yoyo 0.0.1-alpha.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/index.ts +25 -0
- package/openclaw.plugin.json +28 -0
- package/package.json +59 -0
- package/skills/yoyo-control/SKILL.md +346 -0
- package/skills/yoyo-control/references/capture-screenshot.md +66 -0
- package/skills/yoyo-control/references/local-search.md +27 -0
- package/skills/yoyo-control/references/open-app.md +54 -0
- package/skills/yoyo-control/references/phone-call.md +217 -0
- package/skills/yoyo-control/references/schedule.md +107 -0
- package/skills/yoyo-control/references/screen-recorder.md +67 -0
- package/skills/yoyo-control/references/search-contact.md +37 -0
- package/skills/yoyo-control/references/send-message.md +155 -0
- package/skills/yoyo-control/references/volume.md +536 -0
- package/skills/yoyo-control/scripts/README.md +103 -0
- package/skills/yoyo-control/scripts/invoke.js +119 -0
- package/skills/yoyo-control/scripts/volume-up.json +7 -0
- package/src/apis/claw-cloud.ts +74 -0
- package/src/apis/helpers.ts +10 -0
- package/src/apis/honor-auth.ts +148 -0
- package/src/apis/http-client.ts +239 -0
- package/src/apis/index.ts +8 -0
- package/src/apis/types.ts +47 -0
- package/src/cloud-channel/channel.ts +230 -0
- package/src/cloud-channel/client.ts +312 -0
- package/src/cloud-channel/index.ts +4 -0
- package/src/cloud-channel/types.ts +81 -0
- package/src/commands/index.ts +19 -0
- package/src/commands/login/impl.ts +21 -0
- package/src/commands/login/index.ts +1 -0
- package/src/commands/logout/index.ts +53 -0
- package/src/commands/status/index.ts +64 -0
- package/src/gateway-client/client.deprecated.ts +376 -0
- package/src/gateway-client/client.ts +76 -0
- package/src/gateway-client/device/auth.ts +57 -0
- package/src/gateway-client/device/builder.ts +105 -0
- package/src/gateway-client/device/helpers.ts +40 -0
- package/src/gateway-client/device/identity.ts +251 -0
- package/src/gateway-client/device/index.ts +40 -0
- package/src/gateway-client/device/types.ts +57 -0
- package/src/gateway-client/index.ts +2 -0
- package/src/gateway-client/types.deprecated.ts +217 -0
- package/src/gateway-client/types.ts +8 -0
- package/src/honor-auth/browser.ts +82 -0
- package/src/honor-auth/callback-server.ts +112 -0
- package/src/honor-auth/cloud.ts +70 -0
- package/src/honor-auth/config.ts +35 -0
- package/src/honor-auth/index.ts +2 -0
- package/src/honor-auth/token-manager.ts +80 -0
- package/src/honor-auth/types.ts +43 -0
- package/src/index.ts +10 -0
- package/src/modules/claw-configs/config-manager.ts +172 -0
- package/src/modules/claw-configs/index.ts +7 -0
- package/src/modules/claw-configs/types.ts +30 -0
- package/src/modules/device/device-info.ts +70 -0
- package/src/modules/device/index.ts +3 -0
- package/src/modules/device/providers/base.ts +27 -0
- package/src/modules/device/providers/pad.ts +114 -0
- package/src/modules/device/providers/windows.ts +67 -0
- package/src/modules/device/registry.ts +34 -0
- package/src/modules/login/impl.ts +70 -0
- package/src/modules/login/index.ts +6 -0
- package/src/runtime.ts +14 -0
- package/src/schemas.ts +20 -0
- package/src/services/connection/impl.ts +259 -0
- package/src/services/connection/index.ts +1 -0
- package/src/services/connection/types.ts +20 -0
- package/src/types.ts +64 -0
- package/src/utils/id.ts +8 -0
- package/src/utils/jwt.ts +36 -0
- package/src/utils/logger.ts +20 -0
- package/src/utils/proxy.ts +58 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createHonorAuthClient } from '../apis/honor-auth.js';
|
|
2
|
+
import type { DeviceInfo, HonorUserInfo } from '../types.js';
|
|
3
|
+
import { startCallbackServer } from './callback-server.js';
|
|
4
|
+
import { getConfig } from './config.js';
|
|
5
|
+
import { saveToken } from './token-manager.js';
|
|
6
|
+
import type { HonorAuthConfig } from './types.js';
|
|
7
|
+
import open from 'open';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 执行OAuth2授权流程(自动打开浏览器)
|
|
11
|
+
*/
|
|
12
|
+
export async function performOAuth2AuthWithBrowser(deviceInfo: DeviceInfo, configOverrides?: Partial<HonorAuthConfig>) {
|
|
13
|
+
const config = getConfig(configOverrides);
|
|
14
|
+
|
|
15
|
+
console.log('🔐 开始荣耀OAuth2认证流程(自动打开浏览器)...');
|
|
16
|
+
console.log(`📱 设备信息: ${deviceInfo.deviceName} (${deviceInfo.deviceModel})`);
|
|
17
|
+
console.log(`🆔 设备ID: ${deviceInfo.deviceId}`);
|
|
18
|
+
|
|
19
|
+
// 创建认证客户端
|
|
20
|
+
const authClient = createHonorAuthClient(config);
|
|
21
|
+
|
|
22
|
+
// 生成PKCE参数
|
|
23
|
+
const pkceParams = authClient.generatePKCEParams();
|
|
24
|
+
console.log('🔑 PKCE参数已生成');
|
|
25
|
+
|
|
26
|
+
// 构建授权URL
|
|
27
|
+
const authUrl = authClient.buildAuthUrl(pkceParams);
|
|
28
|
+
console.log(`🔗 授权URL: ${authUrl}`);
|
|
29
|
+
|
|
30
|
+
// 启动本地回调服务器
|
|
31
|
+
let receivedCode: string | null = null;
|
|
32
|
+
const serverPromise = startCallbackServer({
|
|
33
|
+
port: config.localPort,
|
|
34
|
+
timeout: 120000,
|
|
35
|
+
onCodeReceived: code => {
|
|
36
|
+
receivedCode = code;
|
|
37
|
+
},
|
|
38
|
+
onError: error => {
|
|
39
|
+
console.error('❌ 回调服务器错误:', error);
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// 打开浏览器
|
|
44
|
+
console.log('🌐 正在打开浏览器...');
|
|
45
|
+
try {
|
|
46
|
+
await open(authUrl);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.warn('⚠️ 自动打开浏览器失败,请手动访问授权URL');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 等待回调
|
|
52
|
+
try {
|
|
53
|
+
await serverPromise;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
throw new Error(`等待授权超时: ${error instanceof Error ? error.message : String(error)}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!receivedCode) {
|
|
59
|
+
throw new Error('未收到授权码');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 使用授权码换取Token
|
|
63
|
+
console.log('🔄 正在换取Token...');
|
|
64
|
+
try {
|
|
65
|
+
const tokenInfo = await authClient.exchangeToken(receivedCode);
|
|
66
|
+
|
|
67
|
+
if (!tokenInfo.token || !tokenInfo.userInfo?.userId) {
|
|
68
|
+
throw new Error('获取用户信息失败,未返回用户ID');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 保存Token
|
|
72
|
+
await saveToken(tokenInfo, config.saveToFile !== false);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
userId: tokenInfo.userInfo.userId,
|
|
76
|
+
token: tokenInfo.token,
|
|
77
|
+
userName: tokenInfo.userInfo.displayName,
|
|
78
|
+
} as HonorUserInfo;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
throw new Error(`换取Token失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本地HTTP回调服务器 - 接收OAuth2授权回调
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createServer, type IncomingMessage, type ServerResponse } from 'http';
|
|
6
|
+
import { URL } from 'url';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 回调服务器选项
|
|
10
|
+
*/
|
|
11
|
+
export interface CallbackServerOptions {
|
|
12
|
+
/** 监听端口 */
|
|
13
|
+
port: number;
|
|
14
|
+
/** 超时时间(毫秒) */
|
|
15
|
+
timeout?: number;
|
|
16
|
+
/** 授权码回调处理 */
|
|
17
|
+
onCodeReceived: (code: string) => void;
|
|
18
|
+
/** 错误回调 */
|
|
19
|
+
onError?: (error: Error) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 启动本地回调服务器
|
|
24
|
+
*/
|
|
25
|
+
export function startCallbackServer(options: CallbackServerOptions): Promise<void> {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const { port, timeout = 120000, onCodeReceived, onError } = options;
|
|
28
|
+
let timeoutId: NodeJS.Timeout | null = null;
|
|
29
|
+
let serverClosed = false;
|
|
30
|
+
let hasReceivedCode = false;
|
|
31
|
+
let promiseResolved = false;
|
|
32
|
+
|
|
33
|
+
const server = createServer((req: IncomingMessage, res: ServerResponse) => {
|
|
34
|
+
try {
|
|
35
|
+
const url = new URL(req.url || '', `http://localhost:${port}`);
|
|
36
|
+
const code = url.searchParams.get('code') || '';
|
|
37
|
+
|
|
38
|
+
if (code && !hasReceivedCode) {
|
|
39
|
+
hasReceivedCode = true;
|
|
40
|
+
console.log('✅ 成功获取授权码 code');
|
|
41
|
+
|
|
42
|
+
// 返回成功响应
|
|
43
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
44
|
+
res.end('<h1>授权成功!可以关闭此窗口</h1>');
|
|
45
|
+
|
|
46
|
+
// 延迟关闭服务器,确保响应已发送
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
if (!serverClosed) {
|
|
49
|
+
serverClosed = true;
|
|
50
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
51
|
+
server.close();
|
|
52
|
+
}
|
|
53
|
+
}, 100);
|
|
54
|
+
|
|
55
|
+
// 调用回调
|
|
56
|
+
onCodeReceived(code);
|
|
57
|
+
} else if (!hasReceivedCode) {
|
|
58
|
+
// 返回错误响应
|
|
59
|
+
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
60
|
+
res.end('<h1>授权失败:未获取到授权码</h1>');
|
|
61
|
+
} else {
|
|
62
|
+
// 重复请求,返回成功响应但不处理
|
|
63
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
64
|
+
res.end('<h1>授权成功!可以关闭此窗口</h1>');
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error('❌ 处理回调请求失败:', error);
|
|
68
|
+
res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
69
|
+
res.end('<h1>服务器错误</h1>');
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
server.on('error', (error) => {
|
|
74
|
+
if (!serverClosed) {
|
|
75
|
+
serverClosed = true;
|
|
76
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
77
|
+
onError?.(error);
|
|
78
|
+
if (!promiseResolved) {
|
|
79
|
+
promiseResolved = true;
|
|
80
|
+
reject(error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
server.listen(port, () => {
|
|
86
|
+
console.log(`📡 本地回调服务器已启动,监听端口: ${port}`);
|
|
87
|
+
|
|
88
|
+
// 设置超时
|
|
89
|
+
timeoutId = setTimeout(() => {
|
|
90
|
+
if (!serverClosed) {
|
|
91
|
+
serverClosed = true;
|
|
92
|
+
server.close();
|
|
93
|
+
const error = new Error('等待授权超时');
|
|
94
|
+
onError?.(error);
|
|
95
|
+
if (!promiseResolved) {
|
|
96
|
+
promiseResolved = true;
|
|
97
|
+
reject(error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}, timeout);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// 服务器关闭时解析Promise
|
|
104
|
+
server.on('close', () => {
|
|
105
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
106
|
+
if (!promiseResolved) {
|
|
107
|
+
promiseResolved = true;
|
|
108
|
+
resolve();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createHonorAuthClient } from '../apis/honor-auth.js';
|
|
2
|
+
import type { DeviceInfo, HonorUserInfo } from '../types.js';
|
|
3
|
+
import { startCallbackServer } from './callback-server.js';
|
|
4
|
+
import { getConfig } from './config.js';
|
|
5
|
+
import { saveToken } from './token-manager.js';
|
|
6
|
+
import type { HonorAuthConfig } from './types.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 执行云侧OAuth2授权流程
|
|
10
|
+
*/
|
|
11
|
+
export async function performOAuth2Auth(deviceInfo: DeviceInfo, configOverrides?: Partial<HonorAuthConfig>) {
|
|
12
|
+
const config = getConfig(configOverrides);
|
|
13
|
+
|
|
14
|
+
console.log('🔐 开始荣耀OAuth2认证流程...');
|
|
15
|
+
console.log(`📱 设备信息: ${deviceInfo.deviceName} (${deviceInfo.deviceModel})`);
|
|
16
|
+
console.log(`🆔 设备ID: ${deviceInfo.deviceId}`);
|
|
17
|
+
|
|
18
|
+
// 创建认证客户端
|
|
19
|
+
const authClient = createHonorAuthClient(config);
|
|
20
|
+
|
|
21
|
+
// 生成PKCE参数
|
|
22
|
+
const pkceParams = authClient.generatePKCEParams();
|
|
23
|
+
console.log('🔑 PKCE参数已生成');
|
|
24
|
+
|
|
25
|
+
// 构建授权URL
|
|
26
|
+
const authUrl = authClient.buildAuthUrl(pkceParams);
|
|
27
|
+
console.log(`🔗 授权URL: ${authUrl}`);
|
|
28
|
+
|
|
29
|
+
// 启动本地回调服务器
|
|
30
|
+
let receivedCode: string | null = null;
|
|
31
|
+
try {
|
|
32
|
+
await startCallbackServer({
|
|
33
|
+
port: config.localPort,
|
|
34
|
+
timeout: 120000,
|
|
35
|
+
onCodeReceived: code => {
|
|
36
|
+
receivedCode = code;
|
|
37
|
+
},
|
|
38
|
+
onError: error => {
|
|
39
|
+
console.error('❌ 回调服务器错误:', error);
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
} catch (error) {
|
|
43
|
+
throw new Error(`回调服务器启动失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!receivedCode) {
|
|
47
|
+
throw new Error('未收到授权码');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 使用授权码换取Token
|
|
51
|
+
console.log('🔄 正在换取Token...');
|
|
52
|
+
try {
|
|
53
|
+
const tokenInfo = await authClient.exchangeToken(receivedCode);
|
|
54
|
+
|
|
55
|
+
if (!tokenInfo.token || !tokenInfo.userInfo?.userId) {
|
|
56
|
+
throw new Error('获取用户信息失败,未返回用户ID');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 保存Token
|
|
60
|
+
await saveToken(tokenInfo, config.saveToFile !== false);
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
userId: tokenInfo.userInfo.userId,
|
|
64
|
+
token: tokenInfo.token,
|
|
65
|
+
userName: tokenInfo.userInfo.displayName,
|
|
66
|
+
} as HonorUserInfo;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
throw new Error(`换取Token失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 荣耀认证配置管理
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { HonorAuthConfig } from './types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 默认配置
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_CONFIG: HonorAuthConfig = {
|
|
11
|
+
authHost: 'https://hnoauth-login-test-drcn.cloud.honor.com',
|
|
12
|
+
clientId: '221641491',
|
|
13
|
+
redirectUri: 'http://127.0.0.1:8081/deepLink',
|
|
14
|
+
localPort: 8081,
|
|
15
|
+
scope: 'openid profile',
|
|
16
|
+
reqClientType: '110',
|
|
17
|
+
loginChannel: '99011000',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 获取配置(支持自定义覆盖)
|
|
22
|
+
*/
|
|
23
|
+
export function getConfig(overrides?: Partial<HonorAuthConfig>): HonorAuthConfig {
|
|
24
|
+
return {
|
|
25
|
+
...DEFAULT_CONFIG,
|
|
26
|
+
...overrides,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 获取默认配置
|
|
32
|
+
*/
|
|
33
|
+
export function getDefaultConfig(): HonorAuthConfig {
|
|
34
|
+
return { ...DEFAULT_CONFIG };
|
|
35
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token管理器 - 负责token的读写缓存
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { TokenResponse } from './types.js';
|
|
6
|
+
import type { HonorUserInfo } from '../types.js';
|
|
7
|
+
import { getConfigManager } from '../modules/claw-configs/index.js';
|
|
8
|
+
import { useClawLogger } from '../utils/logger.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 保存Token到openclaw配置文件
|
|
12
|
+
*/
|
|
13
|
+
export async function saveToken(token: TokenResponse, saveToFile: boolean = true): Promise<void> {
|
|
14
|
+
try {
|
|
15
|
+
if (!saveToFile) {
|
|
16
|
+
console.log('💾 Token已获取(未保存到文件)');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const configManager = getConfigManager();
|
|
21
|
+
|
|
22
|
+
// 计算过期时间(假设token有效期为30天)
|
|
23
|
+
const expired = Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60;
|
|
24
|
+
|
|
25
|
+
// 更新用户配置
|
|
26
|
+
await configManager.updateUserConfig({
|
|
27
|
+
token: token.token,
|
|
28
|
+
expired,
|
|
29
|
+
userId: token.userInfo?.userId,
|
|
30
|
+
userName: token.userInfo?.displayName,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
console.log('💾 Token已保存到openclaw配置文件');
|
|
34
|
+
} catch (error) {
|
|
35
|
+
throw new Error(`保存Token失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 从openclaw配置文件加载Token
|
|
41
|
+
*/
|
|
42
|
+
export async function loadToken(): Promise<HonorUserInfo | null> {
|
|
43
|
+
try {
|
|
44
|
+
const configManager = getConfigManager();
|
|
45
|
+
const userConfig = configManager.getUserConfig();
|
|
46
|
+
|
|
47
|
+
// todo 上线前移除
|
|
48
|
+
if (!userConfig?.token) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 检查是否过期
|
|
53
|
+
if (userConfig.expired && userConfig.expired < Math.floor(Date.now() / 1000)) {
|
|
54
|
+
// Token已过期,从配置中移除
|
|
55
|
+
await configManager.clearUserConfig();
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
userId: userConfig.userId || '',
|
|
61
|
+
token: userConfig.token,
|
|
62
|
+
userName: userConfig.userName,
|
|
63
|
+
};
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw new Error(`加载Token失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 清除Token(用于登出或设备未注册场景)
|
|
71
|
+
*/
|
|
72
|
+
export async function clearToken(): Promise<void> {
|
|
73
|
+
try {
|
|
74
|
+
const configManager = getConfigManager();
|
|
75
|
+
await configManager.clearUserConfig();
|
|
76
|
+
useClawLogger().info('[yoyoclaw-auth] token cleared');
|
|
77
|
+
} catch (error) {
|
|
78
|
+
throw new Error(`清除Token失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 荣耀OAuth2认证相关类型定义
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 荣耀认证配置
|
|
7
|
+
*/
|
|
8
|
+
export interface HonorAuthConfig {
|
|
9
|
+
/** 认证服务器地址 */
|
|
10
|
+
authHost: string;
|
|
11
|
+
/** 客户端ID */
|
|
12
|
+
clientId: string;
|
|
13
|
+
/** 回调地址 */
|
|
14
|
+
redirectUri: string;
|
|
15
|
+
/** 本地监听端口 */
|
|
16
|
+
localPort: number;
|
|
17
|
+
/** 授权范围 */
|
|
18
|
+
scope: string;
|
|
19
|
+
/** 请求客户端类型 */
|
|
20
|
+
reqClientType: string;
|
|
21
|
+
/** 登录渠道 */
|
|
22
|
+
loginChannel: string;
|
|
23
|
+
/** 代理地址,例如:http://127.0.0.1:7890 */
|
|
24
|
+
proxy?: string;
|
|
25
|
+
/** 是否保存token到配置文件,默认为true */
|
|
26
|
+
saveToFile?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Token响应
|
|
31
|
+
*/
|
|
32
|
+
export type TokenResponse = {
|
|
33
|
+
token?: string;
|
|
34
|
+
userInfo?: ApiUserInfo;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 用户信息
|
|
39
|
+
*/
|
|
40
|
+
export interface ApiUserInfo {
|
|
41
|
+
userId: string;
|
|
42
|
+
displayName: string;
|
|
43
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YOYO Claw Extension 主入口
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export * from './honor-auth/index.js';
|
|
6
|
+
export * from './cloud-channel/index.js';
|
|
7
|
+
export * from './modules/login/index.js';
|
|
8
|
+
export * from './services/connection/index.js';
|
|
9
|
+
export * from './schemas.js';
|
|
10
|
+
export * from './types.js';
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置管理器 - 统一管理 openclaw 配置文件的读写
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { OpenClawConfig } from 'openclaw/plugin-sdk';
|
|
6
|
+
import { getYoyoRuntime } from '../../runtime.js';
|
|
7
|
+
import type { GatewayAuthConfig, UserConfig } from './types.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 配置管理器类
|
|
11
|
+
*/
|
|
12
|
+
export class ConfigManager {
|
|
13
|
+
/**
|
|
14
|
+
* 加载完整配置
|
|
15
|
+
*/
|
|
16
|
+
loadConfig(): OpenClawConfig {
|
|
17
|
+
try {
|
|
18
|
+
const runtime = getYoyoRuntime();
|
|
19
|
+
return runtime.config.loadConfig();
|
|
20
|
+
} catch (error) {
|
|
21
|
+
throw new Error(`加载配置失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 保存完整配置
|
|
27
|
+
*/
|
|
28
|
+
async saveConfig(config: OpenClawConfig): Promise<void> {
|
|
29
|
+
try {
|
|
30
|
+
const runtime = getYoyoRuntime();
|
|
31
|
+
await runtime.config.writeConfigFile(config);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
throw new Error(`保存配置失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 获取 Gateway 认证配置
|
|
39
|
+
*/
|
|
40
|
+
getGatewayAuthConfig(): GatewayAuthConfig | undefined {
|
|
41
|
+
try {
|
|
42
|
+
const config = this.loadConfig();
|
|
43
|
+
const authConfig = config.gateway?.auth;
|
|
44
|
+
|
|
45
|
+
if (!authConfig) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const result: GatewayAuthConfig = {};
|
|
50
|
+
|
|
51
|
+
if (authConfig.token) {
|
|
52
|
+
result.token = authConfig.token;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (authConfig.password) {
|
|
56
|
+
result.password = authConfig.password;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 如果没有任何认证信息,返回 undefined
|
|
60
|
+
if (Object.keys(result).length === 0) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return result;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(`[claw-configs] Failed to read gateway auth config: ${error}`);
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 获取 Gateway 端口配置
|
|
73
|
+
*/
|
|
74
|
+
getGatewayPort(): number {
|
|
75
|
+
try {
|
|
76
|
+
const config = this.loadConfig();
|
|
77
|
+
const port = config.gateway?.port;
|
|
78
|
+
return port ?? 18789;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error(`[claw-configs] Failed to read gateway port config: ${error}`);
|
|
81
|
+
return 18789;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 获取用户配置
|
|
87
|
+
*/
|
|
88
|
+
getUserConfig(): UserConfig | undefined {
|
|
89
|
+
try {
|
|
90
|
+
const config = this.loadConfig();
|
|
91
|
+
const userConfig = config.plugins?.entries?.yoyoclaw?.config?.user as UserConfig | undefined;
|
|
92
|
+
return userConfig;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error(`[claw-configs] Failed to read user config: ${error}`);
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 更新用户配置
|
|
101
|
+
*/
|
|
102
|
+
async updateUserConfig(userConfig: UserConfig): Promise<void> {
|
|
103
|
+
try {
|
|
104
|
+
const currentConfig = this.loadConfig();
|
|
105
|
+
|
|
106
|
+
const updatedConfig: OpenClawConfig = {
|
|
107
|
+
...currentConfig,
|
|
108
|
+
plugins: {
|
|
109
|
+
...currentConfig.plugins,
|
|
110
|
+
entries: {
|
|
111
|
+
...currentConfig.plugins?.entries,
|
|
112
|
+
yoyoclaw: {
|
|
113
|
+
...currentConfig.plugins?.entries?.yoyoclaw,
|
|
114
|
+
enabled: true,
|
|
115
|
+
config: {
|
|
116
|
+
user: userConfig,
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
await this.saveConfig(updatedConfig);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
throw new Error(`更新用户配置失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 清除用户配置
|
|
131
|
+
*/
|
|
132
|
+
async clearUserConfig(): Promise<void> {
|
|
133
|
+
try {
|
|
134
|
+
const currentConfig = this.loadConfig();
|
|
135
|
+
|
|
136
|
+
const updatedConfig: OpenClawConfig = {
|
|
137
|
+
...currentConfig,
|
|
138
|
+
plugins: {
|
|
139
|
+
...currentConfig.plugins,
|
|
140
|
+
entries: {
|
|
141
|
+
...currentConfig.plugins?.entries,
|
|
142
|
+
yoyoclaw: {
|
|
143
|
+
...currentConfig.plugins?.entries?.yoyoclaw,
|
|
144
|
+
config: {
|
|
145
|
+
user: undefined,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
await this.saveConfig(updatedConfig);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
throw new Error(`清除用户配置失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 配置管理器单例
|
|
161
|
+
*/
|
|
162
|
+
let configManagerInstance: ConfigManager | null = null;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 获取配置管理器实例
|
|
166
|
+
*/
|
|
167
|
+
export function getConfigManager(): ConfigManager {
|
|
168
|
+
if (!configManagerInstance) {
|
|
169
|
+
configManagerInstance = new ConfigManager();
|
|
170
|
+
}
|
|
171
|
+
return configManagerInstance;
|
|
172
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claw 配置类型定义
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { OpenClawConfig } from 'openclaw/plugin-sdk';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Gateway 认证配置
|
|
9
|
+
*/
|
|
10
|
+
export type GatewayAuthConfig = Pick<OpenClawConfig['gateway']['auth'], 'token' | 'password'>;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 用户配置信息
|
|
14
|
+
*/
|
|
15
|
+
export interface UserConfig {
|
|
16
|
+
token?: string;
|
|
17
|
+
expired?: number;
|
|
18
|
+
userId?: string;
|
|
19
|
+
userName?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* YoyoClaw 插件配置
|
|
24
|
+
*/
|
|
25
|
+
export interface YoyoClawPluginConfig {
|
|
26
|
+
enabled?: boolean;
|
|
27
|
+
config?: {
|
|
28
|
+
user?: UserConfig;
|
|
29
|
+
};
|
|
30
|
+
}
|