@rowger_go/chatu 0.1.3

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 (77) hide show
  1. package/.github/workflows/ci.yml +30 -0
  2. package/.github/workflows/publish.yml +55 -0
  3. package/INSTALL.md +285 -0
  4. package/INSTALL.zh.md +285 -0
  5. package/LICENSE +21 -0
  6. package/README.md +293 -0
  7. package/README.zh.md +293 -0
  8. package/dist/index.d.ts +96 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +1381 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/index.test.d.ts +5 -0
  13. package/dist/index.test.d.ts.map +1 -0
  14. package/dist/index.test.js +334 -0
  15. package/dist/index.test.js.map +1 -0
  16. package/dist/sdk/adapters/cache.d.ts +94 -0
  17. package/dist/sdk/adapters/cache.d.ts.map +1 -0
  18. package/dist/sdk/adapters/cache.js +158 -0
  19. package/dist/sdk/adapters/cache.js.map +1 -0
  20. package/dist/sdk/adapters/cache.test.d.ts +14 -0
  21. package/dist/sdk/adapters/cache.test.d.ts.map +1 -0
  22. package/dist/sdk/adapters/cache.test.js +178 -0
  23. package/dist/sdk/adapters/cache.test.js.map +1 -0
  24. package/dist/sdk/adapters/default.d.ts +24 -0
  25. package/dist/sdk/adapters/default.d.ts.map +1 -0
  26. package/dist/sdk/adapters/default.js +151 -0
  27. package/dist/sdk/adapters/default.js.map +1 -0
  28. package/dist/sdk/adapters/webhub.d.ts +336 -0
  29. package/dist/sdk/adapters/webhub.d.ts.map +1 -0
  30. package/dist/sdk/adapters/webhub.js +663 -0
  31. package/dist/sdk/adapters/webhub.js.map +1 -0
  32. package/dist/sdk/adapters/websocket.d.ts +133 -0
  33. package/dist/sdk/adapters/websocket.d.ts.map +1 -0
  34. package/dist/sdk/adapters/websocket.js +314 -0
  35. package/dist/sdk/adapters/websocket.js.map +1 -0
  36. package/dist/sdk/core/channel.d.ts +104 -0
  37. package/dist/sdk/core/channel.d.ts.map +1 -0
  38. package/dist/sdk/core/channel.js +158 -0
  39. package/dist/sdk/core/channel.js.map +1 -0
  40. package/dist/sdk/index.d.ts +27 -0
  41. package/dist/sdk/index.d.ts.map +1 -0
  42. package/dist/sdk/index.js +33 -0
  43. package/dist/sdk/index.js.map +1 -0
  44. package/dist/sdk/types/adapters.d.ts +128 -0
  45. package/dist/sdk/types/adapters.d.ts.map +1 -0
  46. package/dist/sdk/types/adapters.js +10 -0
  47. package/dist/sdk/types/adapters.js.map +1 -0
  48. package/dist/sdk/types/channel.d.ts +270 -0
  49. package/dist/sdk/types/channel.d.ts.map +1 -0
  50. package/dist/sdk/types/channel.js +36 -0
  51. package/dist/sdk/types/channel.js.map +1 -0
  52. package/docs/channel/01-overview.md +117 -0
  53. package/docs/channel/02-configuration.md +138 -0
  54. package/docs/channel/03-capabilities.md +86 -0
  55. package/docs/channel/04-api-reference.md +394 -0
  56. package/docs/channel/05-message-protocol.md +194 -0
  57. package/docs/channel/06-security.md +83 -0
  58. package/docs/channel/README.md +30 -0
  59. package/docs/sdk/README.md +13 -0
  60. package/docs/sdk/v2026.1.29-v2026.2.19.md +630 -0
  61. package/jest.config.js +19 -0
  62. package/openclaw.plugin.json +113 -0
  63. package/package.json +74 -0
  64. package/run-poll.mjs +209 -0
  65. package/scripts/reload-plugin.sh +78 -0
  66. package/src/index.test.ts +432 -0
  67. package/src/index.ts +1638 -0
  68. package/src/sdk/adapters/cache.test.ts +205 -0
  69. package/src/sdk/adapters/cache.ts +193 -0
  70. package/src/sdk/adapters/default.ts +196 -0
  71. package/src/sdk/adapters/webhub.ts +857 -0
  72. package/src/sdk/adapters/websocket.ts +378 -0
  73. package/src/sdk/core/channel.ts +230 -0
  74. package/src/sdk/index.ts +36 -0
  75. package/src/sdk/types/adapters.ts +169 -0
  76. package/src/sdk/types/channel.ts +346 -0
  77. package/tsconfig.json +31 -0
@@ -0,0 +1,113 @@
1
+ {
2
+ "id": "chatu",
3
+ "name": "Chatu",
4
+ "version": "0.1.0",
5
+ "description": "Connect OpenClaw to any Website via HTTP/WebSocket",
6
+ "kind": "channel",
7
+ "channels": [
8
+ "chatu"
9
+ ],
10
+ "configSchema": {
11
+ "type": "object",
12
+ "additionalProperties": false,
13
+ "properties": {
14
+ "enabled": {
15
+ "type": "boolean",
16
+ "default": true,
17
+ "description": "Enable or disable the Chatu channel"
18
+ },
19
+ "apiUrl": {
20
+ "type": "string",
21
+ "description": "Default API base URL for Chatu service"
22
+ },
23
+ "channelId": {
24
+ "type": "string",
25
+ "description": "Channel ID from the Chatu service"
26
+ },
27
+ "secret": {
28
+ "type": "string",
29
+ "description": "Channel secret for registration (wh_secret_xxx)"
30
+ },
31
+ "accessToken": {
32
+ "type": "string",
33
+ "description": "Access token (auto-obtained via register, or set manually)"
34
+ },
35
+ "timeout": {
36
+ "type": "number",
37
+ "default": 30000,
38
+ "description": "Request timeout in milliseconds"
39
+ },
40
+ "accounts": {
41
+ "type": "object",
42
+ "additionalProperties": {
43
+ "type": "object",
44
+ "properties": {
45
+ "accountId": {
46
+ "type": "string",
47
+ "description": "Unique account identifier"
48
+ },
49
+ "apiUrl": {
50
+ "type": "string",
51
+ "description": "Account-specific API URL (overrides default)"
52
+ },
53
+ "channelId": {
54
+ "type": "string",
55
+ "description": "Account-specific Channel ID (overrides default)"
56
+ },
57
+ "secret": {
58
+ "type": "string",
59
+ "description": "Account-specific channel secret (overrides default)"
60
+ },
61
+ "accessToken": {
62
+ "type": "string",
63
+ "description": "Account-specific access token (overrides default)"
64
+ },
65
+ "timeout": {
66
+ "type": "number",
67
+ "description": "Account-specific request timeout in milliseconds"
68
+ }
69
+ },
70
+ "required": ["accountId"]
71
+ },
72
+ "description": "Multiple account configurations"
73
+ }
74
+ }
75
+ },
76
+ "uiHints": {
77
+ "enabled": {
78
+ "label": "Enabled",
79
+ "description": "Enable Chatu channel"
80
+ },
81
+ "apiUrl": {
82
+ "label": "API URL",
83
+ "placeholder": "https://your-webhub.example.com",
84
+ "description": "WebHub service base URL"
85
+ },
86
+ "channelId": {
87
+ "label": "Channel ID",
88
+ "placeholder": "wh_ch_xxxxxx",
89
+ "description": "Channel ID from the WebHub admin panel"
90
+ },
91
+ "secret": {
92
+ "label": "Channel Secret",
93
+ "sensitive": true,
94
+ "placeholder": "wh_secret_xxxxxxxxxx",
95
+ "description": "Channel secret for registration"
96
+ },
97
+ "accessToken": {
98
+ "label": "Access Token",
99
+ "sensitive": true,
100
+ "placeholder": "wh_xxxxxxxxxxxxxxxx",
101
+ "description": "Access token for API authentication"
102
+ },
103
+ "timeout": {
104
+ "label": "Timeout (ms)",
105
+ "placeholder": "30000",
106
+ "description": "Request timeout in milliseconds"
107
+ },
108
+ "accounts": {
109
+ "label": "Accounts",
110
+ "description": "Configure multiple Chatu accounts"
111
+ }
112
+ }
113
+ }
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@rowger_go/chatu",
3
+ "version": "0.1.3",
4
+ "description": "OpenClaw Channel SDK - Build custom channel plugins",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "openclaw": {
8
+ "extensions": [
9
+ "./dist/index.js"
10
+ ],
11
+ "channel": {
12
+ "id": "chatu",
13
+ "label": "Chatu",
14
+ "selectionLabel": "Chatu (HTTP/WebSocket)",
15
+ "docsPath": "/channels/chatu",
16
+ "docsLabel": "chatu",
17
+ "blurb": "Connect to any website via HTTP/WebSocket",
18
+ "order": 60,
19
+ "aliases": [
20
+ "chatu",
21
+ "http-channel"
22
+ ]
23
+ },
24
+ "install": {
25
+ "npmSpec": "@rowger_go/chatu",
26
+ "defaultChoice": "npm"
27
+ }
28
+ },
29
+ "scripts": {
30
+ "build": "tsc",
31
+ "watch": "tsc --watch",
32
+ "check": "tsc --noEmit",
33
+ "lint": "eslint src --ext .ts",
34
+ "prepare": "npm run build",
35
+ "test": "jest --no-coverage",
36
+ "release:patch": "npm version patch && git push --follow-tags",
37
+ "release:minor": "npm version minor && git push --follow-tags",
38
+ "release:major": "npm version major && git push --follow-tags"
39
+ },
40
+ "keywords": [
41
+ "openclaw",
42
+ "channel",
43
+ "sdk"
44
+ ],
45
+ "author": "OpenClaw Team",
46
+ "license": "MIT",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "https://github.com/chatu-ai/webhub.git"
50
+ },
51
+ "bugs": {
52
+ "url": "https://github.com/chatu-ai/webhub/issues"
53
+ },
54
+ "homepage": "https://github.com/chatu-ai/webhub",
55
+ "engines": {
56
+ "node": ">=18.0.0"
57
+ },
58
+ "dependencies": {
59
+ "@sinclair/typebox": "^0.32.0",
60
+ "@types/uuid": "^10.0.0",
61
+ "uuid": "^13.0.0",
62
+ "ws": "^8.14.0",
63
+ "zod": "^3.22.0"
64
+ },
65
+ "devDependencies": {
66
+ "@types/jest": "^30.0.0",
67
+ "@types/node": "^20.0.0",
68
+ "@types/ws": "^8.5.0",
69
+ "jest": "^30.2.0",
70
+ "openclaw": "2026.2.19-2",
71
+ "ts-jest": "^29.4.6",
72
+ "typescript": "^5.3.0"
73
+ }
74
+ }
package/run-poll.mjs ADDED
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Standalone poll loop for chatu channel.
3
+ * Runs when openclaw gateway doesn't auto-start the account.
4
+ *
5
+ * Usage: node run-poll.mjs
6
+ * API_URL=http://localhost:3000 CHANNEL_ID=xxx ACCESS_TOKEN=yyy node run-poll.mjs
7
+ */
8
+ import { readFileSync } from 'fs';
9
+ import { homedir } from 'os';
10
+ import { join } from 'path';
11
+
12
+ // --- Read config from ~/.openclaw/openclaw.json ---
13
+ let cfg;
14
+ try {
15
+ cfg = JSON.parse(readFileSync(join(homedir(), '.openclaw', 'openclaw.json'), 'utf8'));
16
+ } catch {
17
+ cfg = {};
18
+ }
19
+ const chatuCfg = cfg?.channels?.chatu ?? {};
20
+
21
+ const API_URL = process.env.API_URL || chatuCfg.apiUrl || 'http://localhost:3000';
22
+ const CHANNEL_ID = process.env.CHANNEL_ID || chatuCfg.channelId || '';
23
+ const ACCESS_TOKEN = process.env.ACCESS_TOKEN || chatuCfg.accessToken || '';
24
+
25
+ if (!CHANNEL_ID || !ACCESS_TOKEN) {
26
+ console.error('[poll] Missing CHANNEL_ID or ACCESS_TOKEN. Check ~/.openclaw/openclaw.json');
27
+ process.exit(1);
28
+ }
29
+
30
+ console.log(`[poll] Starting poll loop`);
31
+ console.log(`[poll] API_URL : ${API_URL}`);
32
+ console.log(`[poll] CHANNEL_ID : ${CHANNEL_ID}`);
33
+
34
+ // --- Load compiled plugin and dispatch via openclaw runtime API ---
35
+ // We'll use a lightweight inline dispatcher instead of the full openclaw api:
36
+ // Just forward messages to the gateway via the openclaw CLI `chat` command.
37
+
38
+ const POLL_INTERVAL_MS = 2000;
39
+ const MAX_BACKOFF_MS = 30_000;
40
+
41
+ let lastCursor = '';
42
+ let consecutiveErrors = 0;
43
+ const MAX_ERRORS = 10;
44
+
45
+ async function timedFetch(url, init, timeoutMs = 30000) {
46
+ const ctrl = new AbortController();
47
+ const id = setTimeout(() => ctrl.abort(), timeoutMs);
48
+ try {
49
+ return await fetch(url, { ...init, signal: ctrl.signal });
50
+ } finally {
51
+ clearTimeout(id);
52
+ }
53
+ }
54
+
55
+ async function ackMessage(msgId) {
56
+ try {
57
+ await timedFetch(
58
+ `${API_URL}/api/channel/messages/${msgId}/ack`,
59
+ {
60
+ method: 'POST',
61
+ headers: { 'Content-Type': 'application/json', 'X-Channel-Token': ACCESS_TOKEN },
62
+ body: JSON.stringify({ channelId: CHANNEL_ID }),
63
+ },
64
+ 10000,
65
+ );
66
+ } catch (e) {
67
+ console.warn(`[poll] ack failed for ${msgId}: ${e.message}`);
68
+ }
69
+ }
70
+
71
+ async function dispatchToOpenClaw(msg) {
72
+ const content = msg.content || msg.text || '';
73
+ const sender = msg.senderName || msg.senderId || 'user';
74
+ if (!content) return;
75
+
76
+ console.log(`[poll] Dispatching to openclaw: "${content}" from ${sender}`);
77
+
78
+ // Use openclaw CLI to send the message and get AI reply
79
+ const { spawn } = await import('child_process');
80
+ return new Promise((resolve) => {
81
+ const proc = spawn('openclaw', [
82
+ 'chat',
83
+ '--channel', 'chatu',
84
+ '--account', 'default',
85
+ '--message', content,
86
+ '--sender', CHANNEL_ID,
87
+ '--json',
88
+ ], { stdio: ['ignore', 'pipe', 'pipe'] });
89
+
90
+ let stdout = '';
91
+ let stderr = '';
92
+ proc.stdout.on('data', (d) => { stdout += d.toString(); });
93
+ proc.stderr.on('data', (d) => { stderr += d.toString(); });
94
+ proc.on('close', async (code) => {
95
+ if (code !== 0) {
96
+ console.error(`[poll] openclaw chat failed (code=${code}): ${stderr.slice(0, 200)}`);
97
+ resolve();
98
+ return;
99
+ }
100
+
101
+ // Parse JSON replies and deliver back to service
102
+ try {
103
+ const lines = stdout.trim().split('\n').filter(Boolean);
104
+ for (const line of lines) {
105
+ try {
106
+ const parsed = JSON.parse(line);
107
+ const reply = parsed?.reply || parsed?.text || parsed?.content || (typeof parsed === 'string' ? parsed : null);
108
+ if (reply) {
109
+ await deliverReply(reply, msg);
110
+ }
111
+ } catch { /* skip non-json lines */ }
112
+ }
113
+ // If no JSON, treat whole stdout as reply
114
+ if (stdout.trim() && !lines.some(l => { try { JSON.parse(l); return true; } catch { return false; } })) {
115
+ await deliverReply(stdout.trim(), msg);
116
+ }
117
+ } catch (e) {
118
+ console.error(`[poll] failed to parse reply: ${e.message}`);
119
+ }
120
+ resolve();
121
+ });
122
+ });
123
+ }
124
+
125
+ async function deliverReply(text, originalMsg) {
126
+ console.log(`[poll] Delivering reply: "${text.slice(0, 80)}..."`);
127
+ try {
128
+ const resp = await timedFetch(
129
+ `${API_URL}/api/channel/messages`,
130
+ {
131
+ method: 'POST',
132
+ headers: { 'Content-Type': 'application/json', 'X-Channel-Token': ACCESS_TOKEN },
133
+ body: JSON.stringify({
134
+ channelId: CHANNEL_ID,
135
+ content: text,
136
+ direction: 'inbound',
137
+ senderId: 'ai',
138
+ targetId: originalMsg.senderId || 'user',
139
+ metadata: {},
140
+ }),
141
+ },
142
+ 30000,
143
+ );
144
+ if (resp.ok) {
145
+ console.log('[poll] Reply delivered OK');
146
+ } else {
147
+ const body = await resp.text();
148
+ console.error(`[poll] Reply delivery failed: ${resp.status} ${body.slice(0, 100)}`);
149
+ }
150
+ } catch (e) {
151
+ console.error(`[poll] Reply delivery error: ${e.message}`);
152
+ }
153
+ }
154
+
155
+ // Main poll loop
156
+ async function pollLoop() {
157
+ console.log('[poll] Poll loop started');
158
+ while (true) {
159
+ const backoffMs = Math.min(POLL_INTERVAL_MS * Math.pow(2, consecutiveErrors), MAX_BACKOFF_MS);
160
+ await new Promise(r => setTimeout(r, backoffMs));
161
+
162
+ try {
163
+ const url =
164
+ `${API_URL}/api/channel/messages/pending` +
165
+ `?channelId=${encodeURIComponent(CHANNEL_ID)}` +
166
+ `&after=${encodeURIComponent(lastCursor)}`;
167
+
168
+ const resp = await timedFetch(url, {
169
+ method: 'GET',
170
+ headers: {
171
+ 'X-Channel-Token': ACCESS_TOKEN,
172
+ 'X-Channel-ID': 'default',
173
+ },
174
+ }, 15000);
175
+
176
+ if (!resp.ok) {
177
+ consecutiveErrors++;
178
+ console.warn(`[poll] HTTP ${resp.status} (errors=${consecutiveErrors})`);
179
+ continue;
180
+ }
181
+
182
+ consecutiveErrors = 0;
183
+ const data = await resp.json();
184
+ const messages = data?.data ?? [];
185
+
186
+ for (const msg of messages) {
187
+ lastCursor = msg.id ?? lastCursor;
188
+ await ackMessage(msg.id);
189
+
190
+ // Typing indicator
191
+ timedFetch(`${API_URL}/api/channel/typing`, {
192
+ method: 'POST',
193
+ headers: { 'Content-Type': 'application/json', 'X-Channel-Token': ACCESS_TOKEN },
194
+ body: JSON.stringify({ channelId: CHANNEL_ID }),
195
+ }, 3000).catch(() => {});
196
+
197
+ await dispatchToOpenClaw(msg);
198
+ }
199
+ } catch (err) {
200
+ consecutiveErrors++;
201
+ console.warn(`[poll] Error (errors=${consecutiveErrors}): ${err.message}`);
202
+ }
203
+ }
204
+ }
205
+
206
+ pollLoop().catch(err => {
207
+ console.error('[poll] Fatal:', err);
208
+ process.exit(1);
209
+ });
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================
3
+ # reload-plugin.sh — Build & reload the openclaw-web-hub-channel plugin
4
+ #
5
+ # Usage:
6
+ # ./scripts/reload-plugin.sh [--no-build]
7
+ #
8
+ # What it does:
9
+ # 1. Builds the plugin (unless --no-build is passed)
10
+ # 2. Prints the new plugin version
11
+ # 3. Guides you through clearing the cached accessToken so
12
+ # openclaw picks up the new build on next start
13
+ # ============================================================
14
+
15
+ set -euo pipefail
16
+
17
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
+ PLUGIN_ROOT="$(dirname "$SCRIPT_DIR")"
19
+
20
+ NO_BUILD=false
21
+ for arg in "$@"; do
22
+ [[ "$arg" == "--no-build" ]] && NO_BUILD=true
23
+ done
24
+
25
+ echo ""
26
+ echo "=== openclaw-web-hub-channel plugin reload ==="
27
+ echo ""
28
+
29
+ # ── Step 1: Build ────────────────────────────────────────────
30
+ if [[ "$NO_BUILD" == "false" ]]; then
31
+ echo "▶ Building plugin..."
32
+ cd "$PLUGIN_ROOT"
33
+ npm run build
34
+ echo "✓ Build complete."
35
+ else
36
+ echo "⚠ Skipping build (--no-build flag set)."
37
+ fi
38
+
39
+ # ── Step 2: Print version ────────────────────────────────────
40
+ PLUGIN_VERSION=$(node -e "console.log(require('$PLUGIN_ROOT/package.json').version)")
41
+ echo ""
42
+ echo "✓ Plugin version: $PLUGIN_VERSION"
43
+
44
+ # ── Step 3: Version detection via service API ─────────────────
45
+ WEBHUB_URL="${WEBHUB_URL:-http://localhost:3000}"
46
+ echo ""
47
+ echo "▶ Checking service version endpoint..."
48
+ if curl -sf "$WEBHUB_URL/api/channel/version" | grep -q "serviceVersion"; then
49
+ echo "✓ Service version response:"
50
+ curl -s "$WEBHUB_URL/api/channel/version" | python3 -m json.tool 2>/dev/null \
51
+ || curl -s "$WEBHUB_URL/api/channel/version"
52
+ else
53
+ echo "⚠ Service at $WEBHUB_URL is not reachable or version endpoint unavailable."
54
+ echo " Set WEBHUB_URL env var if the service runs on a different address."
55
+ fi
56
+
57
+ # ── Step 4: Reload instructions ──────────────────────────────
58
+ echo ""
59
+ echo "=== Manual reload steps ==="
60
+ echo ""
61
+ echo "To activate the new plugin version in openclaw:"
62
+ echo ""
63
+ echo " Option A — full restart (recommended):"
64
+ echo " 1. Stop openclaw"
65
+ echo " 2. Run this script (already done if you're reading this)"
66
+ echo " 3. Start openclaw"
67
+ echo ""
68
+ echo " Option B — clear cached accessToken so plugin re-registers:"
69
+ echo " 1. In openclaw config, remove the 'accessToken' field under"
70
+ echo " channels.chatu (or channels.chatu.accounts.<id>)"
71
+ echo " 2. Keep 'channelId' and 'secret' in place"
72
+ echo " 3. Restart openclaw — the plugin will exchange the secret for"
73
+ echo " a fresh token and report its new version on /api/channel/version"
74
+ echo ""
75
+ echo " After restart, verify version via:"
76
+ echo " curl $WEBHUB_URL/api/channel/version"
77
+ echo ""
78
+ echo "=== Done ==="