openclaw-elys 1.3.2 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -36
- package/dist/src/cli.js +60 -2
- package/dist/src/monitor.d.ts +7 -4
- package/dist/src/monitor.js +24 -19
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# openclaw-elys
|
|
2
2
|
|
|
3
|
-
OpenClaw channel plugin for [Elys App](https://elys.ai) — connects your local OpenClaw agent to the Elys mobile app
|
|
3
|
+
OpenClaw channel plugin for [Elys App](https://elys.ai) — connects your local OpenClaw agent to the Elys mobile app.
|
|
4
4
|
|
|
5
|
-
Elys App 的 OpenClaw 频道插件 —
|
|
5
|
+
Elys App 的 OpenClaw 频道插件 — 将本地 OpenClaw 智能体连接到 Elys 手机应用。
|
|
6
6
|
|
|
7
7
|
## Install / 安装
|
|
8
8
|
|
|
@@ -27,32 +27,15 @@ Example / 示例:
|
|
|
27
27
|
npx openclaw-elys setup https://your-gateway.com reg_abc123def456
|
|
28
28
|
# Device registered successfully!
|
|
29
29
|
# Device ID: d_xxxxxxxxxxxx
|
|
30
|
-
# MQTT Broker: mqtts://xxx.emqxcloud.cn:8883
|
|
31
30
|
# Credentials saved to ~/.elys/config.json
|
|
31
|
+
# OpenClaw config updated: channels.elys.gatewayUrl = https://your-gateway.com
|
|
32
|
+
#
|
|
33
|
+
# Restart OpenClaw gateway to activate: openclaw gateway restart
|
|
32
34
|
```
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
The setup command automatically configures `channels.elys` in `~/.openclaw/openclaw.json`. OpenClaw will auto-detect the config change — no restart needed.
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
```json
|
|
39
|
-
{
|
|
40
|
-
"channels": {
|
|
41
|
-
"elys": {
|
|
42
|
-
"enabled": true,
|
|
43
|
-
"gatewayUrl": "https://your-gateway.com"
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Restart the OpenClaw gateway to activate:
|
|
50
|
-
|
|
51
|
-
重启 OpenClaw 网关以激活:
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
openclaw gateway restart
|
|
55
|
-
```
|
|
38
|
+
setup 命令会自动配置 `~/.openclaw/openclaw.json` 中的 `channels.elys`。OpenClaw 会自动检测配置变更,无需重启。
|
|
56
39
|
|
|
57
40
|
## Status / 查看状态
|
|
58
41
|
|
|
@@ -78,18 +61,6 @@ Then remove the plugin:
|
|
|
78
61
|
openclaw plugins uninstall openclaw-elys
|
|
79
62
|
```
|
|
80
63
|
|
|
81
|
-
## How it works / 工作原理
|
|
82
|
-
|
|
83
|
-
```
|
|
84
|
-
Elys App -> Backend -> Gateway (HTTP) -> EMQX (MQTT) -> OpenClaw Plugin
|
|
85
|
-
|
|
|
86
|
-
OpenClaw Plugin -> EMQX (MQTT) -> Gateway -> Backend -> Elys App
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
- **Downlink** / 下行 (App -> Device): Backend calls Gateway HTTP API, Gateway publishes to MQTT
|
|
90
|
-
- **Uplink** / 上行 (Device -> App): Plugin publishes to MQTT, Gateway receives and returns to Backend
|
|
91
|
-
- **Streaming** / 流式: Plugin sends AI reply chunks in real-time via MQTT stream messages
|
|
92
|
-
|
|
93
64
|
## License
|
|
94
65
|
|
|
95
66
|
MIT
|
package/dist/src/cli.js
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import path from "path";
|
|
2
5
|
import { registerDevice } from "./register.js";
|
|
3
6
|
import { loadCredentials, loadGatewayUrl, deleteCredentials, } from "./config.js";
|
|
7
|
+
const OPENCLAW_CONFIG = path.join(os.homedir(), ".openclaw", "openclaw.json");
|
|
8
|
+
/**
|
|
9
|
+
* Auto-configure channels.elys in ~/.openclaw/openclaw.json
|
|
10
|
+
*/
|
|
11
|
+
function configureOpenClawChannel(gatewayUrl) {
|
|
12
|
+
try {
|
|
13
|
+
if (!fs.existsSync(OPENCLAW_CONFIG))
|
|
14
|
+
return false;
|
|
15
|
+
const raw = fs.readFileSync(OPENCLAW_CONFIG, "utf-8");
|
|
16
|
+
const cfg = JSON.parse(raw);
|
|
17
|
+
if (!cfg.channels)
|
|
18
|
+
cfg.channels = {};
|
|
19
|
+
cfg.channels.elys = {
|
|
20
|
+
gatewayUrl,
|
|
21
|
+
};
|
|
22
|
+
fs.writeFileSync(OPENCLAW_CONFIG, JSON.stringify(cfg, null, 2), "utf-8");
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Remove channels.elys from ~/.openclaw/openclaw.json
|
|
31
|
+
*/
|
|
32
|
+
function removeOpenClawChannel() {
|
|
33
|
+
try {
|
|
34
|
+
if (!fs.existsSync(OPENCLAW_CONFIG))
|
|
35
|
+
return false;
|
|
36
|
+
const raw = fs.readFileSync(OPENCLAW_CONFIG, "utf-8");
|
|
37
|
+
const cfg = JSON.parse(raw);
|
|
38
|
+
if (cfg.channels?.elys) {
|
|
39
|
+
delete cfg.channels.elys;
|
|
40
|
+
fs.writeFileSync(OPENCLAW_CONFIG, JSON.stringify(cfg, null, 2), "utf-8");
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
4
49
|
const args = process.argv.slice(2);
|
|
5
50
|
const command = args[0];
|
|
6
51
|
if (command === "setup") {
|
|
@@ -16,8 +61,18 @@ if (command === "setup") {
|
|
|
16
61
|
.then((creds) => {
|
|
17
62
|
console.log("Device registered successfully!");
|
|
18
63
|
console.log(` Device ID: ${creds.deviceId}`);
|
|
19
|
-
console.log(` MQTT Broker: ${creds.mqttBroker}`);
|
|
20
64
|
console.log(` Credentials saved to ~/.elys/config.json`);
|
|
65
|
+
// Auto-configure OpenClaw channel
|
|
66
|
+
if (configureOpenClawChannel(gatewayUrl)) {
|
|
67
|
+
console.log(` OpenClaw config updated: channels.elys.gatewayUrl = ${gatewayUrl}`);
|
|
68
|
+
console.log("");
|
|
69
|
+
console.log("OpenClaw will auto-detect the config change. You're all set!");
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
console.log("");
|
|
73
|
+
console.log("Add to ~/.openclaw/openclaw.json:");
|
|
74
|
+
console.log(` "channels": { "elys": { "gatewayUrl": "${gatewayUrl}" } }`);
|
|
75
|
+
}
|
|
21
76
|
})
|
|
22
77
|
.catch((err) => {
|
|
23
78
|
console.error("Registration failed:", err.message);
|
|
@@ -35,6 +90,7 @@ else if (command === "uninstall") {
|
|
|
35
90
|
if (!gatewayUrl) {
|
|
36
91
|
console.log("No gateway URL found. Cleaning up local credentials only.");
|
|
37
92
|
deleteCredentials();
|
|
93
|
+
removeOpenClawChannel();
|
|
38
94
|
console.log("Local credentials removed.");
|
|
39
95
|
process.exit(0);
|
|
40
96
|
}
|
|
@@ -59,6 +115,9 @@ else if (command === "uninstall") {
|
|
|
59
115
|
})
|
|
60
116
|
.finally(() => {
|
|
61
117
|
deleteCredentials();
|
|
118
|
+
if (removeOpenClawChannel()) {
|
|
119
|
+
console.log("OpenClaw channel config removed.");
|
|
120
|
+
}
|
|
62
121
|
console.log("Local credentials removed.");
|
|
63
122
|
});
|
|
64
123
|
}
|
|
@@ -67,7 +126,6 @@ else if (command === "status") {
|
|
|
67
126
|
if (creds) {
|
|
68
127
|
console.log("Elys device credentials:");
|
|
69
128
|
console.log(` Device ID: ${creds.deviceId}`);
|
|
70
|
-
console.log(` MQTT Broker: ${creds.mqttBroker}`);
|
|
71
129
|
console.log(` Config file: ~/.elys/config.json`);
|
|
72
130
|
}
|
|
73
131
|
else {
|
package/dist/src/monitor.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface MonitorElysOpts {
|
|
|
5
5
|
accountId?: string;
|
|
6
6
|
channelRuntime?: ChannelRuntimeReply;
|
|
7
7
|
}
|
|
8
|
-
/** Subset of OpenClaw's
|
|
8
|
+
/** Subset of OpenClaw's PluginRuntime["channel"] that we use */
|
|
9
9
|
type ReplyPayload = {
|
|
10
10
|
text?: string;
|
|
11
11
|
mediaUrl?: string;
|
|
@@ -18,9 +18,12 @@ type ChannelRuntimeReply = {
|
|
|
18
18
|
ctx: Record<string, unknown>;
|
|
19
19
|
cfg: Record<string, unknown>;
|
|
20
20
|
dispatcherOptions: {
|
|
21
|
-
deliver: (payload: ReplyPayload
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
deliver: (payload: ReplyPayload, info: {
|
|
22
|
+
kind: "tool" | "block" | "final";
|
|
23
|
+
}) => Promise<void>;
|
|
24
|
+
onError?: (err: unknown, info: {
|
|
25
|
+
kind: string;
|
|
26
|
+
}) => void;
|
|
24
27
|
};
|
|
25
28
|
replyOptions?: Record<string, unknown>;
|
|
26
29
|
}) => Promise<{
|
package/dist/src/monitor.js
CHANGED
|
@@ -30,6 +30,7 @@ export async function monitorElysProvider(opts) {
|
|
|
30
30
|
const channelRuntime = opts.channelRuntime;
|
|
31
31
|
const dispatchReply = channelRuntime?.reply?.dispatchReplyWithBufferedBlockDispatcher;
|
|
32
32
|
const finalizeCtx = channelRuntime?.reply?.finalizeInboundContext;
|
|
33
|
+
log(`[elys] channelRuntime available: ${!!channelRuntime}, dispatchReply: ${!!dispatchReply}, finalizeCtx: ${!!finalizeCtx}`);
|
|
33
34
|
const commandHandler = async (cmd) => {
|
|
34
35
|
log(`[elys] executing command: ${cmd.command}`, cmd.args);
|
|
35
36
|
// If we have the full OpenClaw channelRuntime, use the standard dispatch path
|
|
@@ -51,37 +52,36 @@ export async function monitorElysProvider(opts) {
|
|
|
51
52
|
AccountId: "default",
|
|
52
53
|
SessionKey: `elys:${credentials.deviceId}`,
|
|
53
54
|
CommandAuthorized: true,
|
|
55
|
+
OriginatingChannel: "elys",
|
|
56
|
+
OriginatingTo: credentials.deviceId,
|
|
54
57
|
});
|
|
55
58
|
await dispatchReply({
|
|
56
59
|
ctx: inboundCtx,
|
|
57
60
|
cfg: opts.config,
|
|
58
61
|
dispatcherOptions: {
|
|
59
|
-
//
|
|
60
|
-
|
|
62
|
+
// Single deliver callback with kind discriminator (matches OpenClaw API)
|
|
63
|
+
deliver: async (payload, info) => {
|
|
61
64
|
if (payload.text) {
|
|
62
65
|
fullText += payload.text;
|
|
63
66
|
seq++;
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
const done = info.kind === "final";
|
|
68
|
+
mqttClient.publishStreamChunk(cmd.id, payload.text, seq, done);
|
|
69
|
+
if (info.kind === "block") {
|
|
70
|
+
log(`[elys] stream chunk #${seq}: ${payload.text.slice(0, 80)}...`);
|
|
71
|
+
}
|
|
72
|
+
else if (info.kind === "final") {
|
|
73
|
+
log(`[elys] final reply delivered`);
|
|
74
|
+
}
|
|
66
75
|
}
|
|
67
|
-
|
|
68
|
-
// Final delivery
|
|
69
|
-
deliver: async (payload) => {
|
|
70
|
-
if (payload.text) {
|
|
71
|
-
fullText += payload.text;
|
|
72
|
-
}
|
|
73
|
-
// Send final stream marker
|
|
74
|
-
seq++;
|
|
75
|
-
mqttClient.publishStreamChunk(cmd.id, payload.text ?? "", seq, true);
|
|
76
|
-
log(`[elys] final reply delivered`);
|
|
77
|
-
},
|
|
78
|
-
// Tool result delivery (optional)
|
|
79
|
-
deliverToolResult: async (payload) => {
|
|
80
|
-
if (payload.text) {
|
|
76
|
+
else if (info.kind === "final") {
|
|
81
77
|
seq++;
|
|
82
|
-
mqttClient.publishStreamChunk(cmd.id,
|
|
78
|
+
mqttClient.publishStreamChunk(cmd.id, "", seq, true);
|
|
79
|
+
log(`[elys] final reply delivered (empty)`);
|
|
83
80
|
}
|
|
84
81
|
},
|
|
82
|
+
onError: (err, info) => {
|
|
83
|
+
log(`[elys] dispatch error (${info.kind}):`, err);
|
|
84
|
+
},
|
|
85
85
|
},
|
|
86
86
|
});
|
|
87
87
|
return {
|
|
@@ -93,6 +93,7 @@ export async function monitorElysProvider(opts) {
|
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
95
|
catch (err) {
|
|
96
|
+
log(`[elys] dispatch error:`, err);
|
|
96
97
|
return {
|
|
97
98
|
id: cmd.id,
|
|
98
99
|
type: "result",
|
|
@@ -103,6 +104,7 @@ export async function monitorElysProvider(opts) {
|
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
// Fallback: echo the command back (no channelRuntime available)
|
|
107
|
+
log(`[elys] no channelRuntime — using fallback echo handler`);
|
|
106
108
|
return {
|
|
107
109
|
id: cmd.id,
|
|
108
110
|
type: "result",
|
|
@@ -121,6 +123,9 @@ export async function monitorElysProvider(opts) {
|
|
|
121
123
|
}
|
|
122
124
|
}
|
|
123
125
|
function formatCommandAsText(cmd) {
|
|
126
|
+
if (cmd.args?.text && typeof cmd.args.text === "string") {
|
|
127
|
+
return cmd.args.text;
|
|
128
|
+
}
|
|
124
129
|
const parts = [cmd.command];
|
|
125
130
|
if (cmd.args) {
|
|
126
131
|
for (const [k, v] of Object.entries(cmd.args)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-elys",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "OpenClaw Elys channel plugin — connects to Elys App",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -43,4 +43,4 @@
|
|
|
43
43
|
"defaultChoice": "npm"
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
}
|
|
46
|
+
}
|