@eyeclaw/eyeclaw 1.0.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 EyeClaw Team
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,222 @@
1
+ # @eyeclaw/eyeclaw
2
+
3
+ EyeClaw channel plugin for [OpenClaw](https://github.com/openclaw/openclaw) - Connect your local OpenClaw instance to the EyeClaw platform.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ openclaw plugins install @eyeclaw/eyeclaw
9
+ ```
10
+
11
+ ### Windows Troubleshooting
12
+
13
+ If `openclaw plugins install` fails with `spawn npm ENOENT`, install manually:
14
+
15
+ ```bash
16
+ # 1. Download the package
17
+ curl -O https://registry.npmjs.org/@eyeclaw/eyeclaw/-/eyeclaw-1.0.0.tgz
18
+
19
+ # 2. Install from local file
20
+ openclaw plugins install ./eyeclaw-1.0.0.tgz
21
+ ```
22
+
23
+ ## Configuration
24
+
25
+ ### 1. Create a Bot on EyeClaw
26
+
27
+ 1. Sign up at [https://eyeclaw.io](https://eyeclaw.io)
28
+ 2. Create a new bot in your dashboard
29
+ 3. Copy the Bot ID and SDK Token
30
+
31
+ ### 2. Configure OpenClaw
32
+
33
+ ```bash
34
+ openclaw config set channels.eyeclaw.enabled true
35
+ openclaw config set channels.eyeclaw.botId "your-bot-id"
36
+ openclaw config set channels.eyeclaw.sdkToken "your-sdk-token"
37
+ openclaw config set channels.eyeclaw.serverUrl "https://eyeclaw.io"
38
+ ```
39
+
40
+ ### 3. Start OpenClaw
41
+
42
+ ```bash
43
+ openclaw start
44
+ ```
45
+
46
+ You should see the connection message:
47
+
48
+ ```
49
+ ✅ Successfully subscribed to BotChannel
50
+ 🎉 Bot connected! Session ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
51
+ ```
52
+
53
+ ## Configuration Options
54
+
55
+ ```yaml
56
+ channels:
57
+ eyeclaw:
58
+ enabled: true
59
+ botId: "1"
60
+ sdkToken: "your-sdk-token-here"
61
+ serverUrl: "https://eyeclaw.io" # or self-hosted URL
62
+ reconnectInterval: 5000 # milliseconds (default: 5000)
63
+ heartbeatInterval: 30000 # milliseconds (default: 30000)
64
+ ```
65
+
66
+ | Option | Type | Default | Description |
67
+ |--------|------|---------|-------------|
68
+ | `enabled` | boolean | `false` | Enable/disable the plugin |
69
+ | `botId` | string | - | Bot ID from EyeClaw dashboard |
70
+ | `sdkToken` | string | - | SDK Token for authentication |
71
+ | `serverUrl` | string | `https://eyeclaw.io` | EyeClaw server URL |
72
+ | `reconnectInterval` | number | `5000` | Reconnect interval in ms |
73
+ | `heartbeatInterval` | number | `30000` | Heartbeat interval in ms |
74
+
75
+ ## Features
76
+
77
+ - **WebSocket Connection**: Real-time bidirectional communication with EyeClaw platform
78
+ - **Auto-Reconnect**: Automatically reconnects on connection loss
79
+ - **Heartbeat**: Keeps connection alive with periodic ping/pong
80
+ - **Real-time Monitoring**: View bot status, logs, and sessions in EyeClaw dashboard
81
+ - **MCP Integration**: Expose your bot as MCP plugin for platforms like Coze, Claude Desktop
82
+ - **Session Management**: Track connection sessions with uptime and activity logs
83
+
84
+ ## MCP Plugin Integration
85
+
86
+ Your bot automatically provides an MCP plugin URL that can be used with AI platforms:
87
+
88
+ ```
89
+ https://eyeclaw.io/mcp/stream/your-bot-id?api_key=your-api-key
90
+ ```
91
+
92
+ ### Supported Platforms
93
+
94
+ - **Coze**: Add as custom MCP plugin
95
+ - **Claude Desktop**: Add to `claude_desktop_config.json`
96
+ - **Other MCP-compatible platforms**: Use the stream URL
97
+
98
+ ## Upgrade
99
+
100
+ ```bash
101
+ openclaw plugins update eyeclaw
102
+ ```
103
+
104
+ ## Uninstall
105
+
106
+ ```bash
107
+ openclaw plugins uninstall eyeclaw
108
+ ```
109
+
110
+ ## Troubleshooting
111
+
112
+ ### Bot cannot connect
113
+
114
+ 1. Check your internet connection
115
+ 2. Verify `botId` and `sdkToken` are correct
116
+ 3. Ensure `serverUrl` is accessible
117
+ 4. Check OpenClaw logs: `openclaw logs`
118
+
119
+ ### Connection drops frequently
120
+
121
+ 1. Check firewall settings
122
+ 2. Verify network stability
123
+ 3. Try increasing `heartbeatInterval`:
124
+
125
+ ```bash
126
+ openclaw config set channels.eyeclaw.heartbeatInterval 60000
127
+ ```
128
+
129
+ ### "Unauthorized" error
130
+
131
+ Your SDK token may be invalid or expired. Regenerate it in the EyeClaw dashboard:
132
+
133
+ 1. Go to your bot settings
134
+ 2. Click "Regenerate SDK Token"
135
+ 3. Update OpenClaw config with the new token
136
+
137
+ ## Architecture
138
+
139
+ ```
140
+ ┌─────────────────┐ WebSocket ┌──────────────────┐
141
+ │ Local OpenClaw │◄────────────────────────►│ EyeClaw Platform │
142
+ │ Instance │ ActionCable/BotChannel │ (Rails) │
143
+ └─────────────────┘ └──────────────────┘
144
+ │ │
145
+ │ │
146
+ ▼ ▼
147
+ Execute commands Real-time dashboard
148
+ Process requests Status monitoring
149
+ Run tools Activity logs
150
+ ```
151
+
152
+ ### How it Works
153
+
154
+ 1. **Plugin Initialization**: When OpenClaw starts, the plugin connects to EyeClaw via WebSocket
155
+ 2. **Authentication**: Uses SDK Token to authenticate with BotChannel
156
+ 3. **Session Creation**: Server creates a session and assigns a session ID
157
+ 4. **Heartbeat**: Plugin sends periodic pings to keep connection alive
158
+ 5. **Event Forwarding**: OpenClaw events (messages, tool execution, errors) are sent to EyeClaw
159
+ 6. **Dashboard Updates**: EyeClaw dashboard shows real-time status and logs
160
+
161
+ ## Development
162
+
163
+ ### Build from Source
164
+
165
+ ```bash
166
+ cd sdk
167
+ npm install
168
+ npm run build
169
+ ```
170
+
171
+ ### Link Locally
172
+
173
+ ```bash
174
+ npm link
175
+ openclaw plugins install /path/to/eyeclaw/sdk
176
+ ```
177
+
178
+ ### Run TypeScript in Watch Mode
179
+
180
+ ```bash
181
+ npm run watch
182
+ ```
183
+
184
+ ## API Reference
185
+
186
+ ### EyeClawClient
187
+
188
+ ```typescript
189
+ import { EyeClawClient } from '@eyeclaw/eyeclaw'
190
+
191
+ const client = new EyeClawClient(config, logger)
192
+
193
+ // Connect to EyeClaw
194
+ await client.connect()
195
+
196
+ // Send log message
197
+ client.sendLog('info', 'Hello from OpenClaw')
198
+
199
+ // Send command result
200
+ client.sendCommandResult('execute_tool', { result: 'success' })
201
+
202
+ // Request bot status
203
+ client.requestStatus()
204
+
205
+ // Disconnect
206
+ client.disconnect()
207
+ ```
208
+
209
+ ## Support
210
+
211
+ - **Documentation**: [https://eyeclaw.io/docs](https://eyeclaw.io/docs)
212
+ - **Issues**: [https://github.com/eyeclaw/eyeclaw/issues](https://github.com/eyeclaw/eyeclaw/issues)
213
+ - **Discord**: [https://discord.gg/eyeclaw](https://discord.gg/eyeclaw)
214
+
215
+ ## License
216
+
217
+ MIT © EyeClaw Team
218
+
219
+ ## Related Projects
220
+
221
+ - [OpenClaw](https://github.com/openclaw/openclaw) - The AI assistant framework
222
+ - [clawdbot-feishu](https://github.com/m1heng/clawdbot-feishu) - Feishu/Lark channel plugin (inspiration for this plugin)
package/index.ts ADDED
@@ -0,0 +1,82 @@
1
+ import type { OpenClawPluginApi } from 'openclaw/plugin-sdk'
2
+ import { emptyPluginConfigSchema } from 'openclaw/plugin-sdk'
3
+ import { EyeClawClient } from './src/client.js'
4
+ import type { PluginConfig } from './src/types.js'
5
+
6
+ let client: EyeClawClient | null = null
7
+
8
+ /**
9
+ * EyeClaw SDK - OpenClaw Channel Plugin
10
+ *
11
+ * Connects local OpenClaw instance to EyeClaw platform via WebSocket.
12
+ */
13
+ const plugin = {
14
+ id: 'eyeclaw',
15
+ name: 'EyeClaw',
16
+ description: 'EyeClaw platform channel plugin',
17
+ configSchema: emptyPluginConfigSchema(),
18
+
19
+ register(api: OpenClawPluginApi) {
20
+ const runtime = api.runtime
21
+ const logger = runtime.logging.getChildLogger({ plugin: 'eyeclaw' })
22
+ const config = runtime.config as any
23
+
24
+ // Get EyeClaw config from channels.eyeclaw
25
+ const eyeclawConfig: PluginConfig = config?.channels?.eyeclaw || {}
26
+
27
+ // Check if enabled
28
+ if (eyeclawConfig.enabled === false) {
29
+ logger.info('Plugin disabled in config')
30
+ return
31
+ }
32
+
33
+ // Validate required fields
34
+ if (!eyeclawConfig.botId || !eyeclawConfig.sdkToken) {
35
+ logger.warn('botId and sdkToken are required')
36
+ logger.warn('Configure with: openclaw config set channels.eyeclaw.botId "YOUR_BOT_ID"')
37
+ logger.warn('Configure with: openclaw config set channels.eyeclaw.sdkToken "YOUR_SDK_TOKEN"')
38
+ return
39
+ }
40
+
41
+ // Set defaults
42
+ eyeclawConfig.serverUrl = eyeclawConfig.serverUrl || 'http://localhost:3000'
43
+ eyeclawConfig.reconnectInterval = eyeclawConfig.reconnectInterval || 5000
44
+ eyeclawConfig.heartbeatInterval = eyeclawConfig.heartbeatInterval || 30000
45
+
46
+ logger.info('🦞 Starting EyeClaw SDK...', { botId: eyeclawConfig.botId, serverUrl: eyeclawConfig.serverUrl })
47
+
48
+ // Create logger adapter for client (simple console logger)
49
+ const clientLogger = {
50
+ debug: (msg: string) => logger.debug(msg),
51
+ info: (msg: string) => logger.info(msg),
52
+ warn: (msg: string) => logger.warn(msg),
53
+ error: (msg: string) => logger.error(msg),
54
+ }
55
+
56
+ // Create and connect WebSocket client
57
+ client = new EyeClawClient(eyeclawConfig, clientLogger)
58
+
59
+ client.connect().then(() => {
60
+ logger.info('✅ Successfully connected to EyeClaw platform')
61
+ }).catch((error: Error) => {
62
+ logger.error('Failed to connect to EyeClaw', { error: error.message })
63
+ })
64
+
65
+ // Handle shutdown
66
+ const shutdown = () => {
67
+ if (client) {
68
+ logger.info('🛑 Shutting down EyeClaw SDK...')
69
+ client.disconnect()
70
+ }
71
+ }
72
+
73
+ process.on('SIGINT', shutdown)
74
+ process.on('SIGTERM', shutdown)
75
+ },
76
+ }
77
+
78
+ export default plugin
79
+
80
+ // Re-export for direct usage
81
+ export { EyeClawClient } from './src/client.js'
82
+ export * from './src/types.js'
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "eyeclaw",
3
+ "channels": ["eyeclaw"],
4
+ "configSchema": {
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "properties": {}
8
+ }
9
+ }
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@eyeclaw/eyeclaw",
3
+ "version": "1.0.0",
4
+ "description": "EyeClaw channel plugin for OpenClaw - Connect your local OpenClaw instance to EyeClaw platform",
5
+ "type": "module",
6
+ "main": "./index.ts",
7
+ "types": "./index.ts",
8
+ "files": [
9
+ "index.ts",
10
+ "src",
11
+ "README.md",
12
+ "LICENSE",
13
+ "openclaw.plugin.json"
14
+ ],
15
+ "keywords": [
16
+ "openclaw",
17
+ "eyeclaw",
18
+ "plugin",
19
+ "channel",
20
+ "mcp",
21
+ "websocket"
22
+ ],
23
+ "author": "EyeClaw Team",
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/eyeclaw/eyeclaw.git",
28
+ "directory": "sdk"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/eyeclaw/eyeclaw/issues"
32
+ },
33
+ "homepage": "https://eyeclaw.io",
34
+ "openclaw": {
35
+ "extensions": ["./index.ts"],
36
+ "channel": {
37
+ "id": "eyeclaw",
38
+ "label": "EyeClaw",
39
+ "selectionLabel": "EyeClaw Platform",
40
+ "blurb": "Connect local OpenClaw to EyeClaw platform via WebSocket.",
41
+ "order": 100
42
+ },
43
+ "install": {
44
+ "npmSpec": "@eyeclaw/eyeclaw",
45
+ "localPath": ".",
46
+ "defaultChoice": "npm"
47
+ }
48
+ },
49
+ "dependencies": {
50
+ "ws": "^8.16.0",
51
+ "@types/ws": "^8.5.10",
52
+ "zod": "^3.22.4"
53
+ },
54
+ "peerDependencies": {
55
+ "openclaw": ">=2026.2.3"
56
+ },
57
+ "devDependencies": {
58
+ "typescript": "^5.3.3",
59
+ "@types/node": "^20.11.0",
60
+ "openclaw": ">=2026.2.3"
61
+ },
62
+ "engines": {
63
+ "node": ">=18.0.0"
64
+ }
65
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ import chalk from 'chalk'
3
+
4
+ /**
5
+ * EyeClaw SDK CLI
6
+ *
7
+ * This file provides a minimal CLI interface for the plugin.
8
+ * The actual installation is handled by: openclaw plugins install @eyeclaw/sdk
9
+ */
10
+
11
+ const USAGE = `
12
+ ${chalk.bold.blue('EyeClaw SDK')} - Connect your local OpenClaw to EyeClaw platform
13
+
14
+ ${chalk.bold('Installation:')}
15
+ ${chalk.cyan('openclaw plugins install @eyeclaw/sdk')}
16
+
17
+ ${chalk.bold('Configuration:')}
18
+ ${chalk.cyan('openclaw config set channels.eyeclaw.enabled true')}
19
+ ${chalk.cyan('openclaw config set channels.eyeclaw.botId "your-bot-id"')}
20
+ ${chalk.cyan('openclaw config set channels.eyeclaw.sdkToken "your-sdk-token"')}
21
+ ${chalk.cyan('openclaw config set channels.eyeclaw.serverUrl "https://eyeclaw.io"')}
22
+
23
+ ${chalk.bold('Upgrade:')}
24
+ ${chalk.cyan('openclaw plugins update eyeclaw')}
25
+
26
+ ${chalk.bold('Uninstall:')}
27
+ ${chalk.cyan('openclaw plugins uninstall eyeclaw')}
28
+
29
+ ${chalk.bold('Documentation:')}
30
+ ${chalk.cyan('https://eyeclaw.io/docs')}
31
+
32
+ ${chalk.bold('Support:')}
33
+ ${chalk.cyan('https://github.com/eyeclaw/eyeclaw/issues')}
34
+ `
35
+
36
+ function main() {
37
+ const args = process.argv.slice(2)
38
+
39
+ if (args.includes('--help') || args.includes('-h')) {
40
+ console.log(USAGE)
41
+ process.exit(0)
42
+ }
43
+
44
+ if (args.includes('--version') || args.includes('-v')) {
45
+ // Read version from package.json
46
+ const pkg = require('../package.json')
47
+ console.log(`v${pkg.version}`)
48
+ process.exit(0)
49
+ }
50
+
51
+ // Default: show usage
52
+ console.log(USAGE)
53
+ console.log(chalk.yellow('ℹ This is an OpenClaw plugin. Please install it using:'))
54
+ console.log(chalk.cyan(' openclaw plugins install @eyeclaw/sdk\n'))
55
+ }
56
+
57
+ main()
package/src/client.ts ADDED
@@ -0,0 +1,226 @@
1
+ import WebSocket from 'ws'
2
+ import type { PluginConfig, Logger, ChannelMessage, BotStatus } from './types.js'
3
+
4
+ export class EyeClawClient {
5
+ private ws: WebSocket | null = null
6
+ private config: PluginConfig
7
+ private logger: Logger
8
+ private reconnectTimer: NodeJS.Timeout | null = null
9
+ private heartbeatTimer: NodeJS.Timeout | null = null
10
+ private sessionId: string | null = null
11
+ private connected = false
12
+
13
+ constructor(config: PluginConfig, logger: Logger) {
14
+ this.config = config
15
+ this.logger = logger
16
+ }
17
+
18
+ async connect(): Promise<void> {
19
+ if (this.connected) {
20
+ this.logger.warn('Already connected to EyeClaw')
21
+ return
22
+ }
23
+
24
+ // Add sdk_token as query parameter for connection authentication
25
+ const wsUrl = this.config.serverUrl.replace(/^http/, 'ws') + '/cable?sdk_token=' + encodeURIComponent(this.config.sdkToken)
26
+ this.logger.info(`Connecting to EyeClaw: ${wsUrl.split('?')[0]}?sdk_token=***`)
27
+
28
+ try {
29
+ this.ws = new WebSocket(wsUrl)
30
+
31
+ this.ws.on('open', () => this.handleOpen())
32
+ this.ws.on('message', (data) => this.handleMessage(data))
33
+ this.ws.on('error', (error) => this.handleError(error))
34
+ this.ws.on('close', () => this.handleClose())
35
+ } catch (error) {
36
+ this.logger.error('Failed to create WebSocket connection:', error)
37
+ this.scheduleReconnect()
38
+ }
39
+ }
40
+
41
+ private handleOpen(): void {
42
+ this.logger.info('WebSocket connected, subscribing to BotChannel...')
43
+ this.connected = true
44
+
45
+ // Subscribe to BotChannel (token already passed in connection URL)
46
+ const subscribeMessage = {
47
+ command: 'subscribe',
48
+ identifier: JSON.stringify({
49
+ channel: 'BotChannel',
50
+ }),
51
+ }
52
+
53
+ this.send(subscribeMessage)
54
+ }
55
+
56
+ private handleMessage(data: WebSocket.Data): void {
57
+ try {
58
+ const message = JSON.parse(data.toString())
59
+ this.logger.debug('Received message:', message)
60
+
61
+ // ActionCable protocol messages
62
+ if (message.type === 'ping') {
63
+ // Respond to ping
64
+ return
65
+ }
66
+
67
+ if (message.type === 'welcome') {
68
+ this.logger.info('Received welcome message from ActionCable')
69
+ return
70
+ }
71
+
72
+ if (message.type === 'confirm_subscription') {
73
+ this.logger.info('✅ Successfully subscribed to BotChannel')
74
+ this.startHeartbeat()
75
+ return
76
+ }
77
+
78
+ // Channel messages
79
+ if (message.message) {
80
+ this.handleChannelMessage(message.message)
81
+ }
82
+ } catch (error) {
83
+ this.logger.error('Failed to parse message:', error)
84
+ }
85
+ }
86
+
87
+ private handleChannelMessage(message: Record<string, unknown>): void {
88
+ const { type } = message
89
+
90
+ switch (type) {
91
+ case 'connected':
92
+ this.sessionId = message.session_id as string
93
+ this.logger.info(`🎉 Bot connected! Session ID: ${this.sessionId}`)
94
+ break
95
+
96
+ case 'pong':
97
+ this.logger.debug('Received pong from server')
98
+ break
99
+
100
+ case 'status_response':
101
+ this.handleStatusResponse(message as unknown as BotStatus)
102
+ break
103
+
104
+ case 'command_received':
105
+ this.logger.info('Command received by server:', message.command)
106
+ break
107
+
108
+ case 'log':
109
+ this.logger.info(`[Server Log] ${message.level}: ${message.message}`)
110
+ break
111
+
112
+ default:
113
+ this.logger.warn('Unknown message type:', type)
114
+ }
115
+ }
116
+
117
+ private handleStatusResponse(status: BotStatus): void {
118
+ this.logger.info('Bot status:', {
119
+ online: status.online,
120
+ status: status.status,
121
+ sessions: status.active_sessions,
122
+ uptime: `${Math.floor(status.uptime / 60)}m`,
123
+ })
124
+ }
125
+
126
+ private handleError(error: Error): void {
127
+ this.logger.error('WebSocket error:', error.message)
128
+ }
129
+
130
+ private handleClose(): void {
131
+ this.logger.warn('WebSocket connection closed')
132
+ this.connected = false
133
+ this.stopHeartbeat()
134
+ this.scheduleReconnect()
135
+ }
136
+
137
+ private scheduleReconnect(): void {
138
+ if (this.reconnectTimer) {
139
+ return
140
+ }
141
+
142
+ const interval = this.config.reconnectInterval || 5000
143
+ this.logger.info(`Reconnecting in ${interval / 1000}s...`)
144
+
145
+ this.reconnectTimer = setTimeout(() => {
146
+ this.reconnectTimer = null
147
+ this.connect()
148
+ }, interval)
149
+ }
150
+
151
+ private startHeartbeat(): void {
152
+ const interval = this.config.heartbeatInterval || 30000
153
+
154
+ this.heartbeatTimer = setInterval(() => {
155
+ this.sendChannelMessage('ping', {})
156
+ }, interval)
157
+ }
158
+
159
+ private stopHeartbeat(): void {
160
+ if (this.heartbeatTimer) {
161
+ clearInterval(this.heartbeatTimer)
162
+ this.heartbeatTimer = null
163
+ }
164
+ }
165
+
166
+ private send(message: Record<string, unknown>): void {
167
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
168
+ this.logger.error('Cannot send message: WebSocket not connected')
169
+ return
170
+ }
171
+
172
+ this.ws.send(JSON.stringify(message))
173
+ }
174
+
175
+ private sendChannelMessage(action: string, data: Record<string, unknown>): void {
176
+ const message = {
177
+ command: 'message',
178
+ identifier: JSON.stringify({
179
+ channel: 'BotChannel',
180
+ }),
181
+ data: JSON.stringify({
182
+ action,
183
+ ...data,
184
+ }),
185
+ }
186
+
187
+ this.send(message)
188
+ }
189
+
190
+ sendLog(level: string, message: string): void {
191
+ this.sendChannelMessage('log', {
192
+ level,
193
+ message,
194
+ timestamp: new Date().toISOString(),
195
+ })
196
+ }
197
+
198
+ sendCommandResult(command: string, result: unknown, error?: string): void {
199
+ this.sendChannelMessage('command_result', {
200
+ command,
201
+ result,
202
+ error,
203
+ timestamp: new Date().toISOString(),
204
+ })
205
+ }
206
+
207
+ requestStatus(): void {
208
+ this.sendChannelMessage('status', {})
209
+ }
210
+
211
+ disconnect(): void {
212
+ this.logger.info('Disconnecting from EyeClaw...')
213
+ this.connected = false
214
+ this.stopHeartbeat()
215
+
216
+ if (this.reconnectTimer) {
217
+ clearTimeout(this.reconnectTimer)
218
+ this.reconnectTimer = null
219
+ }
220
+
221
+ if (this.ws) {
222
+ this.ws.close()
223
+ this.ws = null
224
+ }
225
+ }
226
+ }
package/src/types.ts ADDED
@@ -0,0 +1,41 @@
1
+ // Type definitions for OpenClaw plugin system
2
+ // Reference: clawdbot-feishu architecture
3
+
4
+ export interface PluginConfig {
5
+ enabled: boolean
6
+ botId: string
7
+ sdkToken: string
8
+ serverUrl: string
9
+ reconnectInterval?: number
10
+ heartbeatInterval?: number
11
+ }
12
+
13
+ export interface OpenClawContext {
14
+ config: PluginConfig
15
+ logger: Logger
16
+ emit: (event: string, data: unknown) => void
17
+ on: (event: string, handler: (data: unknown) => void) => void
18
+ }
19
+
20
+ export interface Logger {
21
+ info: (message: string) => void
22
+ warn: (message: string) => void
23
+ error: (message: string) => void
24
+ debug: (message: string) => void
25
+ }
26
+
27
+ export interface ChannelMessage {
28
+ type: string
29
+ content?: string
30
+ role?: 'user' | 'assistant'
31
+ timestamp?: string
32
+ metadata?: Record<string, unknown>
33
+ }
34
+
35
+ export interface BotStatus {
36
+ online: boolean
37
+ status: string
38
+ active_sessions: number
39
+ total_sessions: number
40
+ uptime: number
41
+ }