@honor-claw/yoyo 0.0.1-beta.2 → 0.0.1-beta.21
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 +2 -2
- package/openclaw.plugin.json +7 -0
- package/package.json +20 -20
- package/skills/search/SKILL.md +182 -0
- package/skills/search/scripts/search.sh +69 -0
- package/skills/yoyo-control/SKILL.md +105 -120
- package/skills/yoyo-control/references/alarm-create.md +473 -0
- package/skills/yoyo-control/references/app-close.md +183 -0
- package/skills/yoyo-control/references/app-open.md +178 -0
- package/skills/yoyo-control/references/call-phone.md +250 -0
- package/skills/yoyo-control/references/capture-screenshot.md +205 -54
- package/skills/yoyo-control/references/contact-search.md +235 -0
- package/skills/yoyo-control/references/hotspot.md +208 -0
- package/skills/yoyo-control/references/local-search.md +224 -15
- package/skills/yoyo-control/references/message-send.md +246 -0
- package/skills/yoyo-control/references/mobile-data.md +248 -0
- package/skills/yoyo-control/references/no-disturb.md +239 -0
- package/skills/yoyo-control/references/quiet-mode.md +228 -0
- package/skills/yoyo-control/references/ringing-mode.md +223 -0
- package/skills/yoyo-control/references/screen-record.md +220 -0
- package/skills/yoyo-control/references/vibration-mode.md +235 -0
- package/skills/yoyo-control/references/volume-operate.md +274 -0
- package/skills/yoyo-control/scripts/invoke.js +33 -111
- package/src/agent/copy-templates.ts +56 -0
- package/src/agent/index.ts +3 -0
- package/src/agent/templates/AGENTS.md +223 -0
- package/src/apis/claw-cloud.ts +70 -23
- package/src/apis/honor-auth.ts +20 -10
- package/src/apis/types.ts +24 -1
- package/src/cloud-channel/channel.ts +245 -58
- package/src/cloud-channel/client.ts +87 -12
- package/src/cloud-channel/types.ts +30 -0
- package/src/commands/env/impl.ts +58 -0
- package/src/commands/env/index.ts +1 -0
- package/src/commands/index.ts +11 -1
- package/src/commands/login/impl.ts +17 -8
- package/src/commands/logout/impl.ts +23 -0
- package/src/commands/logout/index.ts +1 -53
- package/src/commands/status/index.ts +172 -42
- package/src/gateway-client/client.deprecated.ts +1 -1
- package/src/gateway-client/client.ts +15 -20
- package/src/gateway-client/types.ts +2 -2
- package/src/honor-auth/browser.ts +12 -15
- package/src/honor-auth/callback-server.ts +3 -6
- package/src/honor-auth/cloud.ts +65 -12
- package/src/honor-auth/config.ts +25 -17
- package/src/honor-auth/index.ts +1 -0
- package/src/honor-auth/token-manager.ts +24 -14
- package/src/modules/claw-configs/config-manager.ts +211 -11
- package/src/modules/claw-configs/hosts.ts +48 -0
- package/src/modules/claw-configs/index.ts +1 -0
- package/src/modules/claw-configs/types.ts +4 -0
- package/src/modules/device/device-info.ts +20 -9
- package/src/modules/device/providers/linux.ts +128 -0
- package/src/modules/device/providers/macos.ts +123 -0
- package/src/modules/device/providers/pad.ts +0 -16
- package/src/modules/device/registry.ts +12 -3
- package/src/modules/login/impl.ts +38 -16
- package/src/runtime.ts +44 -0
- package/src/schemas.ts +4 -1
- package/src/services/connection/impl.ts +89 -9
- package/src/services/connection/status-tracker/events.ts +127 -0
- package/src/services/connection/status-tracker/index.ts +31 -0
- package/src/services/connection/status-tracker/storage.ts +133 -0
- package/src/services/connection/status-tracker/tracker.ts +370 -0
- package/src/services/connection/status-tracker/types.ts +131 -0
- package/src/types.ts +0 -4
- package/src/utils/fs-safe.ts +544 -0
- package/src/utils/version.ts +29 -0
- package/src/utils/ws.ts +21 -0
- package/skills/yoyo-control/references/open-app.md +0 -54
- package/skills/yoyo-control/references/phone-call.md +0 -217
- package/skills/yoyo-control/references/schedule.md +0 -107
- package/skills/yoyo-control/references/screen-recorder.md +0 -67
- package/skills/yoyo-control/references/search-contact.md +0 -37
- package/skills/yoyo-control/references/send-message.md +0 -155
- package/skills/yoyo-control/references/volume.md +0 -536
- package/skills/yoyo-control/scripts/README.md +0 -103
- package/skills/yoyo-control/scripts/volume-up.json +0 -7
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
* 配置管理器 - 统一管理 openclaw 配置文件的读写
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { OpenClawConfig } from
|
|
6
|
-
import { getYoyoRuntime } from
|
|
7
|
-
import
|
|
5
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
6
|
+
import { getYoyoRuntime } from "../../runtime.js";
|
|
7
|
+
import { isBetaVersion } from "../../utils/version.js";
|
|
8
|
+
import type { GatewayAuthConfig, UserConfig } from "./types.js";
|
|
8
9
|
|
|
9
|
-
const PLUGIN_YOYO_ID =
|
|
10
|
+
const PLUGIN_YOYO_ID = "yoyo";
|
|
11
|
+
const YOYO_ALLOW_COMMANDS = ["mobile-data", "hotspot", "volume.operate", "no-disturb", "screen-record", "quiet-mode", "ringing-mode", "vibration-mode", "capture-screenshot", "app.open", "app.close", "contact.search", "call.phone", "message.send", "local-search", "alarm.create"]
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* 配置管理器类
|
|
@@ -20,7 +22,10 @@ export class ConfigManager {
|
|
|
20
22
|
const runtime = getYoyoRuntime();
|
|
21
23
|
return runtime.config.loadConfig();
|
|
22
24
|
} catch (error) {
|
|
23
|
-
throw new Error(
|
|
25
|
+
throw new Error(
|
|
26
|
+
`Failed to load config: ${error instanceof Error ? error.message : String(error)
|
|
27
|
+
}`
|
|
28
|
+
);
|
|
24
29
|
}
|
|
25
30
|
}
|
|
26
31
|
|
|
@@ -32,7 +37,10 @@ export class ConfigManager {
|
|
|
32
37
|
const runtime = getYoyoRuntime();
|
|
33
38
|
await runtime.config.writeConfigFile(config);
|
|
34
39
|
} catch (error) {
|
|
35
|
-
throw new Error(
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Failed to save config: ${error instanceof Error ? error.message : String(error)
|
|
42
|
+
}`
|
|
43
|
+
);
|
|
36
44
|
}
|
|
37
45
|
}
|
|
38
46
|
|
|
@@ -65,7 +73,9 @@ export class ConfigManager {
|
|
|
65
73
|
|
|
66
74
|
return result;
|
|
67
75
|
} catch (error) {
|
|
68
|
-
console.error(
|
|
76
|
+
console.error(
|
|
77
|
+
`[claw-configs] Failed to read gateway auth config: ${error}`
|
|
78
|
+
);
|
|
69
79
|
return undefined;
|
|
70
80
|
}
|
|
71
81
|
}
|
|
@@ -79,7 +89,9 @@ export class ConfigManager {
|
|
|
79
89
|
const port = config.gateway?.port;
|
|
80
90
|
return port ?? 18789;
|
|
81
91
|
} catch (error) {
|
|
82
|
-
console.error(
|
|
92
|
+
console.error(
|
|
93
|
+
`[claw-configs] Failed to read gateway port config: ${error}`
|
|
94
|
+
);
|
|
83
95
|
return 18789;
|
|
84
96
|
}
|
|
85
97
|
}
|
|
@@ -90,7 +102,8 @@ export class ConfigManager {
|
|
|
90
102
|
getUserConfig(): UserConfig | undefined {
|
|
91
103
|
try {
|
|
92
104
|
const config = this.loadConfig();
|
|
93
|
-
const userConfig = config.plugins?.entries?.[PLUGIN_YOYO_ID]?.config
|
|
105
|
+
const userConfig = config.plugins?.entries?.[PLUGIN_YOYO_ID]?.config
|
|
106
|
+
?.user as UserConfig | undefined;
|
|
94
107
|
return userConfig;
|
|
95
108
|
} catch (error) {
|
|
96
109
|
console.error(`[claw-configs] Failed to read user config: ${error}`);
|
|
@@ -98,12 +111,121 @@ export class ConfigManager {
|
|
|
98
111
|
}
|
|
99
112
|
}
|
|
100
113
|
|
|
114
|
+
/**
|
|
115
|
+
* 获取运行环境配置
|
|
116
|
+
*/
|
|
117
|
+
getEnv(): "test" | "production" {
|
|
118
|
+
try {
|
|
119
|
+
const config = this.loadConfig();
|
|
120
|
+
const env = config.plugins?.entries?.[PLUGIN_YOYO_ID]?.config?.env as
|
|
121
|
+
| "test"
|
|
122
|
+
| "production"
|
|
123
|
+
| undefined;
|
|
124
|
+
return env || "production";
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error(`[claw-configs] Failed to read env config: ${error}`);
|
|
127
|
+
return "production";
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 获取灰度标签配置
|
|
133
|
+
*/
|
|
134
|
+
getGrayTag(): string | undefined {
|
|
135
|
+
try {
|
|
136
|
+
const config = this.loadConfig();
|
|
137
|
+
const grayTag = config.plugins?.entries?.[PLUGIN_YOYO_ID]?.config?.gray as
|
|
138
|
+
| string
|
|
139
|
+
| undefined;
|
|
140
|
+
return grayTag;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error(`[claw-configs] Failed to read gray tag config: ${error}`);
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 更新运行环境配置
|
|
149
|
+
*/
|
|
150
|
+
async updateEnv(env: "test" | "production"): Promise<void> {
|
|
151
|
+
try {
|
|
152
|
+
const currentConfig = this.loadConfig();
|
|
153
|
+
const currentUserConfig = this.getUserConfig() || {};
|
|
154
|
+
const currentGrayTag = this.getGrayTag();
|
|
155
|
+
|
|
156
|
+
const updatedConfig: OpenClawConfig = {
|
|
157
|
+
...currentConfig,
|
|
158
|
+
plugins: {
|
|
159
|
+
...currentConfig.plugins,
|
|
160
|
+
entries: {
|
|
161
|
+
...currentConfig.plugins?.entries,
|
|
162
|
+
[PLUGIN_YOYO_ID]: {
|
|
163
|
+
...currentConfig.plugins?.entries?.[PLUGIN_YOYO_ID],
|
|
164
|
+
enabled: true,
|
|
165
|
+
config: {
|
|
166
|
+
user: currentUserConfig,
|
|
167
|
+
env,
|
|
168
|
+
...(currentGrayTag && { gray: currentGrayTag }),
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
await this.saveConfig(updatedConfig);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`Failed to update env config: ${error instanceof Error ? error.message : String(error)
|
|
179
|
+
}`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 更新灰度标签配置
|
|
186
|
+
*/
|
|
187
|
+
async updateGrayTag(grayTag: string | undefined): Promise<void> {
|
|
188
|
+
try {
|
|
189
|
+
const currentConfig = this.loadConfig();
|
|
190
|
+
const currentUserConfig = this.getUserConfig() || {};
|
|
191
|
+
const currentEnv = this.getEnv();
|
|
192
|
+
|
|
193
|
+
const updatedConfig: OpenClawConfig = {
|
|
194
|
+
...currentConfig,
|
|
195
|
+
plugins: {
|
|
196
|
+
...currentConfig.plugins,
|
|
197
|
+
entries: {
|
|
198
|
+
...currentConfig.plugins?.entries,
|
|
199
|
+
[PLUGIN_YOYO_ID]: {
|
|
200
|
+
...currentConfig.plugins?.entries?.[PLUGIN_YOYO_ID],
|
|
201
|
+
enabled: true,
|
|
202
|
+
config: {
|
|
203
|
+
user: currentUserConfig,
|
|
204
|
+
env: currentEnv,
|
|
205
|
+
...(grayTag && { gray: grayTag }),
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
await this.saveConfig(updatedConfig);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`Failed to update gray tag config: ${error instanceof Error ? error.message : String(error)
|
|
216
|
+
}`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
101
221
|
/**
|
|
102
222
|
* 更新用户配置
|
|
103
223
|
*/
|
|
104
224
|
async updateUserConfig(userConfig: UserConfig): Promise<void> {
|
|
105
225
|
try {
|
|
106
226
|
const currentConfig = this.loadConfig();
|
|
227
|
+
const currentEnv = currentConfig.plugins?.entries?.[PLUGIN_YOYO_ID]?.config?.env;
|
|
228
|
+
const currentGrayTag = currentConfig.plugins?.entries?.[PLUGIN_YOYO_ID]?.config?.gray;
|
|
107
229
|
|
|
108
230
|
const updatedConfig: OpenClawConfig = {
|
|
109
231
|
...currentConfig,
|
|
@@ -116,6 +238,8 @@ export class ConfigManager {
|
|
|
116
238
|
enabled: true,
|
|
117
239
|
config: {
|
|
118
240
|
user: userConfig,
|
|
241
|
+
env: currentEnv,
|
|
242
|
+
...(currentGrayTag && { gray: currentGrayTag }),
|
|
119
243
|
},
|
|
120
244
|
},
|
|
121
245
|
},
|
|
@@ -124,7 +248,10 @@ export class ConfigManager {
|
|
|
124
248
|
|
|
125
249
|
await this.saveConfig(updatedConfig);
|
|
126
250
|
} catch (error) {
|
|
127
|
-
throw new Error(
|
|
251
|
+
throw new Error(
|
|
252
|
+
`Failed to update user config: ${error instanceof Error ? error.message : String(error)
|
|
253
|
+
}`
|
|
254
|
+
);
|
|
128
255
|
}
|
|
129
256
|
}
|
|
130
257
|
|
|
@@ -134,6 +261,8 @@ export class ConfigManager {
|
|
|
134
261
|
async clearUserConfig(): Promise<void> {
|
|
135
262
|
try {
|
|
136
263
|
const currentConfig = this.loadConfig();
|
|
264
|
+
const currentEnv = currentConfig.plugins?.entries?.[PLUGIN_YOYO_ID]?.config?.env;
|
|
265
|
+
const currentGrayTag = currentConfig.plugins?.entries?.[PLUGIN_YOYO_ID]?.config?.gray;
|
|
137
266
|
|
|
138
267
|
const updatedConfig: OpenClawConfig = {
|
|
139
268
|
...currentConfig,
|
|
@@ -145,6 +274,8 @@ export class ConfigManager {
|
|
|
145
274
|
...currentConfig.plugins?.entries?.[PLUGIN_YOYO_ID],
|
|
146
275
|
config: {
|
|
147
276
|
user: undefined,
|
|
277
|
+
env: currentEnv,
|
|
278
|
+
...(currentGrayTag && { gray: currentGrayTag }),
|
|
148
279
|
},
|
|
149
280
|
},
|
|
150
281
|
},
|
|
@@ -153,7 +284,76 @@ export class ConfigManager {
|
|
|
153
284
|
|
|
154
285
|
await this.saveConfig(updatedConfig);
|
|
155
286
|
} catch (error) {
|
|
156
|
-
throw new Error(
|
|
287
|
+
throw new Error(
|
|
288
|
+
`Failed to clear user config: ${error instanceof Error ? error.message : String(error)
|
|
289
|
+
}`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* 初始化插件配置 - 统一处理插件和命令配置
|
|
296
|
+
*/
|
|
297
|
+
async initializePluginConfig(pluginId: string): Promise<void> {
|
|
298
|
+
try {
|
|
299
|
+
const currentConfig = this.loadConfig();
|
|
300
|
+
|
|
301
|
+
// 处理 plugins.allow
|
|
302
|
+
const currentAllow = currentConfig.plugins?.allow || [];
|
|
303
|
+
const updatedAllow = currentAllow.includes(pluginId)
|
|
304
|
+
? currentAllow
|
|
305
|
+
: [...currentAllow, pluginId];
|
|
306
|
+
|
|
307
|
+
// 处理 gateway.nodes.allowCommands
|
|
308
|
+
const currentAllowCommands =
|
|
309
|
+
currentConfig.gateway?.nodes?.allowCommands || [];
|
|
310
|
+
const mergedCommands = Array.from(
|
|
311
|
+
new Set([...currentAllowCommands, ...YOYO_ALLOW_COMMANDS])
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
// 处理 env 默认值(仅在未设置时设置)
|
|
315
|
+
const currentEnv =
|
|
316
|
+
currentConfig.plugins?.entries?.[pluginId]?.config?.env;
|
|
317
|
+
const shouldSetEnv = !isBetaVersion() || !currentEnv;
|
|
318
|
+
const defaultEnv = isBetaVersion() ? "test" : "production";
|
|
319
|
+
|
|
320
|
+
// 构建插件配置
|
|
321
|
+
const pluginConfig =
|
|
322
|
+
currentConfig.plugins?.entries?.[pluginId]?.config || {};
|
|
323
|
+
const updatedPluginConfig = shouldSetEnv
|
|
324
|
+
? { ...pluginConfig, env: defaultEnv }
|
|
325
|
+
: pluginConfig;
|
|
326
|
+
|
|
327
|
+
// 一次性更新所有配置
|
|
328
|
+
const updatedConfig: OpenClawConfig = {
|
|
329
|
+
...currentConfig,
|
|
330
|
+
plugins: {
|
|
331
|
+
...currentConfig.plugins,
|
|
332
|
+
allow: updatedAllow,
|
|
333
|
+
entries: {
|
|
334
|
+
...currentConfig.plugins?.entries,
|
|
335
|
+
[pluginId]: {
|
|
336
|
+
...currentConfig.plugins?.entries?.[pluginId],
|
|
337
|
+
enabled: true,
|
|
338
|
+
config: updatedPluginConfig,
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
gateway: {
|
|
343
|
+
...currentConfig.gateway,
|
|
344
|
+
nodes: {
|
|
345
|
+
...currentConfig.gateway?.nodes,
|
|
346
|
+
allowCommands: mergedCommands,
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
await this.saveConfig(updatedConfig);
|
|
352
|
+
} catch (error) {
|
|
353
|
+
throw new Error(
|
|
354
|
+
`failed to initialize plugin config: ${error instanceof Error ? error.message : String(error)
|
|
355
|
+
}`
|
|
356
|
+
);
|
|
157
357
|
}
|
|
158
358
|
}
|
|
159
359
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { getYoyoEnvInfo } from '../../runtime.js';
|
|
2
|
+
|
|
3
|
+
export interface ApiHostInfo {
|
|
4
|
+
clawCloud: string;
|
|
5
|
+
ics: string;
|
|
6
|
+
grayTag?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 获取当前环境的api host信息(包含灰度标签)
|
|
11
|
+
*/
|
|
12
|
+
export function takeApiHost(): ApiHostInfo {
|
|
13
|
+
const envInfo = getYoyoEnvInfo();
|
|
14
|
+
|
|
15
|
+
let hosts: ApiHostInfo;
|
|
16
|
+
|
|
17
|
+
switch (envInfo.env) {
|
|
18
|
+
case 'dev': {
|
|
19
|
+
hosts = {
|
|
20
|
+
clawCloud: 'omni-dev-drcn.hiboard.hihonorcloud.com',
|
|
21
|
+
ics: 'api-agd-test-drcn.hiboard.hihonorcloud.com',
|
|
22
|
+
};
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
case 'test': {
|
|
26
|
+
hosts = {
|
|
27
|
+
clawCloud: 'omni-pre-drcn.hiboard.hihonorcloud.com',
|
|
28
|
+
ics: 'api-agd-test-drcn.hiboard.hihonorcloud.com',
|
|
29
|
+
};
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
case 'production':
|
|
33
|
+
default: {
|
|
34
|
+
hosts = {
|
|
35
|
+
clawCloud: 'yoyoclaw-drcn.hiboard.hihonorcloud.com',
|
|
36
|
+
ics: 'api-prd-drcn.hiboard.hihonorcloud.com',
|
|
37
|
+
};
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 添加灰度标签
|
|
43
|
+
if (envInfo.grayTag) {
|
|
44
|
+
hosts.grayTag = envInfo.grayTag;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return hosts;
|
|
48
|
+
}
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import type { DeviceInfo } from '../../types.js';
|
|
5
5
|
import { WindowsDeviceInfoProvider } from './providers/windows.js';
|
|
6
6
|
import { PadDevice } from './providers/pad.js';
|
|
7
|
+
import { LinuxDeviceInfoProvider } from './providers/linux.js';
|
|
8
|
+
import { MacOSDeviceInfoProvider } from './providers/macos.js';
|
|
7
9
|
import type { DeviceInfoProvider } from './providers/base.js';
|
|
8
10
|
import { useClawLogger } from '../../utils/logger.js';
|
|
9
11
|
import { getConfigManager } from '../claw-configs/config-manager.js';
|
|
@@ -11,7 +13,7 @@ import { getConfigManager } from '../claw-configs/config-manager.js';
|
|
|
11
13
|
/**
|
|
12
14
|
* 检测当前设备类型
|
|
13
15
|
*/
|
|
14
|
-
function detectDeviceType(): 'windows' | '
|
|
16
|
+
function detectDeviceType(): 'windows' | 'pad' | 'linux' | 'macos' {
|
|
15
17
|
const platform = process.platform;
|
|
16
18
|
|
|
17
19
|
if (platform === 'win32') {
|
|
@@ -19,20 +21,24 @@ function detectDeviceType(): 'windows' | 'android' {
|
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
if (platform === 'linux') {
|
|
22
|
-
// 检查是否为
|
|
24
|
+
// 检查是否为 pad 环境
|
|
23
25
|
try {
|
|
24
26
|
const fs = require('fs');
|
|
25
27
|
if (fs.existsSync('/system/bin/getprop')) {
|
|
26
|
-
return '
|
|
28
|
+
return 'pad';
|
|
27
29
|
}
|
|
28
30
|
} catch {
|
|
29
|
-
//
|
|
31
|
+
// 忽略错误,继续检测为 Linux
|
|
30
32
|
}
|
|
31
|
-
return '
|
|
33
|
+
return 'linux';
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
if (platform === 'darwin') {
|
|
37
|
+
return 'macos';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 默认返回 linux(兼容性)
|
|
41
|
+
return 'linux';
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
/**
|
|
@@ -42,11 +48,16 @@ function getDeviceInfoProvider(): DeviceInfoProvider {
|
|
|
42
48
|
const deviceType = detectDeviceType();
|
|
43
49
|
|
|
44
50
|
switch (deviceType) {
|
|
45
|
-
case '
|
|
51
|
+
case 'pad':
|
|
46
52
|
return new PadDevice();
|
|
47
53
|
case 'windows':
|
|
48
|
-
default:
|
|
49
54
|
return new WindowsDeviceInfoProvider();
|
|
55
|
+
case 'linux':
|
|
56
|
+
return new LinuxDeviceInfoProvider();
|
|
57
|
+
case 'macos':
|
|
58
|
+
return new MacOSDeviceInfoProvider();
|
|
59
|
+
default:
|
|
60
|
+
return new LinuxDeviceInfoProvider();
|
|
50
61
|
}
|
|
51
62
|
}
|
|
52
63
|
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linux 设备信息提供者
|
|
3
|
+
*/
|
|
4
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
5
|
+
import os from 'os';
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
import { createHash } from 'crypto';
|
|
8
|
+
import type { DeviceInfoProvider } from './base.js';
|
|
9
|
+
import type { DeviceType } from '../../../types.js';
|
|
10
|
+
|
|
11
|
+
export class LinuxDeviceInfoProvider implements DeviceInfoProvider {
|
|
12
|
+
/**
|
|
13
|
+
* 执行 shell 命令并获取输出
|
|
14
|
+
*/
|
|
15
|
+
private execCommand(command: string): string {
|
|
16
|
+
try {
|
|
17
|
+
return execSync(command, { encoding: 'utf-8' }).trim();
|
|
18
|
+
} catch {
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 获取 Linux 设备唯一ID
|
|
25
|
+
*/
|
|
26
|
+
private getLinuxDeviceId(): string {
|
|
27
|
+
try {
|
|
28
|
+
// 尝试获取机器ID(systemd-based systems)
|
|
29
|
+
const machineId = this.execCommand('cat /etc/machine-id 2>/dev/null');
|
|
30
|
+
if (machineId) {
|
|
31
|
+
return machineId;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 尝试获取 D-Bus 机器ID
|
|
35
|
+
const dbusMachineId = this.execCommand('cat /var/lib/dbus/machine-id 2>/dev/null');
|
|
36
|
+
if (dbusMachineId) {
|
|
37
|
+
return dbusMachineId;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 尝试获取主板序列号
|
|
41
|
+
const serial = this.execCommand('sudo dmidecode -s system-serial-number 2>/dev/null');
|
|
42
|
+
if (serial && serial !== 'Not Specified') {
|
|
43
|
+
return serial;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 获取 CPU 信息作为备选
|
|
47
|
+
const cpuInfo = this.execCommand('cat /proc/cpuinfo | grep "serial" | head -1 | cut -d":" -f2 | tr -d " "');
|
|
48
|
+
if (cpuInfo) {
|
|
49
|
+
return cpuInfo;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 最后备用方案:使用主机名和架构生成哈希
|
|
53
|
+
const hostname = os.hostname();
|
|
54
|
+
const arch = os.arch();
|
|
55
|
+
const raw = `${hostname}_${arch}_${os.platform()}`;
|
|
56
|
+
return createHash('sha256').update(raw, 'utf-8').digest('hex');
|
|
57
|
+
} catch {
|
|
58
|
+
return uuidv4();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 获取 Linux 设备型号
|
|
64
|
+
*/
|
|
65
|
+
private getLinuxDeviceModel(): string {
|
|
66
|
+
try {
|
|
67
|
+
// 尝试获取产品名称
|
|
68
|
+
const productName = this.execCommand('sudo dmidecode -s system-product-name 2>/dev/null');
|
|
69
|
+
if (productName && productName !== 'Not Specified') {
|
|
70
|
+
return productName;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 尝试获取主板信息
|
|
74
|
+
const boardName = this.execCommand('sudo dmidecode -s baseboard-product-name 2>/dev/null');
|
|
75
|
+
if (boardName && boardName !== 'Not Specified') {
|
|
76
|
+
return boardName;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 尝试从 /sys/class/dmi/id 获取信息
|
|
80
|
+
const sysProductName = this.execCommand('cat /sys/class/dmi/id/product_name 2>/dev/null');
|
|
81
|
+
if (sysProductName) {
|
|
82
|
+
return sysProductName;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 默认返回 Linux PC
|
|
86
|
+
return 'Linux PC';
|
|
87
|
+
} catch {
|
|
88
|
+
return 'Linux PC';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 获取 Linux 设备名称
|
|
94
|
+
*/
|
|
95
|
+
private getLinuxDeviceName(): string {
|
|
96
|
+
const hostname = os.hostname();
|
|
97
|
+
const model = this.getLinuxDeviceModel();
|
|
98
|
+
return `${hostname} (${model})`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 获取设备唯一ID
|
|
103
|
+
*/
|
|
104
|
+
getDeviceId(): string {
|
|
105
|
+
return this.getLinuxDeviceId();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 获取设备名称
|
|
110
|
+
*/
|
|
111
|
+
getDeviceName(): string {
|
|
112
|
+
return this.getLinuxDeviceName();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 获取设备型号
|
|
117
|
+
*/
|
|
118
|
+
getDeviceModel(): string {
|
|
119
|
+
return this.getLinuxDeviceModel();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 获取设备类型
|
|
124
|
+
*/
|
|
125
|
+
getDeviceType(): DeviceType {
|
|
126
|
+
return 'pc';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* macOS 设备信息提供者
|
|
3
|
+
*/
|
|
4
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
5
|
+
import os from 'os';
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
import { createHash } from 'crypto';
|
|
8
|
+
import type { DeviceInfoProvider } from './base.js';
|
|
9
|
+
import type { DeviceType } from '../../../types.js';
|
|
10
|
+
|
|
11
|
+
export class MacOSDeviceInfoProvider implements DeviceInfoProvider {
|
|
12
|
+
/**
|
|
13
|
+
* 执行 shell 命令并获取输出
|
|
14
|
+
*/
|
|
15
|
+
private execCommand(command: string): string {
|
|
16
|
+
try {
|
|
17
|
+
return execSync(command, {
|
|
18
|
+
encoding: 'utf-8',
|
|
19
|
+
shell: '/bin/zsh', // 明确指定 shell
|
|
20
|
+
timeout: 10000, // 10秒超时
|
|
21
|
+
stdio: ['ignore', 'pipe', 'pipe'] // 捕获错误输出
|
|
22
|
+
}).trim();
|
|
23
|
+
} catch (error) {
|
|
24
|
+
// 记录错误信息以便调试
|
|
25
|
+
console.error(`Command failed: ${command}`, error);
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 获取 macOS 设备唯一ID
|
|
32
|
+
*/
|
|
33
|
+
private getMacOSDeviceId(): string {
|
|
34
|
+
try {
|
|
35
|
+
// 获取硬件UUID(最可靠的macOS设备标识)
|
|
36
|
+
const hardwareUuid = this.execCommand('system_profiler SPHardwareDataType | grep "Hardware UUID" | cut -d ":" -f2 | tr -d " "');
|
|
37
|
+
if (hardwareUuid) {
|
|
38
|
+
return hardwareUuid;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 获取序列号
|
|
42
|
+
const serialNumber = this.execCommand('system_profiler SPHardwareDataType | grep "Serial Number" | cut -d ":" -f2 | tr -d " "');
|
|
43
|
+
if (serialNumber) {
|
|
44
|
+
return serialNumber;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 获取主板序列号
|
|
48
|
+
const boardSerial = this.execCommand('system_profiler SPHardwareDataType | grep "Board ID" | cut -d ":" -f2 | tr -d " "');
|
|
49
|
+
if (boardSerial) {
|
|
50
|
+
return boardSerial;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 最后备用方案:使用主机名和架构生成哈希
|
|
54
|
+
const hostname = os.hostname();
|
|
55
|
+
const arch = os.arch();
|
|
56
|
+
const raw = `${hostname}_${arch}_macOS`;
|
|
57
|
+
return createHash('sha256').update(raw, 'utf-8').digest('hex');
|
|
58
|
+
} catch {
|
|
59
|
+
return uuidv4();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 获取 macOS 设备型号
|
|
65
|
+
*/
|
|
66
|
+
private getMacOSDeviceModel(): string {
|
|
67
|
+
try {
|
|
68
|
+
// 获取硬件型号
|
|
69
|
+
const modelName = this.execCommand('system_profiler SPHardwareDataType | grep "Model Name" | cut -d ":" -f2 | sed "s/^ //"');
|
|
70
|
+
if (modelName) {
|
|
71
|
+
return modelName;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 获取机型标识符
|
|
75
|
+
const modelIdentifier = this.execCommand('system_profiler SPHardwareDataType | grep "Model Identifier" | cut -d ":" -f2 | tr -d " "');
|
|
76
|
+
if (modelIdentifier) {
|
|
77
|
+
return modelIdentifier;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 默认返回 Mac
|
|
81
|
+
return 'Mac';
|
|
82
|
+
} catch {
|
|
83
|
+
return 'Mac';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 获取 macOS 设备名称
|
|
89
|
+
*/
|
|
90
|
+
private getMacOSDeviceName(): string {
|
|
91
|
+
const hostname = os.hostname();
|
|
92
|
+
const model = this.getMacOSDeviceModel();
|
|
93
|
+
return `${hostname} (${model})`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 获取设备唯一ID
|
|
98
|
+
*/
|
|
99
|
+
getDeviceId(): string {
|
|
100
|
+
return this.getMacOSDeviceId();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 获取设备名称
|
|
105
|
+
*/
|
|
106
|
+
getDeviceName(): string {
|
|
107
|
+
return this.getMacOSDeviceName();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 获取设备型号
|
|
112
|
+
*/
|
|
113
|
+
getDeviceModel(): string {
|
|
114
|
+
return this.getMacOSDeviceModel();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 获取设备类型
|
|
119
|
+
*/
|
|
120
|
+
getDeviceType(): DeviceType {
|
|
121
|
+
return 'pc';
|
|
122
|
+
}
|
|
123
|
+
}
|