openclaw-channel-openilink 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 openilink
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # openclaw-channel-openilink
2
+
3
+ [OpenClaw](https://github.com/openclaw/openclaw) 的 Channel 插件,通过 [OpeniLink Hub](https://github.com/openilink/openilink-hub) 接入微信 Bot。
4
+
5
+ 安装后,OpenClaw AI 可以通过 Hub 管理的微信 Bot 收发消息。
6
+
7
+ ## 工作原理
8
+
9
+ ```
10
+ OpenClaw AI 助手
11
+ ↕ (插件 SDK)
12
+ [openclaw-channel-openilink]
13
+ ↕ (WebSocket + HTTP)
14
+ OpeniLink Hub Bot API
15
+ ↕ (iLink 协议)
16
+ 微信 ClawBot
17
+ ```
18
+
19
+ - **收消息**:微信用户发消息 → Hub 通过 WebSocket 推送给插件 → 插件转发给 OpenClaw AI 处理
20
+ - **发消息**:OpenClaw AI 生成回复 → 插件调 Hub Bot API 发送 → Hub 通过 Bot 发到微信
21
+
22
+ ## 安装
23
+
24
+ ```bash
25
+ npm config set @openilink:registry https://npm.pkg.github.com && openclaw plugins install @openilink/openclaw-channel-openilink
26
+ ```
27
+
28
+ ## 配置
29
+
30
+ ### 前置步骤
31
+
32
+ 1. 部署或访问一个 [OpeniLink Hub](https://github.com/openilink/openilink-hub) 实例
33
+ 2. 在 Hub 上安装 **OpenClaw** App 到你的 Bot(应用市场 → OpenClaw → 安装)
34
+ 3. 在安装详情页复制 **Token**
35
+
36
+ ### 单账户
37
+
38
+ 编辑 OpenClaw 配置文件(`openclaw.yaml`):
39
+
40
+ ```yaml
41
+ channels:
42
+ openilink:
43
+ hub_url: "https://hub.openilink.com"
44
+ app_token: "app_你的token"
45
+ ```
46
+
47
+ ### 多账户
48
+
49
+ 连接多个 Hub Bot(不同 Bot 或不同 Hub 实例):
50
+
51
+ ```yaml
52
+ channels:
53
+ openilink:
54
+ accounts:
55
+ sales-bot:
56
+ hub_url: "https://hub.openilink.com"
57
+ app_token: "app_销售bot的token"
58
+ support-bot:
59
+ hub_url: "https://hub.openilink.com"
60
+ app_token: "app_客服bot的token"
61
+ ```
62
+
63
+ 每个账户维护独立的 WebSocket 连接,互不干扰。
64
+
65
+ ### 重启 OpenClaw
66
+
67
+ ```bash
68
+ openclaw restart
69
+ ```
70
+
71
+ 配置完成后,微信消息会自动转发到 OpenClaw AI 处理。
72
+
73
+ ## 配置参数
74
+
75
+ | 参数 | 类型 | 必填 | 说明 |
76
+ |------|------|------|------|
77
+ | `hub_url` | string | 是 | OpeniLink Hub 地址(如 `https://hub.openilink.com`) |
78
+ | `app_token` | string | 是 | 从 Hub 安装 OpenClaw App 后获取的 Token |
79
+
80
+ ## 功能
81
+
82
+ - **实时消息** — 通过 WebSocket 持久连接接收 Hub 消息
83
+ - **自动重连** — 断线后 5 秒自动重连
84
+ - **私聊/群聊** — 支持直接对话和群组消息
85
+ - **媒体消息** — 支持通过 Hub Bot API 发送图片
86
+ - **多账户** — 同时连接多个 Bot
87
+ - **链路追踪** — Trace ID 端到端传递,方便调试
88
+
89
+ ## 开发
90
+
91
+ ```bash
92
+ git clone https://github.com/openilink/openclaw-channel-openilink.git
93
+ cd openclaw-channel-openilink
94
+ npm install
95
+ npm run build
96
+
97
+ # 本地开发链接
98
+ openclaw plugins install --link .
99
+ ```
100
+
101
+ ## 相关链接
102
+
103
+ - [OpeniLink Hub](https://github.com/openilink/openilink-hub) — 微信 Bot 消息管理平台
104
+ - [OpenClaw](https://github.com/openclaw/openclaw) — 开源 AI 助手
105
+ - [OpenClaw 插件文档](https://docs.openclaw.ai/tools/plugin) — 插件开发指南
106
+
107
+ ## License
108
+
109
+ MIT
110
+
111
+ ---
112
+
113
+ ## English
114
+
115
+ OpenClaw channel plugin for [OpeniLink Hub](https://github.com/openilink/openilink-hub) — bridge OpenClaw AI to WeChat bots.
116
+
117
+ ### Quick Start
118
+
119
+ ```bash
120
+ npm config set @openilink:registry https://npm.pkg.github.com && openclaw plugins install @openilink/openclaw-channel-openilink
121
+ ```
122
+
123
+ Configure `openclaw.yaml`:
124
+
125
+ ```yaml
126
+ channels:
127
+ openilink:
128
+ hub_url: "https://hub.openilink.com"
129
+ app_token: "your_app_token"
130
+ ```
131
+
132
+ Then `openclaw restart`.
133
+
134
+ ### How to Get Your Token
135
+
136
+ 1. Log into your OpeniLink Hub
137
+ 2. Go to your Bot → App Marketplace → Install **OpenClaw** app
138
+ 3. Copy the **Token** from the installation detail page
139
+ 4. Paste into your OpenClaw config
140
+
141
+ ### Features
142
+
143
+ - Real-time messaging via WebSocket
144
+ - Auto-reconnect (5s backoff)
145
+ - Direct and group chat support
146
+ - Media messages (images)
147
+ - Multi-account support
148
+ - Trace ID propagation
@@ -0,0 +1,8 @@
1
+ declare const _default: {
2
+ id: string;
3
+ name: string;
4
+ description: string;
5
+ configSchema: import("openclaw/plugin-sdk").OpenClawPluginConfigSchema;
6
+ register: NonNullable<import("openclaw/plugin-sdk/core").OpenClawPluginDefinition["register"]>;
7
+ } & Pick<import("openclaw/plugin-sdk/core").OpenClawPluginDefinition, "kind">;
8
+ export default _default;
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
2
+ import { openiLinkChannel } from "./src/channel.js";
3
+ import { setPluginRuntime } from "./src/runtime.js";
4
+ export default defineChannelPluginEntry({
5
+ id: "openilink",
6
+ name: "OpeniLink Hub",
7
+ description: "Bridge WeChat bots via OpeniLink Hub",
8
+ plugin: openiLinkChannel,
9
+ setRuntime: setPluginRuntime,
10
+ });
@@ -0,0 +1,34 @@
1
+ import type { OpeniLinkConfig } from "./types.js";
2
+ import { startAccount, stopAccount } from "./gateway.js";
3
+ export declare const openiLinkChannel: {
4
+ id: any;
5
+ meta: {
6
+ id: any;
7
+ label: string;
8
+ selectionLabel: string;
9
+ docsPath: string;
10
+ blurb: string;
11
+ };
12
+ capabilities: {
13
+ chatTypes: Array<"direct" | "group">;
14
+ media: boolean;
15
+ };
16
+ config: {
17
+ listAccountIds: (cfg: any) => string[];
18
+ resolveAccount: (cfg: any, accountId?: string | null) => OpeniLinkConfig;
19
+ isConfigured: (account: OpeniLinkConfig) => boolean;
20
+ describeAccount: (account: OpeniLinkConfig) => {
21
+ status: string;
22
+ label: string;
23
+ };
24
+ };
25
+ gateway: {
26
+ startAccount: typeof startAccount;
27
+ stopAccount: typeof stopAccount;
28
+ };
29
+ outbound: {
30
+ deliveryMode: "direct";
31
+ sendText(ctx: any): Promise<any>;
32
+ sendMedia(ctx: any): Promise<any>;
33
+ };
34
+ };
@@ -0,0 +1,31 @@
1
+ import { resolveConfig, listAccountIds } from "./config.js";
2
+ import { startAccount, stopAccount } from "./gateway.js";
3
+ import { createOutboundAdapter } from "./outbound.js";
4
+ export const openiLinkChannel = {
5
+ id: "openilink",
6
+ meta: {
7
+ id: "openilink",
8
+ label: "OpeniLink Hub",
9
+ selectionLabel: "OpeniLink Hub (WeChat Bridge)",
10
+ docsPath: "channels/openilink",
11
+ blurb: "Send and receive WeChat messages via OpeniLink Hub",
12
+ },
13
+ capabilities: {
14
+ chatTypes: ["direct", "group"],
15
+ media: true,
16
+ },
17
+ config: {
18
+ listAccountIds: (cfg) => listAccountIds(cfg),
19
+ resolveAccount: (cfg, accountId) => resolveConfig(cfg, accountId),
20
+ isConfigured: (account) => !!(account.hubUrl && account.appToken),
21
+ describeAccount: (account) => ({
22
+ status: account.hubUrl ? "configured" : "unconfigured",
23
+ label: account.hubUrl || "Not configured",
24
+ }),
25
+ },
26
+ gateway: {
27
+ startAccount,
28
+ stopAccount,
29
+ },
30
+ outbound: createOutboundAdapter(resolveConfig),
31
+ };
@@ -0,0 +1,4 @@
1
+ import type { OpeniLinkConfig } from "./types.js";
2
+ export declare function resolveConfig(cfg: any, accountId?: string | null): OpeniLinkConfig;
3
+ export declare function listAccountIds(cfg: any): string[];
4
+ export declare function isConfigured(cfg: any, accountId?: string | null): boolean;
@@ -0,0 +1,30 @@
1
+ export function resolveConfig(cfg, accountId) {
2
+ const channelCfg = cfg?.channels?.openilink || {};
3
+ // Support multi-account
4
+ if (accountId && channelCfg.accounts?.[accountId]) {
5
+ const account = channelCfg.accounts[accountId];
6
+ return {
7
+ hubUrl: (account.hub_url || channelCfg.hub_url || "").replace(/\/$/, ""),
8
+ appToken: account.app_token || channelCfg.app_token || "",
9
+ };
10
+ }
11
+ return {
12
+ hubUrl: (channelCfg.hub_url || "").replace(/\/$/, ""),
13
+ appToken: channelCfg.app_token || "",
14
+ };
15
+ }
16
+ export function listAccountIds(cfg) {
17
+ const channelCfg = cfg?.channels?.openilink || {};
18
+ if (channelCfg.accounts) {
19
+ return Object.keys(channelCfg.accounts);
20
+ }
21
+ // Single account mode — return default ID
22
+ if (channelCfg.hub_url && channelCfg.app_token) {
23
+ return ["default"];
24
+ }
25
+ return [];
26
+ }
27
+ export function isConfigured(cfg, accountId) {
28
+ const resolved = resolveConfig(cfg, accountId);
29
+ return !!(resolved.hubUrl && resolved.appToken);
30
+ }
@@ -0,0 +1,2 @@
1
+ export declare function startAccount(ctx: any): Promise<void>;
2
+ export declare function stopAccount(ctx: any): Promise<void>;
@@ -0,0 +1,71 @@
1
+ import WebSocket from "ws";
2
+ import { handleInboundEvent } from "./inbound.js";
3
+ const connections = new Map();
4
+ export async function startAccount(ctx) {
5
+ const { accountId, account, cfg, abortSignal, setStatus } = ctx;
6
+ const config = account;
7
+ if (!config.hubUrl || !config.appToken) {
8
+ setStatus({ status: "error", error: "Missing hub_url or app_token" });
9
+ return;
10
+ }
11
+ const wsUrl = `${config.hubUrl.replace(/^http/, "ws")}/bot/v1/ws?token=${config.appToken}`;
12
+ function connect() {
13
+ if (abortSignal.aborted)
14
+ return;
15
+ const ws = new WebSocket(wsUrl);
16
+ connections.set(accountId, ws);
17
+ ws.on("open", () => {
18
+ setStatus({ status: "connected" });
19
+ console.log(`[openilink] Connected to Hub: ${config.hubUrl}`);
20
+ });
21
+ ws.on("message", (data) => {
22
+ try {
23
+ const msg = JSON.parse(data.toString());
24
+ if (msg.type === "init") {
25
+ console.log(`[openilink] Init: bot=${msg.data.bot_id}, app=${msg.data.app_slug}`);
26
+ return;
27
+ }
28
+ if (msg.type === "event") {
29
+ handleInboundEvent(msg, config, cfg, accountId);
30
+ return;
31
+ }
32
+ if (msg.type === "pong")
33
+ return;
34
+ }
35
+ catch (err) {
36
+ console.error("[openilink] Parse error:", err);
37
+ }
38
+ });
39
+ ws.on("close", () => {
40
+ connections.delete(accountId);
41
+ setStatus({ status: "disconnected" });
42
+ // Reconnect after 5s
43
+ if (!abortSignal.aborted) {
44
+ setTimeout(connect, 5000);
45
+ }
46
+ });
47
+ ws.on("error", (err) => {
48
+ console.error("[openilink] WS error:", err.message);
49
+ setStatus({ status: "error", error: err.message });
50
+ });
51
+ // Ping every 30s
52
+ const pingInterval = setInterval(() => {
53
+ if (ws.readyState === WebSocket.OPEN) {
54
+ ws.send(JSON.stringify({ type: "ping" }));
55
+ }
56
+ }, 30000);
57
+ ws.on("close", () => clearInterval(pingInterval));
58
+ abortSignal.addEventListener("abort", () => {
59
+ clearInterval(pingInterval);
60
+ ws.close();
61
+ });
62
+ }
63
+ connect();
64
+ }
65
+ export async function stopAccount(ctx) {
66
+ const ws = connections.get(ctx.accountId);
67
+ if (ws) {
68
+ ws.close();
69
+ connections.delete(ctx.accountId);
70
+ }
71
+ }
@@ -0,0 +1,2 @@
1
+ import type { HubWSEvent, OpeniLinkConfig } from "./types.js";
2
+ export declare function handleInboundEvent(event: HubWSEvent, config: OpeniLinkConfig, cfg: any, accountId: string): Promise<void>;
@@ -0,0 +1,97 @@
1
+ import { getPluginRuntime } from "./runtime.js";
2
+ export async function handleInboundEvent(event, config, cfg, accountId) {
3
+ const rt = getPluginRuntime();
4
+ if (!rt?.channel)
5
+ return;
6
+ const eventData = event.event.data;
7
+ const sender = eventData.sender;
8
+ const group = eventData.group;
9
+ const content = eventData.content || "";
10
+ const senderId = sender?.id || "unknown";
11
+ const senderName = sender?.name || senderId;
12
+ const isDirect = !group;
13
+ const peerId = isDirect ? senderId : group.id;
14
+ // Resolve agent route
15
+ const route = rt.channel.routing.resolveAgentRoute({
16
+ cfg,
17
+ channel: "openilink",
18
+ accountId,
19
+ peer: { kind: isDirect ? "direct" : "group", id: peerId },
20
+ });
21
+ // Format inbound envelope
22
+ const envelopeOptions = rt.channel.reply.resolveEnvelopeFormatOptions(cfg);
23
+ const body = rt.channel.reply.formatInboundEnvelope({
24
+ channel: "OpeniLink",
25
+ from: senderName,
26
+ timestamp: event.event.timestamp * 1000,
27
+ body: content,
28
+ chatType: isDirect ? "direct" : "group",
29
+ sender: { name: senderName, id: senderId },
30
+ envelope: envelopeOptions,
31
+ });
32
+ // Build inbound context
33
+ const ctx = rt.channel.reply.finalizeInboundContext({
34
+ Body: body,
35
+ RawBody: content,
36
+ CommandBody: content,
37
+ From: peerId,
38
+ To: peerId,
39
+ SessionKey: route.sessionKey,
40
+ AccountId: accountId,
41
+ ChatType: isDirect ? "direct" : "group",
42
+ SenderName: senderName,
43
+ SenderId: senderId,
44
+ Provider: "openilink",
45
+ Surface: "openilink",
46
+ MessageSid: event.event.id || `${event.trace_id}-${Date.now()}`,
47
+ Timestamp: event.event.timestamp * 1000,
48
+ CommandAuthorized: true,
49
+ OriginatingChannel: "openilink",
50
+ OriginatingTo: peerId,
51
+ });
52
+ // Record inbound session
53
+ const storePath = rt.channel.session.resolveStorePath(`openilink/${accountId}`);
54
+ await rt.channel.session.recordInboundSession({
55
+ storePath,
56
+ sessionKey: route.sessionKey,
57
+ ctx,
58
+ onRecordError: (err) => {
59
+ console.error("[openilink] Session record error:", err);
60
+ },
61
+ });
62
+ // Dispatch to AI and deliver response
63
+ await rt.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
64
+ ctx,
65
+ cfg,
66
+ dispatcherOptions: {
67
+ responsePrefix: "",
68
+ deliver: async (payload) => {
69
+ // Send AI response back to Hub
70
+ if (payload.text) {
71
+ await sendToHub(config, payload.text, event.trace_id);
72
+ }
73
+ if (payload.mediaUrl) {
74
+ await sendToHub(config, payload.mediaUrl, event.trace_id);
75
+ }
76
+ },
77
+ },
78
+ replyOptions: {},
79
+ });
80
+ }
81
+ async function sendToHub(config, content, traceId) {
82
+ const body = { content };
83
+ if (traceId)
84
+ body.trace_id = traceId;
85
+ const resp = await fetch(`${config.hubUrl}/bot/v1/message/send`, {
86
+ method: "POST",
87
+ headers: {
88
+ "Content-Type": "application/json",
89
+ Authorization: `Bearer ${config.appToken}`,
90
+ },
91
+ body: JSON.stringify(body),
92
+ });
93
+ if (!resp.ok) {
94
+ const text = await resp.text();
95
+ console.error(`[openilink] Send failed: ${resp.status} ${text}`);
96
+ }
97
+ }
@@ -0,0 +1,6 @@
1
+ import type { OpeniLinkConfig } from "./types.js";
2
+ export declare function createOutboundAdapter(resolveConfig: (cfg: any, accountId?: string | null) => OpeniLinkConfig): {
3
+ deliveryMode: "direct";
4
+ sendText(ctx: any): Promise<any>;
5
+ sendMedia(ctx: any): Promise<any>;
6
+ };
@@ -0,0 +1,43 @@
1
+ export function createOutboundAdapter(resolveConfig) {
2
+ return {
3
+ deliveryMode: "direct",
4
+ async sendText(ctx) {
5
+ const config = resolveConfig(ctx.cfg, ctx.accountId);
6
+ const body = { content: ctx.text };
7
+ const resp = await fetch(`${config.hubUrl}/bot/v1/message/send`, {
8
+ method: "POST",
9
+ headers: {
10
+ "Content-Type": "application/json",
11
+ Authorization: `Bearer ${config.appToken}`,
12
+ },
13
+ body: JSON.stringify(body),
14
+ });
15
+ if (!resp.ok) {
16
+ return { ok: false, error: `Hub returned ${resp.status}` };
17
+ }
18
+ const result = await resp.json();
19
+ return { ok: true, messageId: result.client_id };
20
+ },
21
+ async sendMedia(ctx) {
22
+ const config = resolveConfig(ctx.cfg, ctx.accountId);
23
+ const body = {
24
+ content: ctx.text || "",
25
+ type: "image",
26
+ url: ctx.mediaUrl || "",
27
+ };
28
+ const resp = await fetch(`${config.hubUrl}/bot/v1/message/send`, {
29
+ method: "POST",
30
+ headers: {
31
+ "Content-Type": "application/json",
32
+ Authorization: `Bearer ${config.appToken}`,
33
+ },
34
+ body: JSON.stringify(body),
35
+ });
36
+ if (!resp.ok) {
37
+ return { ok: false, error: `Hub returned ${resp.status}` };
38
+ }
39
+ const result = await resp.json();
40
+ return { ok: true, messageId: result.client_id };
41
+ },
42
+ };
43
+ }
@@ -0,0 +1,3 @@
1
+ import type { PluginRuntime } from "openclaw/plugin-sdk/core";
2
+ export declare function setPluginRuntime(rt: PluginRuntime): void;
3
+ export declare function getPluginRuntime(): PluginRuntime;
@@ -0,0 +1,7 @@
1
+ let runtime;
2
+ export function setPluginRuntime(rt) {
3
+ runtime = rt;
4
+ }
5
+ export function getPluginRuntime() {
6
+ return runtime;
7
+ }
@@ -0,0 +1,54 @@
1
+ export interface OpeniLinkConfig {
2
+ hubUrl: string;
3
+ appToken: string;
4
+ }
5
+ export interface HubWSInit {
6
+ type: "init";
7
+ data: {
8
+ installation_id: string;
9
+ bot_id: string;
10
+ app_name: string;
11
+ app_slug: string;
12
+ };
13
+ }
14
+ export interface HubWSEvent {
15
+ type: "event";
16
+ v: number;
17
+ trace_id: string;
18
+ installation_id: string;
19
+ bot: {
20
+ id: string;
21
+ };
22
+ event: {
23
+ type: string;
24
+ id: string;
25
+ timestamp: number;
26
+ data: {
27
+ content?: string;
28
+ sender?: {
29
+ id: string;
30
+ name: string;
31
+ };
32
+ group?: {
33
+ id: string;
34
+ name: string;
35
+ } | null;
36
+ msg_type?: string;
37
+ message_id?: string;
38
+ [key: string]: unknown;
39
+ };
40
+ };
41
+ }
42
+ export interface HubWSAck {
43
+ type: "ack";
44
+ req_id: string;
45
+ ok: boolean;
46
+ }
47
+ export interface HubWSError {
48
+ type: "error";
49
+ req_id?: string;
50
+ error: string;
51
+ }
52
+ export type HubWSMessage = HubWSInit | HubWSEvent | HubWSAck | HubWSError | {
53
+ type: "pong";
54
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "openilink",
3
+ "channels": ["openilink"],
4
+ "configSchema": {
5
+ "type": "object",
6
+ "properties": {
7
+ "hub_url": {
8
+ "type": "string",
9
+ "description": "OpeniLink Hub URL (e.g., https://hub.openilink.com)"
10
+ },
11
+ "app_token": {
12
+ "type": "string",
13
+ "description": "App installation token from Hub"
14
+ }
15
+ },
16
+ "required": ["hub_url", "app_token"]
17
+ }
18
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "openclaw-channel-openilink",
3
+ "version": "0.1.0",
4
+ "description": "OpenClaw channel plugin for OpeniLink Hub — bridge WeChat bots via Hub",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist/",
9
+ "openclaw.plugin.json",
10
+ "README.md"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "dev": "tsc --watch",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "keywords": ["openclaw", "openclaw-plugin", "openilink", "wechat"],
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/openilink/openclaw-channel-openilink"
22
+ },
23
+ "peerDependencies": {
24
+ "openclaw": ">=2025.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "openclaw": "*",
28
+ "typescript": "^5.0.0",
29
+ "ws": "^8.0.0",
30
+ "@types/ws": "^8.0.0"
31
+ },
32
+ "dependencies": {
33
+ "ws": "^8.18.0"
34
+ }
35
+ }