@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.
Files changed (79) hide show
  1. package/index.ts +2 -2
  2. package/openclaw.plugin.json +7 -0
  3. package/package.json +20 -20
  4. package/skills/search/SKILL.md +182 -0
  5. package/skills/search/scripts/search.sh +69 -0
  6. package/skills/yoyo-control/SKILL.md +105 -120
  7. package/skills/yoyo-control/references/alarm-create.md +473 -0
  8. package/skills/yoyo-control/references/app-close.md +183 -0
  9. package/skills/yoyo-control/references/app-open.md +178 -0
  10. package/skills/yoyo-control/references/call-phone.md +250 -0
  11. package/skills/yoyo-control/references/capture-screenshot.md +205 -54
  12. package/skills/yoyo-control/references/contact-search.md +235 -0
  13. package/skills/yoyo-control/references/hotspot.md +208 -0
  14. package/skills/yoyo-control/references/local-search.md +224 -15
  15. package/skills/yoyo-control/references/message-send.md +246 -0
  16. package/skills/yoyo-control/references/mobile-data.md +248 -0
  17. package/skills/yoyo-control/references/no-disturb.md +239 -0
  18. package/skills/yoyo-control/references/quiet-mode.md +228 -0
  19. package/skills/yoyo-control/references/ringing-mode.md +223 -0
  20. package/skills/yoyo-control/references/screen-record.md +220 -0
  21. package/skills/yoyo-control/references/vibration-mode.md +235 -0
  22. package/skills/yoyo-control/references/volume-operate.md +274 -0
  23. package/skills/yoyo-control/scripts/invoke.js +33 -111
  24. package/src/agent/copy-templates.ts +56 -0
  25. package/src/agent/index.ts +3 -0
  26. package/src/agent/templates/AGENTS.md +223 -0
  27. package/src/apis/claw-cloud.ts +70 -23
  28. package/src/apis/honor-auth.ts +20 -10
  29. package/src/apis/types.ts +24 -1
  30. package/src/cloud-channel/channel.ts +245 -58
  31. package/src/cloud-channel/client.ts +87 -12
  32. package/src/cloud-channel/types.ts +30 -0
  33. package/src/commands/env/impl.ts +58 -0
  34. package/src/commands/env/index.ts +1 -0
  35. package/src/commands/index.ts +11 -1
  36. package/src/commands/login/impl.ts +17 -8
  37. package/src/commands/logout/impl.ts +23 -0
  38. package/src/commands/logout/index.ts +1 -53
  39. package/src/commands/status/index.ts +172 -42
  40. package/src/gateway-client/client.deprecated.ts +1 -1
  41. package/src/gateway-client/client.ts +15 -20
  42. package/src/gateway-client/types.ts +2 -2
  43. package/src/honor-auth/browser.ts +12 -15
  44. package/src/honor-auth/callback-server.ts +3 -6
  45. package/src/honor-auth/cloud.ts +65 -12
  46. package/src/honor-auth/config.ts +25 -17
  47. package/src/honor-auth/index.ts +1 -0
  48. package/src/honor-auth/token-manager.ts +24 -14
  49. package/src/modules/claw-configs/config-manager.ts +211 -11
  50. package/src/modules/claw-configs/hosts.ts +48 -0
  51. package/src/modules/claw-configs/index.ts +1 -0
  52. package/src/modules/claw-configs/types.ts +4 -0
  53. package/src/modules/device/device-info.ts +20 -9
  54. package/src/modules/device/providers/linux.ts +128 -0
  55. package/src/modules/device/providers/macos.ts +123 -0
  56. package/src/modules/device/providers/pad.ts +0 -16
  57. package/src/modules/device/registry.ts +12 -3
  58. package/src/modules/login/impl.ts +38 -16
  59. package/src/runtime.ts +44 -0
  60. package/src/schemas.ts +4 -1
  61. package/src/services/connection/impl.ts +89 -9
  62. package/src/services/connection/status-tracker/events.ts +127 -0
  63. package/src/services/connection/status-tracker/index.ts +31 -0
  64. package/src/services/connection/status-tracker/storage.ts +133 -0
  65. package/src/services/connection/status-tracker/tracker.ts +370 -0
  66. package/src/services/connection/status-tracker/types.ts +131 -0
  67. package/src/types.ts +0 -4
  68. package/src/utils/fs-safe.ts +544 -0
  69. package/src/utils/version.ts +29 -0
  70. package/src/utils/ws.ts +21 -0
  71. package/skills/yoyo-control/references/open-app.md +0 -54
  72. package/skills/yoyo-control/references/phone-call.md +0 -217
  73. package/skills/yoyo-control/references/schedule.md +0 -107
  74. package/skills/yoyo-control/references/screen-recorder.md +0 -67
  75. package/skills/yoyo-control/references/search-contact.md +0 -37
  76. package/skills/yoyo-control/references/send-message.md +0 -155
  77. package/skills/yoyo-control/references/volume.md +0 -536
  78. package/skills/yoyo-control/scripts/README.md +0 -103
  79. 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 'openclaw/plugin-sdk';
6
- import { getYoyoRuntime } from '../../runtime.js';
7
- import type { GatewayAuthConfig, UserConfig } from './types.js';
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 = 'yoyo';
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(`加载配置失败: ${error instanceof Error ? error.message : String(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(`保存配置失败: ${error instanceof Error ? error.message : String(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(`[claw-configs] Failed to read gateway auth config: ${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(`[claw-configs] Failed to read gateway port config: ${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?.user as UserConfig | undefined;
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(`更新用户配置失败: ${error instanceof Error ? error.message : String(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(`清除用户配置失败: ${error instanceof Error ? error.message : String(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
+ }
@@ -5,3 +5,4 @@
5
5
 
6
6
  export * from './types.js';
7
7
  export * from './config-manager.js';
8
+ export * from './hosts.js';
@@ -26,5 +26,9 @@ export interface YoyoClawPluginConfig {
26
26
  enabled?: boolean;
27
27
  config?: {
28
28
  user?: UserConfig;
29
+ /** 运行环境:test 或 production */
30
+ env?: 'dev' | 'test' | 'production';
31
+ /** 灰度标签 */
32
+ gray?: string;
29
33
  };
30
34
  }
@@ -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' | 'android' {
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
- // 检查是否为 Android 环境
24
+ // 检查是否为 pad 环境
23
25
  try {
24
26
  const fs = require('fs');
25
27
  if (fs.existsSync('/system/bin/getprop')) {
26
- return 'android';
28
+ return 'pad';
27
29
  }
28
30
  } catch {
29
- // 忽略错误,默认为 android
31
+ // 忽略错误,继续检测为 Linux
30
32
  }
31
- return 'android';
33
+ return 'linux';
32
34
  }
33
35
 
34
- // 默认返回 windows(兼容性)
35
- return 'windows';
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 'android':
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
+ }