@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
|
@@ -8,6 +8,9 @@ import {
|
|
|
8
8
|
type DeviceRole,
|
|
9
9
|
} from "../types.js";
|
|
10
10
|
|
|
11
|
+
// 导入状态事件类型(可选导入,避免循环依赖)
|
|
12
|
+
export type { StatusEvent } from "../services/connection/status-tracker/events.js";
|
|
13
|
+
|
|
11
14
|
export interface YoyoClawMessage {
|
|
12
15
|
sourceRole?: DeviceRole;
|
|
13
16
|
sourceDeviceId: string;
|
|
@@ -17,6 +20,12 @@ export interface YoyoClawMessage {
|
|
|
17
20
|
port: number | string;
|
|
18
21
|
data?: string;
|
|
19
22
|
msgType: "userMessage" | "devicePairMessage";
|
|
23
|
+
/**
|
|
24
|
+
* 会话轮次等追加信息,只有配对消息有
|
|
25
|
+
*/
|
|
26
|
+
sessionInfo?: {
|
|
27
|
+
nodeConnectTimestamp: number;
|
|
28
|
+
};
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
/**
|
|
@@ -43,6 +52,12 @@ export interface YoyoClawSocketWrapper<T> {
|
|
|
43
52
|
extData?: {
|
|
44
53
|
offlineSocketId?: string;
|
|
45
54
|
};
|
|
55
|
+
/**
|
|
56
|
+
* 会话轮次等追加信息,只有配对消息有
|
|
57
|
+
*/
|
|
58
|
+
sessionInfo?: {
|
|
59
|
+
nodeConnectTimestamp: number;
|
|
60
|
+
}
|
|
46
61
|
}
|
|
47
62
|
|
|
48
63
|
/**
|
|
@@ -57,6 +72,8 @@ export interface ClawChannelConfig {
|
|
|
57
72
|
onClose?: () => void;
|
|
58
73
|
// 当前设备未注册回调
|
|
59
74
|
onDeviceNotRegistered?: () => void;
|
|
75
|
+
// 状态事件回调(用于状态跟踪)
|
|
76
|
+
onStatusEvent?: (event: any) => void;
|
|
60
77
|
}
|
|
61
78
|
|
|
62
79
|
export interface ClawSocketSourceInfo {
|
|
@@ -78,4 +95,17 @@ export interface ClawSocketClientOptions {
|
|
|
78
95
|
onRemoteDeviceOffline?: (sourceDeviceId: string) => void;
|
|
79
96
|
// 当前设备未注册回调
|
|
80
97
|
onDeviceNotRegistered?: () => void;
|
|
98
|
+
// 额外的请求头
|
|
99
|
+
extraHeaders?: Record<string, string>;
|
|
100
|
+
// 状态事件回调(用于状态跟踪)
|
|
101
|
+
onStatusEvent?: (event: any) => void;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 设备会话信息
|
|
106
|
+
*/
|
|
107
|
+
export interface DeviceSessionInfo {
|
|
108
|
+
sessionId: string;
|
|
109
|
+
timestamp: number;
|
|
110
|
+
sourceInfo: ClawSocketSourceInfo;
|
|
81
111
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { getConfigManager } from "../../modules/claw-configs/config-manager.js";
|
|
2
|
+
import { useClawLogger } from "../../utils/logger.js";
|
|
3
|
+
import type { Command } from "commander";
|
|
4
|
+
|
|
5
|
+
export function registerEnvCommand(_: unknown, command: Command) {
|
|
6
|
+
const nextCommand = command
|
|
7
|
+
.command("env")
|
|
8
|
+
.description("Manage runtime environment (dev/test/production)")
|
|
9
|
+
.option("--set <env>", "Set environment: dev, test or production")
|
|
10
|
+
.option("--gray <tag>", "Set gray header")
|
|
11
|
+
.action(async (options) => {
|
|
12
|
+
const { set: setEnv, gray: grayTag } = options;
|
|
13
|
+
const logger = useClawLogger();
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const configManager = getConfigManager();
|
|
17
|
+
|
|
18
|
+
if (setEnv) {
|
|
19
|
+
// 设置环境
|
|
20
|
+
if (setEnv !== "test" && setEnv !== "dev" && setEnv !== "production") {
|
|
21
|
+
logger.error("❌ Invalid environment. Use 'test' or 'production'.");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
await configManager.updateEnv(setEnv);
|
|
26
|
+
logger.info(`✅ Environment set to: ${setEnv}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (grayTag) {
|
|
30
|
+
// 设置灰度标签
|
|
31
|
+
await configManager.updateGrayTag(grayTag);
|
|
32
|
+
logger.info(`✅ Gray tag set to: ${grayTag}`);
|
|
33
|
+
} else if (grayTag === '') {
|
|
34
|
+
// 清除灰度标签
|
|
35
|
+
await configManager.updateGrayTag(undefined);
|
|
36
|
+
logger.info(`✅ Gray tag cleared`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!setEnv && grayTag === undefined) {
|
|
40
|
+
// 获取当前环境信息
|
|
41
|
+
const currentEnv = configManager.getEnv();
|
|
42
|
+
const currentGrayTag = configManager.getGrayTag();
|
|
43
|
+
|
|
44
|
+
logger.info(`📋 Current environment: ${currentEnv}`);
|
|
45
|
+
if (currentGrayTag) {
|
|
46
|
+
logger.info(`📋 Current gray tag: ${currentGrayTag}`);
|
|
47
|
+
} else {
|
|
48
|
+
logger.info(`📋 Gray tag: not set`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
53
|
+
logger.error(`❌ Failed to manage environment: ${errorMessage}`);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return nextCommand;
|
|
58
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './impl.js';
|
package/src/commands/index.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { type OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
|
2
2
|
import { registerLoginCommand } from "./login/index.js";
|
|
3
3
|
import { registerStatusCommand } from "./status/index.js";
|
|
4
4
|
import { registerLogoutCommand } from "./logout/index.js";
|
|
5
|
+
import { registerEnvCommand } from "./env/index.js";
|
|
6
|
+
import { isBetaVersion } from "../utils/version.js";
|
|
5
7
|
|
|
6
8
|
export function registerCommands(api: OpenClawPluginApi) {
|
|
7
9
|
api.registerCli(
|
|
@@ -10,9 +12,17 @@ export function registerCommands(api: OpenClawPluginApi) {
|
|
|
10
12
|
.command("honor")
|
|
11
13
|
.description("Commands for honor yoyoclaw");
|
|
12
14
|
|
|
15
|
+
// @ts-ignore
|
|
13
16
|
registerLoginCommand(api, rootCommand);
|
|
17
|
+
// @ts-ignore
|
|
14
18
|
registerStatusCommand(api, rootCommand);
|
|
15
|
-
//
|
|
19
|
+
// @ts-ignore
|
|
20
|
+
registerLogoutCommand(api, rootCommand);
|
|
21
|
+
|
|
22
|
+
// 只在 beta 版本时注册 env 命令
|
|
23
|
+
if (isBetaVersion()) {
|
|
24
|
+
registerEnvCommand(api, rootCommand);
|
|
25
|
+
}
|
|
16
26
|
},
|
|
17
27
|
{ commands: ["honor"] }
|
|
18
28
|
);
|
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
import { type OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
2
|
import { performLogin } from "../../modules/login/impl.js";
|
|
3
3
|
import { type Command } from "commander";
|
|
4
|
+
import { isBetaVersion } from "../../utils/version.js";
|
|
4
5
|
|
|
5
6
|
export function registerLoginCommand(api: OpenClawPluginApi, command: Command) {
|
|
6
|
-
|
|
7
|
+
let nextCommand = command
|
|
7
8
|
.command("login")
|
|
8
|
-
.description("
|
|
9
|
-
|
|
9
|
+
.description("login to yoyoclaw and register devices");
|
|
10
|
+
|
|
11
|
+
if (isBetaVersion()) {
|
|
12
|
+
nextCommand = nextCommand.option(
|
|
13
|
+
"--skip-auth",
|
|
14
|
+
"debug mode, no auth required"
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
nextCommand = nextCommand
|
|
19
|
+
.option("-u, --userId <userId>", "user ID for direct login")
|
|
20
|
+
.option("--token <token>", "token for direct login")
|
|
10
21
|
.action(async (options) => {
|
|
11
|
-
const { skipAuth } = options;
|
|
22
|
+
const { skipAuth, userId, token } = options;
|
|
12
23
|
|
|
13
|
-
api.logger.
|
|
14
|
-
`honor login CLI command called with skip auth: ${!!skipAuth}`
|
|
15
|
-
);
|
|
24
|
+
api.logger.debug?.('honor login CLI command called');
|
|
16
25
|
|
|
17
|
-
await performLogin({ noAuth: skipAuth });
|
|
26
|
+
await performLogin({ noAuth: skipAuth, userId, token });
|
|
18
27
|
});
|
|
19
28
|
|
|
20
29
|
return nextCommand;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
|
+
import { type Command } from "commander";
|
|
3
|
+
import { performLogout } from "../../honor-auth/cloud.js";
|
|
4
|
+
|
|
5
|
+
export function registerLogoutCommand(api: OpenClawPluginApi, command: Command) {
|
|
6
|
+
const nextCommand = command
|
|
7
|
+
.command("logout")
|
|
8
|
+
.description("Logout and clear user configuration")
|
|
9
|
+
.action(async () => {
|
|
10
|
+
api.logger.info("logout CLI command called");
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
await performLogout();
|
|
14
|
+
console.log("✅ Logout successful, gateway will automatically restart to handle new configuration");
|
|
15
|
+
} catch (error) {
|
|
16
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
17
|
+
console.error("❌ Logout failed:", errorMessage);
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return nextCommand;
|
|
23
|
+
}
|
|
@@ -1,53 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { getConnectionManager } from "../../services/connection-manager.js";
|
|
3
|
-
import { type Command } from "commander";
|
|
4
|
-
|
|
5
|
-
export function registerLogoutCommand(api: OpenClawPluginApi, command: Command) {
|
|
6
|
-
const nextCommand = command
|
|
7
|
-
.command("logout")
|
|
8
|
-
.description("Logout and disconnect from YOYOClaw")
|
|
9
|
-
.option("--force", "Force logout without confirmation")
|
|
10
|
-
.action(async (options) => {
|
|
11
|
-
api.logger.info("honor logout CLI command called");
|
|
12
|
-
|
|
13
|
-
const { force } = options;
|
|
14
|
-
const connectionManager = getConnectionManager();
|
|
15
|
-
const isConnected = connectionManager.isConnected();
|
|
16
|
-
const connectedDevices = connectionManager.getConnectedDevices();
|
|
17
|
-
|
|
18
|
-
if (!isConnected) {
|
|
19
|
-
console.log("⚠️ 当前未连接到 YOYOClaw");
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// 如果有活跃的设备连接且不是强制模式,提示确认
|
|
24
|
-
if (connectedDevices.length > 0 && !force) {
|
|
25
|
-
console.log(`⚠️ 当前有 ${connectedDevices.length} 个活跃的设备连接:`);
|
|
26
|
-
for (const device of connectedDevices) {
|
|
27
|
-
console.log(` - ${device}`);
|
|
28
|
-
}
|
|
29
|
-
console.log("\n⚠️ 断开连接将停止所有设备间的通信");
|
|
30
|
-
console.log('💡 使用 --force 选项可以跳过此确认');
|
|
31
|
-
|
|
32
|
-
// 注意:在命令行工具中,我们无法直接等待用户输入
|
|
33
|
-
// 这里我们给出提示,然后继续执行
|
|
34
|
-
console.log("\n🔌 正在断开连接...");
|
|
35
|
-
} else {
|
|
36
|
-
console.log("🔌 正在断开连接...");
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
await connectionManager.cleanup();
|
|
41
|
-
console.log("✅ WebSocket 连接已关闭");
|
|
42
|
-
console.log("✅ 所有设备连接已清理");
|
|
43
|
-
console.log("✅ 资源已释放");
|
|
44
|
-
console.log("👋 已登出");
|
|
45
|
-
} catch (error) {
|
|
46
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
47
|
-
console.error("❌ 登出失败:", errorMessage);
|
|
48
|
-
throw error;
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
return nextCommand;
|
|
53
|
-
}
|
|
1
|
+
export * from "./impl.js";
|
|
@@ -1,64 +1,194 @@
|
|
|
1
|
-
import { type OpenClawPluginApi } from
|
|
2
|
-
import { type Command } from
|
|
3
|
-
import {
|
|
1
|
+
import { type OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
|
+
import { type Command } from "commander";
|
|
3
|
+
import { StatusStorage } from "../../services/connection/status-tracker/index.js";
|
|
4
|
+
import type { ConnectionStatusData } from "../../services/connection/status-tracker/index.js";
|
|
5
|
+
import { loadToken } from "../../honor-auth/token-manager.js";
|
|
4
6
|
|
|
5
|
-
export function registerStatusCommand(
|
|
7
|
+
export function registerStatusCommand(
|
|
8
|
+
api: OpenClawPluginApi,
|
|
9
|
+
command: Command
|
|
10
|
+
) {
|
|
6
11
|
const nextCommand = command
|
|
7
|
-
.command(
|
|
8
|
-
.description(
|
|
12
|
+
.command("status")
|
|
13
|
+
.description("Show YOYOClaw connection status")
|
|
9
14
|
.action(async () => {
|
|
10
|
-
api.logger.
|
|
11
|
-
|
|
12
|
-
//
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const stateText = getStateText(status);
|
|
18
|
-
|
|
19
|
-
api.logger.info(`连接状态: ${stateEmoji} ${stateText}`);
|
|
20
|
-
|
|
21
|
-
if (status === 'connected') {
|
|
22
|
-
// 已连接状态
|
|
23
|
-
api.logger.info('\n✅ YOYOClaw 连接正常');
|
|
24
|
-
} else if (status === 'connecting') {
|
|
25
|
-
// 连接中状态
|
|
26
|
-
api.logger.info('\n⏳ 正在建立连接...');
|
|
27
|
-
} else {
|
|
28
|
-
// 未连接状态
|
|
29
|
-
api.logger.info("\n💡 请先执行 'yoyoclaw login' 建立连接");
|
|
15
|
+
api.logger.debug?.("YOYOClaw status CLI command called");
|
|
16
|
+
|
|
17
|
+
// Check if user is logged in
|
|
18
|
+
const userInfo = await loadToken();
|
|
19
|
+
if (!userInfo) {
|
|
20
|
+
console.log("❌ You need to login first. Please run: openclaw honor login");
|
|
21
|
+
return;
|
|
30
22
|
}
|
|
23
|
+
|
|
24
|
+
// Try to load detailed status from status file
|
|
25
|
+
const statusStorage = new StatusStorage();
|
|
26
|
+
const statusData = await statusStorage.load();
|
|
27
|
+
|
|
28
|
+
displayDetailedStatus(statusData);
|
|
31
29
|
});
|
|
32
30
|
|
|
33
31
|
return nextCommand;
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
/**
|
|
37
|
-
*
|
|
35
|
+
* Display detailed status information
|
|
38
36
|
*/
|
|
39
|
-
function
|
|
37
|
+
function displayDetailedStatus(statusData: ConnectionStatusData | null): void {
|
|
38
|
+
if (!statusData) {
|
|
39
|
+
console.log("\n❌ No status data available");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Cloud Socket status
|
|
44
|
+
console.log("\n📡 Cloud Socket:");
|
|
45
|
+
const cloudStatus = statusData.cloudSocket;
|
|
46
|
+
const cloudEmoji = cloudStatus.connected ? "✅" : "❌";
|
|
47
|
+
console.log(
|
|
48
|
+
` Status: ${cloudEmoji} ${cloudStatus.connected ? "Connected" : "Disconnected"}`
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
if (cloudStatus.connectedAt) {
|
|
52
|
+
console.log(` Connected at: ${formatDateTime(cloudStatus.connectedAt)}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (cloudStatus.lastDisconnectedAt) {
|
|
56
|
+
console.log(
|
|
57
|
+
` Last disconnected: ${formatDateTime(cloudStatus.lastDisconnectedAt)}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log(` Retry count: ${cloudStatus.retryCount}`);
|
|
62
|
+
console.log(` Last error: ${cloudStatus.lastError || "None"}`);
|
|
63
|
+
|
|
64
|
+
// Gateway connection statistics
|
|
65
|
+
console.log("\n🌐 Gateway Connections:");
|
|
66
|
+
console.log(` Total connections: ${statusData.gateway.totalConnections}`);
|
|
67
|
+
console.log(` Active connections: ${statusData.gateway.activeConnections}`);
|
|
68
|
+
|
|
69
|
+
if (statusData.gateway.connections.length > 0) {
|
|
70
|
+
console.log(" ┌────────────────────────────┬────────────────────────────┬────────────┐");
|
|
71
|
+
console.log(" │ Session ID │ Hardware Device ID │ Connected │");
|
|
72
|
+
console.log(" ├────────────────────────────┼────────────────────────────┼────────────┤");
|
|
73
|
+
|
|
74
|
+
for (const conn of statusData.gateway.connections) {
|
|
75
|
+
const sessionId = truncateString(conn.sessionId, 26);
|
|
76
|
+
const deviceId = truncateString(conn.hardwareDeviceId, 26);
|
|
77
|
+
const connTime = formatTime(conn.connectedAt);
|
|
78
|
+
console.log(
|
|
79
|
+
` │ ${sessionId.padEnd(28)}│ ${deviceId.padEnd(28)}│ ${connTime.padEnd(12)}│`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log(" └────────────────────────────┴────────────────────────────┴────────────┘");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Physical device statistics
|
|
87
|
+
console.log("\n📱 Physical Devices:");
|
|
88
|
+
console.log(` Unique devices: ${statusData.devices.uniqueHardwareDevices}`);
|
|
89
|
+
|
|
90
|
+
if (statusData.devices.devices.length > 0) {
|
|
91
|
+
console.log(" ┌────────────────────────────┬─────────┬────────────┐");
|
|
92
|
+
console.log(" │ Hardware Device ID │ Sessions │ Last Active│");
|
|
93
|
+
console.log(" ├────────────────────────────┼─────────┼────────────┤");
|
|
94
|
+
|
|
95
|
+
for (const device of statusData.devices.devices) {
|
|
96
|
+
const deviceId = truncateString(device.hardwareDeviceId, 26);
|
|
97
|
+
const sessions = String(device.sessions).padEnd(9);
|
|
98
|
+
const lastActive = formatTime(device.lastActiveAt);
|
|
99
|
+
console.log(
|
|
100
|
+
` │ ${deviceId.padEnd(28)}│ ${sessions}│ ${lastActive.padEnd(12)}│`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log(" └────────────────────────────┴─────────┴────────────┘");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Connection history
|
|
108
|
+
console.log("\n📈 Connection History:");
|
|
109
|
+
console.log(` Total connections: ${statusData.history.connectionCount}`);
|
|
110
|
+
console.log(` Total disconnections: ${statusData.history.disconnectionCount}`);
|
|
111
|
+
|
|
112
|
+
if (statusData.history.lastConnectionAt) {
|
|
113
|
+
console.log(
|
|
114
|
+
` Last connection: ${formatDateTime(statusData.history.lastConnectionAt)}`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (statusData.history.lastDisconnectionAt) {
|
|
119
|
+
console.log(
|
|
120
|
+
` Last disconnection: ${formatDateTime(statusData.history.lastDisconnectionAt)}`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Update time
|
|
125
|
+
console.log("\n🕐 Status Updated At:");
|
|
126
|
+
console.log(` ${formatDateTime(statusData.updatedAt)}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get emoji for connection status
|
|
131
|
+
*/
|
|
132
|
+
function getStateEmoji(status: "idle" | "connecting" | "connected"): string {
|
|
40
133
|
switch (status) {
|
|
41
|
-
case
|
|
42
|
-
return
|
|
43
|
-
case
|
|
44
|
-
return
|
|
45
|
-
case
|
|
134
|
+
case "connected":
|
|
135
|
+
return "✅";
|
|
136
|
+
case "connecting":
|
|
137
|
+
return "⏳";
|
|
138
|
+
case "idle":
|
|
46
139
|
default:
|
|
47
|
-
return
|
|
140
|
+
return "⚪";
|
|
48
141
|
}
|
|
49
142
|
}
|
|
50
143
|
|
|
51
144
|
/**
|
|
52
|
-
*
|
|
145
|
+
* Get text for connection status
|
|
53
146
|
*/
|
|
54
|
-
function getStateText(status:
|
|
147
|
+
function getStateText(status: "idle" | "connecting" | "connected"): string {
|
|
55
148
|
switch (status) {
|
|
56
|
-
case
|
|
57
|
-
return
|
|
58
|
-
case
|
|
59
|
-
return
|
|
60
|
-
case
|
|
149
|
+
case "connected":
|
|
150
|
+
return "Connected";
|
|
151
|
+
case "connecting":
|
|
152
|
+
return "Connecting";
|
|
153
|
+
case "idle":
|
|
61
154
|
default:
|
|
62
|
-
return
|
|
155
|
+
return "Disconnected";
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Format date time (YYYY-MM-DD HH:mm:ss)
|
|
161
|
+
*/
|
|
162
|
+
function formatDateTime(isoString: string): string {
|
|
163
|
+
const date = new Date(isoString);
|
|
164
|
+
const year = date.getFullYear();
|
|
165
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
166
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
167
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
168
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
169
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
170
|
+
|
|
171
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Format time (HH:mm:ss)
|
|
176
|
+
*/
|
|
177
|
+
function formatTime(isoString: string): string {
|
|
178
|
+
const date = new Date(isoString);
|
|
179
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
180
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
181
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
182
|
+
|
|
183
|
+
return `${hours}:${minutes}:${seconds}`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Truncate string with ellipsis
|
|
188
|
+
*/
|
|
189
|
+
function truncateString(str: string, maxLength: number): string {
|
|
190
|
+
if (str.length <= maxLength) {
|
|
191
|
+
return str;
|
|
63
192
|
}
|
|
193
|
+
return str.substring(0, maxLength - 3) + "...";
|
|
64
194
|
}
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from "./device/identity.js";
|
|
17
17
|
import { buildDeviceAuthPayloadV3 } from "./device/auth.js";
|
|
18
18
|
import type { DeviceIdentity } from "./device/types.js";
|
|
19
|
-
import { rawDataToString } from "
|
|
19
|
+
import { rawDataToString } from "../utils/ws.js";
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* 插件 Gateway Client 配置选项(简化版)
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
const GATEWAY_SERVER_URL = "ws://127.0.0.1:18789";
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
2
|
+
import { GatewayClientOptions } from './types.js';
|
|
3
|
+
import { getConfigManager } from '../modules/claw-configs/config-manager.js';
|
|
4
|
+
import { rawDataToString } from '../utils/ws.js';
|
|
7
5
|
|
|
8
6
|
/**
|
|
9
7
|
* 纯透传 Gateway Client
|
|
@@ -26,29 +24,28 @@ export class GatewayClient {
|
|
|
26
24
|
if (this.closed) {
|
|
27
25
|
return;
|
|
28
26
|
}
|
|
29
|
-
|
|
27
|
+
|
|
28
|
+
const configManager = getConfigManager();
|
|
29
|
+
const url = `ws://127.0.0.1:${configManager.getGatewayPort()}`;
|
|
30
30
|
this.ws = new WebSocket(url, { maxPayload: 25 * 1024 * 1024 });
|
|
31
31
|
|
|
32
|
-
this.ws.on(
|
|
32
|
+
this.ws.on('open', () => {
|
|
33
33
|
this.opts.onOpen?.();
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
this.ws.on(
|
|
36
|
+
this.ws.on('message', data => {
|
|
37
37
|
const dataText = rawDataToString(data);
|
|
38
|
-
useClawLogger().debug?.(`[yoyoclaw-gateway] received message:, ${dataText.slice(0, 500)}`);
|
|
39
38
|
this.opts.onMessage?.(dataText);
|
|
40
39
|
});
|
|
41
40
|
|
|
42
|
-
this.ws.on(
|
|
41
|
+
this.ws.on('close', (code, reason) => {
|
|
43
42
|
const reasonText = rawDataToString(reason);
|
|
44
43
|
this.ws = null;
|
|
45
|
-
|
|
46
|
-
this.opts.onClose?.();
|
|
44
|
+
this.opts.onClose?.(`code: ${code}, reason: ${reasonText ?? ''}`);
|
|
47
45
|
});
|
|
48
46
|
|
|
49
|
-
this.ws.on(
|
|
50
|
-
|
|
51
|
-
this.opts.onClose?.();
|
|
47
|
+
this.ws.on('error', err => {
|
|
48
|
+
this.opts.onClose?.(`socket error: ${(err as Error).message}`);
|
|
52
49
|
});
|
|
53
50
|
}
|
|
54
51
|
|
|
@@ -66,11 +63,9 @@ export class GatewayClient {
|
|
|
66
63
|
*/
|
|
67
64
|
send(data: Buffer | string): void {
|
|
68
65
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
69
|
-
|
|
70
|
-
"[yoyoclaw-gateway] send failed: gateway not connected"
|
|
71
|
-
);
|
|
72
|
-
return;
|
|
66
|
+
throw new Error('gateway not connected');
|
|
73
67
|
}
|
|
68
|
+
|
|
74
69
|
this.ws.send(data);
|
|
75
70
|
}
|
|
76
71
|
}
|