@kodelyth/twitch 2026.5.39 → 2026.6.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.
@@ -0,0 +1,8 @@
1
+ import { createPluginRuntimeStore } from "klaw/plugin-sdk/runtime-store";
2
+ //#region extensions/twitch/src/runtime.ts
3
+ const { setRuntime: setTwitchRuntime, getRuntime: getTwitchRuntime } = createPluginRuntimeStore({
4
+ pluginId: "twitch",
5
+ errorMessage: "Twitch runtime not initialized"
6
+ });
7
+ //#endregion
8
+ export { setTwitchRuntime as n, getTwitchRuntime as t };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ import { defineBundledChannelSetupEntry } from "klaw/plugin-sdk/channel-entry-contract";
2
+ //#region extensions/twitch/setup-entry.ts
3
+ var setup_entry_default = defineBundledChannelSetupEntry({
4
+ importMetaUrl: import.meta.url,
5
+ plugin: {
6
+ specifier: "./setup-plugin-api.js",
7
+ exportName: "twitchSetupPlugin"
8
+ }
9
+ });
10
+ //#endregion
11
+ export { setup_entry_default as default };
@@ -0,0 +1,2 @@
1
+ import { n as twitchSetupPlugin } from "./setup-surface-CovnRl9R.js";
2
+ export { twitchSetupPlugin };
@@ -0,0 +1,527 @@
1
+ import { DEFAULT_ACCOUNT_ID, listCombinedAccountIds, normalizeAccountId, resolveNormalizedAccountEntry } from "klaw/plugin-sdk/account-resolution";
2
+ import { randomUUID } from "node:crypto";
3
+ import { normalizeLowercaseStringOrEmpty } from "klaw/plugin-sdk/string-coerce-runtime";
4
+ import { normalizeOptionalAccountId } from "klaw/plugin-sdk/account-id";
5
+ import { getChatChannelMeta } from "klaw/plugin-sdk/core";
6
+ import { createSetupTranslator, formatDocsLink, normalizeAccountId as normalizeAccountId$1 } from "klaw/plugin-sdk/setup";
7
+ //#region extensions/twitch/src/token.ts
8
+ /**
9
+ * Twitch access token resolution with environment variable support.
10
+ *
11
+ * Supports reading Twitch OAuth access tokens from config or environment variable.
12
+ * The KLAW_TWITCH_ACCESS_TOKEN env var is only used for the default account.
13
+ *
14
+ * Token resolution priority:
15
+ * 1. Account access token from merged config (accounts.{id} or base-level for default)
16
+ * 2. Environment variable: KLAW_TWITCH_ACCESS_TOKEN (default account only)
17
+ */
18
+ /**
19
+ * Normalize a Twitch OAuth token - ensure it has the oauth: prefix
20
+ */
21
+ function normalizeTwitchToken(raw) {
22
+ if (!raw) return;
23
+ const trimmed = raw.trim();
24
+ if (!trimmed) return;
25
+ return trimmed.startsWith("oauth:") ? trimmed : `oauth:${trimmed}`;
26
+ }
27
+ /**
28
+ * Resolve Twitch access token from config or environment variable.
29
+ *
30
+ * Priority:
31
+ * 1. Account access token (from merged config - base-level for default, or accounts.{accountId})
32
+ * 2. Environment variable: KLAW_TWITCH_ACCESS_TOKEN (default account only)
33
+ *
34
+ * The getAccountConfig function handles merging base-level config with accounts.default,
35
+ * so this logic works for both simplified and multi-account patterns.
36
+ *
37
+ * @param cfg - Klaw config
38
+ * @param opts - Options including accountId and optional envToken override
39
+ * @returns Token resolution with source
40
+ */
41
+ function resolveTwitchToken(cfg, opts = {}) {
42
+ const accountId = normalizeAccountId(opts.accountId);
43
+ const twitchCfg = cfg?.channels?.twitch;
44
+ const accounts = twitchCfg?.accounts;
45
+ const accountCfg = resolveNormalizedAccountEntry(accounts, accountId, normalizeAccountId);
46
+ let token;
47
+ if (accountId === DEFAULT_ACCOUNT_ID) token = normalizeTwitchToken((typeof twitchCfg?.accessToken === "string" ? twitchCfg.accessToken : void 0) || accountCfg?.accessToken);
48
+ else token = normalizeTwitchToken(accountCfg?.accessToken);
49
+ if (token) return {
50
+ token,
51
+ source: "config"
52
+ };
53
+ const envToken = accountId === DEFAULT_ACCOUNT_ID ? normalizeTwitchToken(opts.envToken ?? process.env.KLAW_TWITCH_ACCESS_TOKEN) : void 0;
54
+ if (envToken) return {
55
+ token: envToken,
56
+ source: "env"
57
+ };
58
+ return {
59
+ token: "",
60
+ source: "none"
61
+ };
62
+ }
63
+ //#endregion
64
+ //#region extensions/twitch/src/utils/twitch.ts
65
+ /**
66
+ * Twitch-specific utility functions
67
+ */
68
+ /**
69
+ * Normalize Twitch channel names.
70
+ *
71
+ * Removes the '#' prefix if present, converts to lowercase, and trims whitespace.
72
+ * Twitch channel names are case-insensitive and don't use the '#' prefix in the API.
73
+ *
74
+ * @param channel - The channel name to normalize
75
+ * @returns Normalized channel name
76
+ *
77
+ * @example
78
+ * normalizeTwitchChannel("#TwitchChannel") // "twitchchannel"
79
+ * normalizeTwitchChannel("MyChannel") // "mychannel"
80
+ */
81
+ function normalizeTwitchChannel(channel) {
82
+ const trimmed = normalizeLowercaseStringOrEmpty(channel);
83
+ return trimmed.startsWith("#") ? trimmed.slice(1) : trimmed;
84
+ }
85
+ /**
86
+ * Create a standardized error message for missing target.
87
+ *
88
+ * @param provider - The provider name (e.g., "Twitch")
89
+ * @param hint - Optional hint for how to fix the issue
90
+ * @returns Error object with descriptive message
91
+ */
92
+ function missingTargetError(provider, hint) {
93
+ return /* @__PURE__ */ new Error(`Delivering to ${provider} requires target${hint ? ` ${hint}` : ""}`);
94
+ }
95
+ /**
96
+ * Generate a unique message ID for Twitch messages.
97
+ *
98
+ * Twurple's say() doesn't return the message ID, so we generate one
99
+ * for tracking purposes.
100
+ *
101
+ * @returns A unique message ID
102
+ */
103
+ function generateMessageId() {
104
+ return `${Date.now()}-${randomUUID()}`;
105
+ }
106
+ /**
107
+ * Normalize OAuth token by removing the "oauth:" prefix if present.
108
+ *
109
+ * Twurple doesn't require the "oauth:" prefix, so we strip it for consistency.
110
+ *
111
+ * @param token - The OAuth token to normalize
112
+ * @returns Normalized token without "oauth:" prefix
113
+ *
114
+ * @example
115
+ * normalizeToken("oauth:abc123") // "abc123"
116
+ * normalizeToken("abc123") // "abc123"
117
+ */
118
+ function normalizeToken(token) {
119
+ return token.startsWith("oauth:") ? token.slice(6) : token;
120
+ }
121
+ /**
122
+ * Check if an account is properly configured with required credentials.
123
+ *
124
+ * @param account - The Twitch account config to check
125
+ * @returns true if the account has required credentials
126
+ */
127
+ function isAccountConfigured(account, resolvedToken) {
128
+ const token = resolvedToken ?? account?.accessToken;
129
+ return Boolean(account?.username && token && account?.clientId);
130
+ }
131
+ //#endregion
132
+ //#region extensions/twitch/src/config.ts
133
+ /**
134
+ * Default account ID for Twitch
135
+ */
136
+ const DEFAULT_ACCOUNT_ID$1 = "default";
137
+ /**
138
+ * Get account config from core config
139
+ *
140
+ * Handles two patterns:
141
+ * 1. Simplified single-account: base-level properties create implicit "default" account
142
+ * 2. Multi-account: explicit accounts object
143
+ *
144
+ * For "default" account, base-level properties take precedence over accounts.default
145
+ * For other accounts, only the accounts object is checked
146
+ */
147
+ function getAccountConfig(coreConfig, accountId) {
148
+ if (!coreConfig || typeof coreConfig !== "object") return null;
149
+ const cfg = coreConfig;
150
+ const normalizedAccountId = normalizeAccountId(accountId);
151
+ const twitchRaw = cfg.channels?.twitch;
152
+ const accounts = twitchRaw?.accounts;
153
+ if (normalizedAccountId === "default") {
154
+ const accountFromAccounts = resolveNormalizedAccountEntry(accounts, DEFAULT_ACCOUNT_ID$1, normalizeAccountId);
155
+ const baseLevel = {
156
+ username: typeof twitchRaw?.username === "string" ? twitchRaw.username : void 0,
157
+ accessToken: typeof twitchRaw?.accessToken === "string" ? twitchRaw.accessToken : void 0,
158
+ clientId: typeof twitchRaw?.clientId === "string" ? twitchRaw.clientId : void 0,
159
+ channel: typeof twitchRaw?.channel === "string" ? twitchRaw.channel : void 0,
160
+ enabled: typeof twitchRaw?.enabled === "boolean" ? twitchRaw.enabled : void 0,
161
+ allowFrom: Array.isArray(twitchRaw?.allowFrom) ? twitchRaw.allowFrom : void 0,
162
+ allowedRoles: Array.isArray(twitchRaw?.allowedRoles) ? twitchRaw.allowedRoles : void 0,
163
+ requireMention: typeof twitchRaw?.requireMention === "boolean" ? twitchRaw.requireMention : void 0,
164
+ clientSecret: typeof twitchRaw?.clientSecret === "string" ? twitchRaw.clientSecret : void 0,
165
+ refreshToken: typeof twitchRaw?.refreshToken === "string" ? twitchRaw.refreshToken : void 0,
166
+ expiresIn: typeof twitchRaw?.expiresIn === "number" ? twitchRaw.expiresIn : void 0,
167
+ obtainmentTimestamp: typeof twitchRaw?.obtainmentTimestamp === "number" ? twitchRaw.obtainmentTimestamp : void 0
168
+ };
169
+ const merged = {
170
+ ...accountFromAccounts,
171
+ ...baseLevel
172
+ };
173
+ if (merged.username) return merged;
174
+ if (accountFromAccounts) return accountFromAccounts;
175
+ return null;
176
+ }
177
+ const account = resolveNormalizedAccountEntry(accounts, normalizedAccountId, normalizeAccountId);
178
+ if (!account) return null;
179
+ return account;
180
+ }
181
+ /**
182
+ * List all configured account IDs
183
+ *
184
+ * Includes both explicit accounts and implicit "default" from base-level config
185
+ */
186
+ function listAccountIds(cfg) {
187
+ const twitchRaw = cfg.channels?.twitch;
188
+ const accountMap = twitchRaw?.accounts;
189
+ const hasBaseLevelConfig = twitchRaw && (typeof twitchRaw.username === "string" || typeof twitchRaw.accessToken === "string" || typeof twitchRaw.channel === "string");
190
+ return listCombinedAccountIds({
191
+ configuredAccountIds: Object.keys(accountMap ?? {}).map((accountId) => normalizeAccountId(accountId)),
192
+ implicitAccountId: hasBaseLevelConfig ? DEFAULT_ACCOUNT_ID$1 : void 0
193
+ });
194
+ }
195
+ function resolveDefaultTwitchAccountId(cfg) {
196
+ const preferredRaw = typeof cfg.channels?.twitch?.defaultAccount === "string" ? cfg.channels.twitch.defaultAccount.trim() : "";
197
+ const preferred = preferredRaw ? normalizeAccountId(preferredRaw) : "";
198
+ const ids = listAccountIds(cfg);
199
+ if (preferred && ids.includes(preferred)) return preferred;
200
+ if (ids.includes("default")) return DEFAULT_ACCOUNT_ID$1;
201
+ return ids[0] ?? "default";
202
+ }
203
+ function resolveTwitchAccountContext(cfg, accountId) {
204
+ const resolvedAccountId = accountId?.trim() ? normalizeAccountId(accountId) : resolveDefaultTwitchAccountId(cfg);
205
+ const account = getAccountConfig(cfg, resolvedAccountId);
206
+ const tokenResolution = resolveTwitchToken(cfg, { accountId: resolvedAccountId });
207
+ return {
208
+ accountId: resolvedAccountId,
209
+ account,
210
+ tokenResolution,
211
+ configured: account ? isAccountConfigured(account, tokenResolution.token) : false,
212
+ availableAccountIds: listAccountIds(cfg)
213
+ };
214
+ }
215
+ function resolveTwitchSnapshotAccountId(cfg, account) {
216
+ const accountMap = (cfg.channels?.twitch)?.accounts ?? {};
217
+ return Object.entries(accountMap).find(([, value]) => value === account)?.[0] ?? "default";
218
+ }
219
+ //#endregion
220
+ //#region extensions/twitch/src/setup-surface.ts
221
+ /**
222
+ * Twitch setup wizard surface for CLI setup.
223
+ */
224
+ const channel = "twitch";
225
+ const t = createSetupTranslator();
226
+ const INVALID_ACCOUNT_ID_MESSAGE = "Invalid Twitch account id";
227
+ function normalizeRequestedSetupAccountId(accountId) {
228
+ const normalized = normalizeOptionalAccountId(accountId);
229
+ if (!normalized) throw new Error(INVALID_ACCOUNT_ID_MESSAGE);
230
+ return normalized;
231
+ }
232
+ function resolveSetupAccountId(cfg, requestedAccountId) {
233
+ const requested = requestedAccountId?.trim();
234
+ if (requested) return normalizeRequestedSetupAccountId(requested);
235
+ const preferred = cfg.channels?.twitch?.defaultAccount?.trim();
236
+ return preferred ? normalizeAccountId$1(preferred) : resolveDefaultTwitchAccountId(cfg);
237
+ }
238
+ function setTwitchAccount(cfg, account, accountId = resolveSetupAccountId(cfg)) {
239
+ const resolvedAccountId = accountId.trim() ? normalizeRequestedSetupAccountId(accountId) : resolveSetupAccountId(cfg);
240
+ const existing = getAccountConfig(cfg, resolvedAccountId);
241
+ const merged = {
242
+ username: account.username ?? existing?.username ?? "",
243
+ accessToken: account.accessToken ?? existing?.accessToken ?? "",
244
+ clientId: account.clientId ?? existing?.clientId ?? "",
245
+ channel: account.channel ?? existing?.channel ?? "",
246
+ enabled: account.enabled ?? existing?.enabled ?? true,
247
+ allowFrom: account.allowFrom ?? existing?.allowFrom,
248
+ allowedRoles: account.allowedRoles ?? existing?.allowedRoles,
249
+ requireMention: account.requireMention ?? existing?.requireMention,
250
+ clientSecret: account.clientSecret ?? existing?.clientSecret,
251
+ refreshToken: account.refreshToken ?? existing?.refreshToken,
252
+ expiresIn: account.expiresIn ?? existing?.expiresIn,
253
+ obtainmentTimestamp: account.obtainmentTimestamp ?? existing?.obtainmentTimestamp
254
+ };
255
+ return {
256
+ ...cfg,
257
+ channels: {
258
+ ...cfg.channels,
259
+ twitch: {
260
+ ...cfg.channels?.twitch,
261
+ enabled: true,
262
+ accounts: {
263
+ ...(cfg.channels?.twitch)?.accounts,
264
+ [resolvedAccountId]: merged
265
+ }
266
+ }
267
+ }
268
+ };
269
+ }
270
+ async function noteTwitchSetupHelp(prompter) {
271
+ await prompter.note([
272
+ t("wizard.twitch.helpRequiresBot"),
273
+ t("wizard.twitch.helpCreateApp"),
274
+ t("wizard.twitch.helpGenerateToken"),
275
+ t("wizard.twitch.helpTokenTools"),
276
+ t("wizard.twitch.helpCopyToken"),
277
+ t("wizard.twitch.helpEnvVars"),
278
+ `Docs: ${formatDocsLink("/channels/twitch", "channels/twitch")}`
279
+ ].join("\n"), t("wizard.twitch.setupTitle"));
280
+ }
281
+ async function promptToken(prompter, account, envToken) {
282
+ const existingToken = account?.accessToken ?? "";
283
+ if (existingToken && !envToken) {
284
+ if (await prompter.confirm({
285
+ message: t("wizard.twitch.accessTokenKeep"),
286
+ initialValue: true
287
+ })) return existingToken;
288
+ }
289
+ return (await prompter.text({
290
+ message: t("wizard.twitch.oauthTokenPrompt"),
291
+ initialValue: envToken ?? "",
292
+ validate: (value) => {
293
+ const raw = value?.trim() ?? "";
294
+ if (!raw) return "Required";
295
+ if (!raw.startsWith("oauth:")) return "Token should start with 'oauth:'";
296
+ }
297
+ })).trim();
298
+ }
299
+ async function promptUsername(prompter, account) {
300
+ return (await prompter.text({
301
+ message: t("wizard.twitch.botUsernamePrompt"),
302
+ initialValue: account?.username ?? "",
303
+ validate: (value) => value?.trim() ? void 0 : "Required"
304
+ })).trim();
305
+ }
306
+ async function promptClientId(prompter, account) {
307
+ return (await prompter.text({
308
+ message: t("wizard.twitch.clientIdPrompt"),
309
+ initialValue: account?.clientId ?? "",
310
+ validate: (value) => value?.trim() ? void 0 : "Required"
311
+ })).trim();
312
+ }
313
+ async function promptChannelName(prompter, account) {
314
+ return (await prompter.text({
315
+ message: t("wizard.twitch.channelJoinPrompt"),
316
+ initialValue: account?.channel ?? "",
317
+ validate: (value) => value?.trim() ? void 0 : "Required"
318
+ })).trim();
319
+ }
320
+ async function promptRefreshTokenSetup(prompter, account) {
321
+ if (!await prompter.confirm({
322
+ message: t("wizard.twitch.refreshTokenPrompt"),
323
+ initialValue: Boolean(account?.clientSecret && account?.refreshToken)
324
+ })) return {};
325
+ return {
326
+ clientSecret: (await prompter.text({
327
+ message: t("wizard.twitch.clientSecretPrompt"),
328
+ initialValue: account?.clientSecret ?? "",
329
+ validate: (value) => value?.trim() ? void 0 : "Required"
330
+ })).trim() || void 0,
331
+ refreshToken: (await prompter.text({
332
+ message: t("wizard.twitch.refreshTokenInputPrompt"),
333
+ initialValue: account?.refreshToken ?? "",
334
+ validate: (value) => value?.trim() ? void 0 : "Required"
335
+ })).trim() || void 0
336
+ };
337
+ }
338
+ async function configureWithEnvToken(cfg, prompter, account, envToken, forceAllowFrom, dmPolicy, accountId = resolveSetupAccountId(cfg)) {
339
+ const resolvedAccountId = accountId.trim() ? normalizeRequestedSetupAccountId(accountId) : resolveSetupAccountId(cfg);
340
+ if (resolvedAccountId !== "default") return null;
341
+ if (!await prompter.confirm({
342
+ message: t("wizard.twitch.envPrompt"),
343
+ initialValue: true
344
+ })) return null;
345
+ const cfgWithAccount = setTwitchAccount(cfg, {
346
+ username: await promptUsername(prompter, account),
347
+ clientId: await promptClientId(prompter, account),
348
+ accessToken: envToken,
349
+ enabled: true
350
+ }, resolvedAccountId);
351
+ if (forceAllowFrom && dmPolicy.promptAllowFrom) return { cfg: await dmPolicy.promptAllowFrom({
352
+ cfg: cfgWithAccount,
353
+ prompter,
354
+ accountId: resolvedAccountId
355
+ }) };
356
+ return { cfg: cfgWithAccount };
357
+ }
358
+ function setTwitchAccessControl(cfg, allowedRoles, requireMention, accountId) {
359
+ const resolvedAccountId = resolveSetupAccountId(cfg, accountId);
360
+ const account = getAccountConfig(cfg, resolvedAccountId);
361
+ if (!account) return cfg;
362
+ return setTwitchAccount(cfg, {
363
+ ...account,
364
+ allowedRoles,
365
+ requireMention
366
+ }, resolvedAccountId);
367
+ }
368
+ function resolveTwitchGroupPolicy(cfg, accountId) {
369
+ const account = getAccountConfig(cfg, resolveSetupAccountId(cfg, accountId));
370
+ if (account?.allowedRoles?.includes("all")) return "open";
371
+ if (account?.allowedRoles?.includes("moderator")) return "allowlist";
372
+ return "disabled";
373
+ }
374
+ function setTwitchGroupPolicy(cfg, policy, accountId) {
375
+ return setTwitchAccessControl(cfg, policy === "open" ? ["all"] : policy === "allowlist" ? ["moderator", "vip"] : [], true, accountId);
376
+ }
377
+ const twitchDmPolicy = {
378
+ label: "Twitch",
379
+ channel,
380
+ policyKey: "channels.twitch.accounts.default.allowedRoles",
381
+ allowFromKey: "channels.twitch.accounts.default.allowFrom",
382
+ resolveConfigKeys: (cfg, accountId) => {
383
+ const resolvedAccountId = resolveSetupAccountId(cfg, accountId);
384
+ return {
385
+ policyKey: `channels.twitch.accounts.${resolvedAccountId}.allowedRoles`,
386
+ allowFromKey: `channels.twitch.accounts.${resolvedAccountId}.allowFrom`
387
+ };
388
+ },
389
+ getCurrent: (cfg, accountId) => {
390
+ const account = getAccountConfig(cfg, resolveSetupAccountId(cfg, accountId));
391
+ if (account?.allowedRoles?.includes("all")) return "open";
392
+ if (account?.allowFrom && account.allowFrom.length > 0) return "allowlist";
393
+ return "disabled";
394
+ },
395
+ setPolicy: (cfg, policy, accountId) => {
396
+ return setTwitchAccessControl(cfg, policy === "open" ? ["all"] : policy === "allowlist" ? [] : ["moderator"], true, accountId);
397
+ },
398
+ promptAllowFrom: async ({ cfg, prompter, accountId }) => {
399
+ const resolvedAccountId = resolveSetupAccountId(cfg, accountId);
400
+ const account = getAccountConfig(cfg, resolvedAccountId);
401
+ const existingAllowFrom = account?.allowFrom ?? [];
402
+ const allowFrom = (await prompter.text({
403
+ message: t("wizard.twitch.allowFromPrompt"),
404
+ placeholder: "123456789",
405
+ initialValue: existingAllowFrom[0] || void 0
406
+ }) ?? "").split(/[\n,;]+/g).map((s) => s.trim()).filter(Boolean);
407
+ return setTwitchAccount(cfg, {
408
+ ...account ?? void 0,
409
+ allowFrom
410
+ }, resolvedAccountId);
411
+ }
412
+ };
413
+ const twitchGroupAccess = {
414
+ label: "Twitch chat",
415
+ placeholder: "",
416
+ skipAllowlistEntries: true,
417
+ currentPolicy: ({ cfg, accountId }) => resolveTwitchGroupPolicy(cfg, accountId),
418
+ currentEntries: ({ cfg, accountId }) => {
419
+ return getAccountConfig(cfg, resolveSetupAccountId(cfg, accountId))?.allowFrom ?? [];
420
+ },
421
+ updatePrompt: ({ cfg, accountId }) => {
422
+ const account = getAccountConfig(cfg, resolveSetupAccountId(cfg, accountId));
423
+ return Boolean(account?.allowedRoles?.length || account?.allowFrom?.length);
424
+ },
425
+ setPolicy: ({ cfg, accountId, policy }) => setTwitchGroupPolicy(cfg, policy, accountId),
426
+ resolveAllowlist: async () => [],
427
+ applyAllowlist: ({ cfg }) => cfg
428
+ };
429
+ const twitchSetupAdapter = {
430
+ resolveAccountId: ({ cfg }) => resolveSetupAccountId(cfg),
431
+ applyAccountConfig: ({ cfg, accountId }) => setTwitchAccount(cfg, { enabled: true }, accountId)
432
+ };
433
+ const twitchSetupWizard = {
434
+ channel,
435
+ resolveAccountIdForConfigure: ({ cfg, accountOverride }) => resolveSetupAccountId(cfg, accountOverride),
436
+ resolveShouldPromptAccountIds: () => false,
437
+ status: {
438
+ configuredLabel: t("wizard.channels.statusConfigured"),
439
+ unconfiguredLabel: t("wizard.channels.statusNeedsUsernameTokenClientId"),
440
+ configuredHint: t("wizard.channels.statusConfigured"),
441
+ unconfiguredHint: t("wizard.channels.statusNeedsSetup"),
442
+ resolveConfigured: ({ cfg, accountId }) => {
443
+ return resolveTwitchAccountContext(cfg, resolveSetupAccountId(cfg, accountId)).configured;
444
+ },
445
+ resolveStatusLines: ({ cfg, accountId }) => {
446
+ const resolvedAccountId = resolveSetupAccountId(cfg, accountId);
447
+ const configured = resolveTwitchAccountContext(cfg, resolvedAccountId).configured;
448
+ return [`Twitch${resolvedAccountId !== "default" ? ` (${resolvedAccountId})` : ""}: ${configured ? t("wizard.channels.statusConfigured") : t("wizard.channels.statusNeedsUsernameTokenClientId")}`];
449
+ }
450
+ },
451
+ credentials: [],
452
+ finalize: async ({ cfg, accountId: requestedAccountId, prompter, forceAllowFrom }) => {
453
+ const accountId = resolveSetupAccountId(cfg, requestedAccountId);
454
+ const account = getAccountConfig(cfg, accountId);
455
+ if (!account || !isAccountConfigured(account)) await noteTwitchSetupHelp(prompter);
456
+ const envToken = process.env.KLAW_TWITCH_ACCESS_TOKEN?.trim();
457
+ if (accountId === "default" && envToken && !account?.accessToken) {
458
+ const envResult = await configureWithEnvToken(cfg, prompter, account, envToken, forceAllowFrom, twitchDmPolicy, accountId);
459
+ if (envResult) return envResult;
460
+ }
461
+ const username = await promptUsername(prompter, account);
462
+ const token = await promptToken(prompter, account, envToken);
463
+ const clientId = await promptClientId(prompter, account);
464
+ const channelName = await promptChannelName(prompter, account);
465
+ const { clientSecret, refreshToken } = await promptRefreshTokenSetup(prompter, account);
466
+ const cfgWithAccount = setTwitchAccount(cfg, {
467
+ username,
468
+ accessToken: token,
469
+ clientId,
470
+ channel: channelName,
471
+ clientSecret,
472
+ refreshToken,
473
+ enabled: true
474
+ }, accountId);
475
+ return { cfg: forceAllowFrom && twitchDmPolicy.promptAllowFrom ? await twitchDmPolicy.promptAllowFrom({
476
+ cfg: cfgWithAccount,
477
+ prompter,
478
+ accountId
479
+ }) : cfgWithAccount };
480
+ },
481
+ dmPolicy: twitchDmPolicy,
482
+ groupAccess: twitchGroupAccess,
483
+ disable: (cfg) => {
484
+ const twitch = cfg.channels?.twitch;
485
+ return {
486
+ ...cfg,
487
+ channels: {
488
+ ...cfg.channels,
489
+ twitch: {
490
+ ...twitch,
491
+ enabled: false
492
+ }
493
+ }
494
+ };
495
+ }
496
+ };
497
+ const twitchSetupPlugin = {
498
+ id: channel,
499
+ meta: getChatChannelMeta(channel),
500
+ capabilities: { chatTypes: ["group"] },
501
+ config: {
502
+ listAccountIds: (cfg) => listAccountIds(cfg),
503
+ resolveAccount: (cfg, accountId) => {
504
+ const resolvedAccountId = normalizeAccountId$1(accountId ?? resolveDefaultTwitchAccountId(cfg));
505
+ const account = getAccountConfig(cfg, resolvedAccountId);
506
+ if (!account) return {
507
+ accountId: resolvedAccountId,
508
+ username: "",
509
+ accessToken: "",
510
+ clientId: "",
511
+ channel: "",
512
+ enabled: false
513
+ };
514
+ return {
515
+ accountId: resolvedAccountId,
516
+ ...account
517
+ };
518
+ },
519
+ defaultAccountId: (cfg) => resolveDefaultTwitchAccountId(cfg),
520
+ isConfigured: (account, cfg) => resolveTwitchAccountContext(cfg, account?.accountId).configured,
521
+ isEnabled: (account) => account.enabled !== false
522
+ },
523
+ setup: twitchSetupAdapter,
524
+ setupWizard: twitchSetupWizard
525
+ };
526
+ //#endregion
527
+ export { getAccountConfig as a, resolveTwitchAccountContext as c, isAccountConfigured as d, missingTargetError as f, resolveTwitchToken as h, DEFAULT_ACCOUNT_ID$1 as i, resolveTwitchSnapshotAccountId as l, normalizeTwitchChannel as m, twitchSetupPlugin as n, listAccountIds as o, normalizeToken as p, twitchSetupWizard as r, resolveDefaultTwitchAccountId as s, twitchSetupAdapter as t, generateMessageId as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kodelyth/twitch",
3
- "version": "2026.5.39",
3
+ "version": "2026.6.1",
4
4
  "description": "Klaw Twitch channel plugin",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,9 +18,9 @@
18
18
  },
19
19
  "klaw": {
20
20
  "extensions": [
21
- "./index.js"
21
+ "./index.ts"
22
22
  ],
23
- "setupEntry": "./setup-entry.js",
23
+ "setupEntry": "./setup-entry.ts",
24
24
  "install": {
25
25
  "npmSpec": "@kodelyth/twitch",
26
26
  "defaultChoice": "npm",
@@ -45,6 +45,23 @@
45
45
  "release": {
46
46
  "publishToClawHub": true,
47
47
  "publishToNpm": true
48
+ },
49
+ "runtimeExtensions": [
50
+ "./dist/index.js"
51
+ ],
52
+ "runtimeSetupEntry": "./dist/setup-entry.js"
53
+ },
54
+ "files": [
55
+ "dist/**",
56
+ "klaw.plugin.json",
57
+ "README.md"
58
+ ],
59
+ "peerDependencies": {
60
+ "klaw": ">=2026.5.39"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "klaw": {
64
+ "optional": true
48
65
  }
49
66
  }
50
67
  }
package/api.js DELETED
@@ -1,7 +0,0 @@
1
- export * from "../../../dist/extensions/twitch/api.js";
2
- import * as module from "../../../dist/extensions/twitch/api.js";
3
- let defaultExport = "default" in module ? module.default : module;
4
- for (let index = 0; index < 4 && defaultExport && typeof defaultExport === "object" && "default" in defaultExport; index += 1) {
5
- defaultExport = defaultExport.default;
6
- }
7
- export { defaultExport as default };
@@ -1,7 +0,0 @@
1
- export * from "../../../dist/extensions/twitch/channel-plugin-api.js";
2
- import * as module from "../../../dist/extensions/twitch/channel-plugin-api.js";
3
- let defaultExport = "default" in module ? module.default : module;
4
- for (let index = 0; index < 4 && defaultExport && typeof defaultExport === "object" && "default" in defaultExport; index += 1) {
5
- defaultExport = defaultExport.default;
6
- }
7
- export { defaultExport as default };
package/index.js DELETED
@@ -1,7 +0,0 @@
1
- export * from "../../../dist/extensions/twitch/index.js";
2
- import defaultModule from "../../../dist/extensions/twitch/index.js";
3
- let defaultExport = defaultModule;
4
- for (let index = 0; index < 4 && defaultExport && typeof defaultExport === "object" && "default" in defaultExport; index += 1) {
5
- defaultExport = defaultExport.default;
6
- }
7
- export { defaultExport as default };
package/runtime-api.js DELETED
@@ -1,7 +0,0 @@
1
- export * from "../../../dist/extensions/twitch/runtime-api.js";
2
- import * as module from "../../../dist/extensions/twitch/runtime-api.js";
3
- let defaultExport = "default" in module ? module.default : module;
4
- for (let index = 0; index < 4 && defaultExport && typeof defaultExport === "object" && "default" in defaultExport; index += 1) {
5
- defaultExport = defaultExport.default;
6
- }
7
- export { defaultExport as default };
package/setup-entry.js DELETED
@@ -1,7 +0,0 @@
1
- export * from "../../../dist/extensions/twitch/setup-entry.js";
2
- import defaultModule from "../../../dist/extensions/twitch/setup-entry.js";
3
- let defaultExport = defaultModule;
4
- for (let index = 0; index < 4 && defaultExport && typeof defaultExport === "object" && "default" in defaultExport; index += 1) {
5
- defaultExport = defaultExport.default;
6
- }
7
- export { defaultExport as default };
@@ -1,7 +0,0 @@
1
- export * from "../../../dist/extensions/twitch/setup-plugin-api.js";
2
- import * as module from "../../../dist/extensions/twitch/setup-plugin-api.js";
3
- let defaultExport = "default" in module ? module.default : module;
4
- for (let index = 0; index < 4 && defaultExport && typeof defaultExport === "object" && "default" in defaultExport; index += 1) {
5
- defaultExport = defaultExport.default;
6
- }
7
- export { defaultExport as default };