@openclaw/twitch 2026.5.1-beta.1 → 2026.5.2-beta.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.
@@ -3,13 +3,230 @@
3
3
  "activation": {
4
4
  "onStartup": false
5
5
  },
6
- "channels": ["twitch"],
6
+ "channels": [
7
+ "twitch"
8
+ ],
7
9
  "channelEnvVars": {
8
- "twitch": ["OPENCLAW_TWITCH_ACCESS_TOKEN"]
10
+ "twitch": [
11
+ "OPENCLAW_TWITCH_ACCESS_TOKEN"
12
+ ]
9
13
  },
10
14
  "configSchema": {
11
15
  "type": "object",
12
16
  "additionalProperties": false,
13
17
  "properties": {}
18
+ },
19
+ "channelConfigs": {
20
+ "twitch": {
21
+ "schema": {
22
+ "$schema": "http://json-schema.org/draft-07/schema#",
23
+ "anyOf": [
24
+ {
25
+ "type": "object",
26
+ "properties": {
27
+ "name": {
28
+ "type": "string"
29
+ },
30
+ "enabled": {
31
+ "type": "boolean"
32
+ },
33
+ "markdown": {
34
+ "type": "object",
35
+ "properties": {
36
+ "tables": {
37
+ "type": "string",
38
+ "enum": [
39
+ "off",
40
+ "bullets",
41
+ "code",
42
+ "block"
43
+ ]
44
+ }
45
+ },
46
+ "additionalProperties": false
47
+ },
48
+ "defaultAccount": {
49
+ "type": "string"
50
+ },
51
+ "username": {
52
+ "type": "string"
53
+ },
54
+ "accessToken": {
55
+ "type": "string"
56
+ },
57
+ "clientId": {
58
+ "type": "string"
59
+ },
60
+ "channel": {
61
+ "type": "string",
62
+ "minLength": 1
63
+ },
64
+ "allowFrom": {
65
+ "type": "array",
66
+ "items": {
67
+ "type": "string"
68
+ }
69
+ },
70
+ "allowedRoles": {
71
+ "type": "array",
72
+ "items": {
73
+ "type": "string",
74
+ "enum": [
75
+ "moderator",
76
+ "owner",
77
+ "vip",
78
+ "subscriber",
79
+ "all"
80
+ ]
81
+ }
82
+ },
83
+ "requireMention": {
84
+ "type": "boolean"
85
+ },
86
+ "responsePrefix": {
87
+ "type": "string"
88
+ },
89
+ "clientSecret": {
90
+ "type": "string"
91
+ },
92
+ "refreshToken": {
93
+ "type": "string"
94
+ },
95
+ "expiresIn": {
96
+ "anyOf": [
97
+ {
98
+ "type": "number"
99
+ },
100
+ {
101
+ "type": "null"
102
+ }
103
+ ]
104
+ },
105
+ "obtainmentTimestamp": {
106
+ "type": "number"
107
+ }
108
+ },
109
+ "required": [
110
+ "username",
111
+ "accessToken",
112
+ "channel"
113
+ ],
114
+ "additionalProperties": false
115
+ },
116
+ {
117
+ "type": "object",
118
+ "properties": {
119
+ "name": {
120
+ "type": "string"
121
+ },
122
+ "enabled": {
123
+ "type": "boolean"
124
+ },
125
+ "markdown": {
126
+ "type": "object",
127
+ "properties": {
128
+ "tables": {
129
+ "type": "string",
130
+ "enum": [
131
+ "off",
132
+ "bullets",
133
+ "code",
134
+ "block"
135
+ ]
136
+ }
137
+ },
138
+ "additionalProperties": false
139
+ },
140
+ "defaultAccount": {
141
+ "type": "string"
142
+ },
143
+ "accounts": {
144
+ "type": "object",
145
+ "propertyNames": {
146
+ "type": "string"
147
+ },
148
+ "additionalProperties": {
149
+ "type": "object",
150
+ "properties": {
151
+ "username": {
152
+ "type": "string"
153
+ },
154
+ "accessToken": {
155
+ "type": "string"
156
+ },
157
+ "clientId": {
158
+ "type": "string"
159
+ },
160
+ "channel": {
161
+ "type": "string",
162
+ "minLength": 1
163
+ },
164
+ "enabled": {
165
+ "type": "boolean"
166
+ },
167
+ "allowFrom": {
168
+ "type": "array",
169
+ "items": {
170
+ "type": "string"
171
+ }
172
+ },
173
+ "allowedRoles": {
174
+ "type": "array",
175
+ "items": {
176
+ "type": "string",
177
+ "enum": [
178
+ "moderator",
179
+ "owner",
180
+ "vip",
181
+ "subscriber",
182
+ "all"
183
+ ]
184
+ }
185
+ },
186
+ "requireMention": {
187
+ "type": "boolean"
188
+ },
189
+ "responsePrefix": {
190
+ "type": "string"
191
+ },
192
+ "clientSecret": {
193
+ "type": "string"
194
+ },
195
+ "refreshToken": {
196
+ "type": "string"
197
+ },
198
+ "expiresIn": {
199
+ "anyOf": [
200
+ {
201
+ "type": "number"
202
+ },
203
+ {
204
+ "type": "null"
205
+ }
206
+ ]
207
+ },
208
+ "obtainmentTimestamp": {
209
+ "type": "number"
210
+ }
211
+ },
212
+ "required": [
213
+ "username",
214
+ "accessToken",
215
+ "channel"
216
+ ],
217
+ "additionalProperties": false
218
+ }
219
+ }
220
+ },
221
+ "required": [
222
+ "accounts"
223
+ ],
224
+ "additionalProperties": false
225
+ }
226
+ ]
227
+ },
228
+ "label": "Twitch",
229
+ "description": "Twitch chat integration"
230
+ }
14
231
  }
15
232
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/twitch",
3
- "version": "2026.5.1-beta.1",
3
+ "version": "2026.5.2-beta.1",
4
4
  "description": "OpenClaw Twitch channel plugin",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,6 +25,12 @@
25
25
  "defaultChoice": "npm",
26
26
  "minHostVersion": ">=2026.4.10"
27
27
  },
28
+ "compat": {
29
+ "pluginApi": ">=2026.5.2-beta.1"
30
+ },
31
+ "build": {
32
+ "openclawVersion": "2026.5.2-beta.1"
33
+ },
28
34
  "channel": {
29
35
  "id": "twitch",
30
36
  "label": "Twitch",
@@ -36,6 +42,7 @@
36
42
  ]
37
43
  },
38
44
  "release": {
45
+ "publishToClawHub": true,
39
46
  "publishToNpm": true
40
47
  }
41
48
  }
@@ -0,0 +1,46 @@
1
+ import AjvPkg from "ajv";
2
+ import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
3
+ import { describe, expect, it } from "vitest";
4
+ import { TwitchConfigSchema } from "./config-schema.js";
5
+
6
+ function validateTwitchConfig(value: unknown): boolean {
7
+ const Ajv = AjvPkg as unknown as new (opts?: object) => import("ajv").default;
8
+ const schema = buildChannelConfigSchema(TwitchConfigSchema).schema;
9
+ const validate = new Ajv({ allErrors: true, strict: false }).compile(schema);
10
+ const ok = validate(value);
11
+ if (!ok) {
12
+ throw new Error(`expected valid Twitch config: ${JSON.stringify(validate.errors)}`);
13
+ }
14
+ return true;
15
+ }
16
+
17
+ describe("TwitchConfigSchema JSON schema", () => {
18
+ it("accepts single-account channel config with base fields", () => {
19
+ expect(
20
+ validateTwitchConfig({
21
+ enabled: false,
22
+ username: "openclaw",
23
+ accessToken: "oauth:test",
24
+ clientId: "test-client-id",
25
+ channel: "openclaw-test",
26
+ }),
27
+ ).toBe(true);
28
+ });
29
+
30
+ it("accepts multi-account channel config with defaultAccount", () => {
31
+ expect(
32
+ validateTwitchConfig({
33
+ enabled: true,
34
+ defaultAccount: "stream",
35
+ accounts: {
36
+ stream: {
37
+ username: "openclaw",
38
+ accessToken: "oauth:test",
39
+ clientId: "test-client-id",
40
+ channel: "openclaw-test",
41
+ },
42
+ },
43
+ }),
44
+ ).toBe(true);
45
+ });
46
+ });
@@ -6,10 +6,7 @@ import { z } from "openclaw/plugin-sdk/zod";
6
6
  */
7
7
  const TwitchRoleSchema = z.enum(["moderator", "owner", "vip", "subscriber", "all"]);
8
8
 
9
- /**
10
- * Twitch account configuration schema
11
- */
12
- const TwitchAccountSchema = z.object({
9
+ const TwitchAccountShape = {
13
10
  /** Twitch username */
14
11
  username: z.string(),
15
12
  /** Twitch OAuth access token (requires chat:read and chat:write scopes) */
@@ -36,16 +33,22 @@ const TwitchAccountSchema = z.object({
36
33
  expiresIn: z.number().nullable().optional(),
37
34
  /** Timestamp when token was obtained (optional, for token refresh tracking) */
38
35
  obtainmentTimestamp: z.number().optional(),
39
- });
36
+ };
37
+
38
+ /**
39
+ * Twitch account configuration schema
40
+ */
41
+ const TwitchAccountSchema = z.object(TwitchAccountShape);
40
42
 
41
43
  /**
42
44
  * Base configuration properties shared by both single and multi-account modes
43
45
  */
44
- const TwitchConfigBaseSchema = z.object({
46
+ const TwitchConfigBaseShape = {
45
47
  name: z.string().optional(),
46
48
  enabled: z.boolean().optional(),
47
49
  markdown: MarkdownConfigSchema.optional(),
48
- });
50
+ defaultAccount: z.string().optional(),
51
+ };
49
52
 
50
53
  /**
51
54
  * Simplified single-account configuration schema
@@ -53,24 +56,25 @@ const TwitchConfigBaseSchema = z.object({
53
56
  * Use this for single-account setups. Properties are at the top level,
54
57
  * creating an implicit "default" account.
55
58
  */
56
- const SimplifiedSchema = z.intersection(TwitchConfigBaseSchema, TwitchAccountSchema);
59
+ const SimplifiedSchema = z.object({
60
+ ...TwitchConfigBaseShape,
61
+ ...TwitchAccountShape,
62
+ });
57
63
 
58
64
  /**
59
65
  * Multi-account configuration schema
60
66
  *
61
67
  * Use this for multi-account setups. Each key is an account ID (e.g., "default", "secondary").
62
68
  */
63
- const MultiAccountSchema = z.intersection(
64
- TwitchConfigBaseSchema,
65
- z
66
- .object({
67
- /** Per-account configuration (for multi-account setups) */
68
- accounts: z.record(z.string(), TwitchAccountSchema),
69
- })
70
- .refine((val) => Object.keys(val.accounts || {}).length > 0, {
71
- message: "accounts must contain at least one entry",
72
- }),
73
- );
69
+ const MultiAccountSchema = z
70
+ .object({
71
+ ...TwitchConfigBaseShape,
72
+ /** Per-account configuration (for multi-account setups) */
73
+ accounts: z.record(z.string(), TwitchAccountSchema),
74
+ })
75
+ .refine((val) => Object.keys(val.accounts || {}).length > 0, {
76
+ message: "accounts must contain at least one entry",
77
+ });
74
78
 
75
79
  /**
76
80
  * Twitch plugin configuration schema