@opentabs-dev/opentabs-plugin-twitch 0.0.75 → 0.0.77

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/README.md CHANGED
@@ -1,159 +1,79 @@
1
- # opentabs-plugin-twitch
1
+ # Twitch
2
2
 
3
- OpenTabs plugin for Twitch
3
+ OpenTabs plugin for Twitch — browse streams, search channels and games, view clips and videos — gives AI agents access to Twitch through your authenticated browser session.
4
4
 
5
- ## Project Structure
5
+ ## Install
6
6
 
7
+ ```bash
8
+ opentabs plugin install twitch
7
9
  ```
8
- twitch/
9
- ├── package.json # Plugin metadata (name, opentabs field, dependencies)
10
- ├── icon.svg # Optional custom icon (square SVG, max 8KB)
11
- ├── icon-inactive.svg # Optional manual inactive icon override
12
- ├── src/
13
- │ ├── index.ts # Plugin class (extends OpenTabsPlugin)
14
- │ └── tools/ # One file per tool (using defineTool)
15
- │ └── example.ts
16
- └── dist/ # Build output (generated)
17
- ├── adapter.iife.js # Bundled adapter injected into matching tabs
18
- └── tools.json # Tool schemas for MCP registration
19
- ```
20
-
21
- ## Configuration
22
-
23
- Plugin metadata is defined in `package.json` under the `opentabs` field:
24
-
25
- ```json
26
- {
27
- "name": "opentabs-plugin-twitch",
28
- "main": "dist/adapter.iife.js",
29
- "opentabs": {
30
- "displayName": "Twitch",
31
- "description": "OpenTabs plugin for Twitch",
32
- "urlPatterns": ["*://*.twitch.tv/*"]
33
- }
34
- }
35
- ```
36
-
37
- - **`main`** — entry point for the bundled adapter IIFE
38
- - **`opentabs.displayName`** — human-readable name shown in the side panel
39
- - **`opentabs.description`** — short description of what the plugin does
40
- - **`opentabs.urlPatterns`** — Chrome match patterns for tabs where the adapter is injected
41
10
 
42
- ## Custom Icons
11
+ Or install globally via npm:
43
12
 
44
- By default, the side panel shows a colored letter avatar for your plugin. To use a custom icon, place an `icon.svg` file in the plugin root (next to `package.json`):
45
-
46
- ```
47
- twitch/
48
- ├── package.json
49
- ├── icon.svg ← custom icon (optional)
50
- ├── icon-inactive.svg ← manual inactive override (optional, requires icon.svg)
51
- ├── src/
52
- │ └── ...
13
+ ```bash
14
+ npm install -g @opentabs-dev/opentabs-plugin-twitch
53
15
  ```
54
16
 
55
- **How it works:**
17
+ ## Setup
56
18
 
57
- - `opentabs-plugin build` reads `icon.svg`, validates it, auto-generates a grayscale inactive variant, and embeds both in `dist/tools.json`
58
- - To override the auto-generated inactive icon, provide `icon-inactive.svg` (must use only grayscale colors)
59
- - If no `icon.svg` is provided, the letter avatar is used automatically
19
+ 1. Open [twitch.tv](https://www.twitch.tv) in Chrome and log in
20
+ 2. Open the OpenTabs side panel the Twitch plugin should appear as **ready**
60
21
 
61
- **Icon requirements:**
22
+ ## Tools (14)
62
23
 
63
- - Square SVG with a `viewBox` attribute (e.g., `viewBox="0 0 32 32"`)
64
- - Maximum 8 KB file size
65
- - No embedded `<image>`, `<script>`, or event handler attributes (`onclick`, etc.)
66
- - Manual `icon-inactive.svg` must use only achromatic (grayscale) colors
24
+ ### Users (2)
67
25
 
68
- ## Development
26
+ | Tool | Description | Type |
27
+ |---|---|---|
28
+ | `get_current_user` | Get the authenticated Twitch user profile | Read |
29
+ | `get_user_profile` | Get a Twitch user profile by login name | Read |
69
30
 
70
- ```bash
71
- npm install
72
- npm run build # tsc && opentabs-plugin build
73
- npm run dev # watch mode (tsc --watch + opentabs-plugin build --watch)
74
- npm run type-check # tsc --noEmit
75
- npm run lint # biome
76
- ```
31
+ ### Streams (3)
77
32
 
78
- ## Adding Tools
79
-
80
- Create a new file in `src/tools/` using `defineTool`:
81
-
82
- ```ts
83
- import { z } from 'zod';
84
- import { defineTool } from '@opentabs-dev/plugin-sdk';
85
-
86
- export const myTool = defineTool({
87
- name: 'my_tool',
88
- displayName: 'My Tool',
89
- description: 'What this tool does',
90
- icon: 'wrench',
91
- input: z.object({ /* ... */ }),
92
- output: z.object({ /* ... */ }),
93
- handle: async (params) => {
94
- // Tool implementation runs in the browser tab context
95
- return { /* ... */ };
96
- },
97
- });
98
- ```
33
+ | Tool | Description | Type |
34
+ |---|---|---|
35
+ | `get_top_streams` | Get top live streams by viewer count | Read |
36
+ | `get_streams_by_game` | Get live streams for a game or category | Read |
37
+ | `get_stream` | Get live stream info for a channel | Read |
99
38
 
100
- Then register it in `src/index.ts` by adding it to the `tools` array.
39
+ ### Games (2)
101
40
 
102
- ## Authentication
41
+ | Tool | Description | Type |
42
+ |---|---|---|
43
+ | `get_top_games` | Get top games and categories by viewer count | Read |
44
+ | `get_game` | Get game/category details | Read |
103
45
 
104
- Plugin tools run in the browser tab context, so they can read auth tokens directly from the page. The SDK provides utilities for the most common patterns:
46
+ ### Search (2)
105
47
 
106
- ```ts
107
- import { getLocalStorage, getCookie, getPageGlobal } from '@opentabs-dev/plugin-sdk';
48
+ | Tool | Description | Type |
49
+ |---|---|---|
50
+ | `search_channels` | Search for Twitch channels | Read |
51
+ | `search_categories` | Search for games and categories | Read |
108
52
 
109
- // localStorage — most common
110
- const token = getLocalStorage('token');
53
+ ### Clips (2)
111
54
 
112
- // Cookies session tokens, JWTs
113
- const session = getCookie('session_id');
55
+ | Tool | Description | Type |
56
+ |---|---|---|
57
+ | `get_user_clips` | Get clips from a Twitch channel | Read |
58
+ | `get_game_clips` | Get top clips for a game or category | Read |
114
59
 
115
- // Page globals — SPA boot data (e.g., window.__APP_STATE__)
116
- const appState = getPageGlobal('__APP_STATE__');
117
- ```
60
+ ### Videos (2)
118
61
 
119
- **Iframe fallback:** Some apps (e.g., Discord) delete `window.localStorage` after boot. `getLocalStorage` automatically tries a hidden same-origin iframe fallback before returning `null`, so you don't need to handle this case manually.
62
+ | Tool | Description | Type |
63
+ |---|---|---|
64
+ | `get_user_videos` | Get videos from a Twitch channel | Read |
65
+ | `get_video` | Get details about a specific video | Read |
120
66
 
121
- **SPA hydration:** Auth tokens may not be available immediately on page load. Implement polling in `isReady()` to wait until the app has hydrated before your tools run. See the comments in `src/index.ts` for an example polling pattern.
67
+ ### Chat (1)
122
68
 
123
- ## Shared Schemas
69
+ | Tool | Description | Type |
70
+ |---|---|---|
71
+ | `get_channel_emotes` | Get subscription emotes for a channel | Read |
124
72
 
125
- When 3 or more tools share the same input or output shape, extract common Zod schemas into a shared file to avoid duplication:
73
+ ## How It Works
126
74
 
127
- ```ts
128
- // src/schemas/channel.ts
129
- import { z } from 'zod';
75
+ This plugin runs inside your Twitch tab through the [OpenTabs](https://opentabs.dev) Chrome extension. It uses your existing browser session — no API tokens or OAuth apps required. All operations happen as you, with your permissions.
130
76
 
131
- export const channelSchema = z.object({
132
- id: z.string().describe('Channel ID'),
133
- name: z.string().describe('Channel name'),
134
- });
135
-
136
- export type Channel = z.infer<typeof channelSchema>;
137
- ```
138
-
139
- Then import and reuse in your tools:
140
-
141
- ```ts
142
- // src/tools/list-channels.ts
143
- import { channelSchema } from '../schemas/channel.js';
144
-
145
- export const listChannels = defineTool({
146
- name: 'list_channels',
147
- displayName: 'List Channels',
148
- description: 'List all available channels',
149
- icon: 'list',
150
- input: z.object({}),
151
- output: z.object({ channels: z.array(channelSchema) }),
152
- handle: async () => {
153
- // ...
154
- return { channels: [] };
155
- },
156
- });
157
- ```
77
+ ## License
158
78
 
159
- This keeps your tool schemas DRY and makes it easy to evolve shared types in one place.
79
+ MIT
@@ -373,6 +373,8 @@
373
373
  * (e.g., 'https://github.com'), not a match pattern.
374
374
  */
375
375
  homepage;
376
+ /** Typed configuration schema — declares settings that users provide via config.json or the side panel. */
377
+ configSchema;
376
378
  };
377
379
 
378
380
  // src/twitch-api.ts
@@ -14883,21 +14885,21 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
14883
14885
  };
14884
14886
  var src_default = new TwitchPlugin();
14885
14887
 
14886
- // dist/_adapter_entry_076d99d3-40b4-463e-9ce2-53dcd3cb1ae7.ts
14888
+ // dist/_adapter_entry_fc4d328f-7ceb-4051-9f8e-263218be4761.ts
14887
14889
  if (!globalThis.__openTabs) {
14888
14890
  globalThis.__openTabs = {};
14889
14891
  } else {
14890
14892
  const desc = Object.getOwnPropertyDescriptor(globalThis.__openTabs, "adapters");
14891
14893
  if (desc && !desc.writable) {
14892
- const ot2 = globalThis.__openTabs;
14894
+ const ot3 = globalThis.__openTabs;
14893
14895
  const newAdaptersObj = {};
14894
- if (ot2.adapters) {
14895
- for (const key of Object.keys(ot2.adapters)) {
14896
- const d = Object.getOwnPropertyDescriptor(ot2.adapters, key);
14896
+ if (ot3.adapters) {
14897
+ for (const key of Object.keys(ot3.adapters)) {
14898
+ const d = Object.getOwnPropertyDescriptor(ot3.adapters, key);
14897
14899
  if (d) Object.defineProperty(newAdaptersObj, key, d);
14898
14900
  }
14899
14901
  }
14900
- globalThis.__openTabs = Object.assign({}, ot2, { adapters: newAdaptersObj });
14902
+ globalThis.__openTabs = Object.assign({}, ot3, { adapters: newAdaptersObj });
14901
14903
  }
14902
14904
  }
14903
14905
  if (!globalThis.__openTabs.adapters) {
@@ -14935,6 +14937,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
14935
14937
  }
14936
14938
  };
14937
14939
  var restoreTransport = setLogTransport ? setLogTransport(logTransport) : void 0;
14940
+ var ot2 = globalThis.__openTabs;
14941
+ ot2._notifyReadinessChanged = () => {
14942
+ try {
14943
+ const nonce = globalThis.__openTabs?._readinessNonce;
14944
+ if (nonce) {
14945
+ window.postMessage({ type: "opentabs:readiness-changed", plugin: "twitch", nonce }, "*");
14946
+ }
14947
+ } catch {
14948
+ }
14949
+ };
14938
14950
  var existing = adapters["twitch"];
14939
14951
  if (existing) {
14940
14952
  if (typeof existing.teardown === "function") {
@@ -14946,7 +14958,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
14946
14958
  }
14947
14959
  }
14948
14960
  if (!Reflect.deleteProperty(adapters, "twitch")) {
14949
- const ot2 = globalThis.__openTabs;
14961
+ const ot3 = globalThis.__openTabs;
14950
14962
  const newAdapters = {};
14951
14963
  for (const key of Object.keys(adapters)) {
14952
14964
  if (key !== "twitch") {
@@ -14954,7 +14966,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
14954
14966
  if (desc) Object.defineProperty(newAdapters, key, desc);
14955
14967
  }
14956
14968
  }
14957
- globalThis.__openTabs = Object.assign({}, ot2, { adapters: newAdapters });
14969
+ globalThis.__openTabs = Object.assign({}, ot3, { adapters: newAdapters });
14958
14970
  }
14959
14971
  var hasLifecycleHooks = typeof src_default.onToolInvocationStart === "function" || typeof src_default.onToolInvocationEnd === "function";
14960
14972
  for (const tool of src_default.tools) {
@@ -15015,12 +15027,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15015
15027
  }
15016
15028
  }
15017
15029
  };
15018
- const ot2 = globalThis.__openTabs;
15019
- if (!ot2._navigationInterceptor) {
15030
+ const ot3 = globalThis.__openTabs;
15031
+ if (!ot3._navigationInterceptor) {
15020
15032
  const origPushState = history.pushState.bind(history);
15021
15033
  const origReplaceState = history.replaceState.bind(history);
15022
15034
  const callbacks = /* @__PURE__ */ new Map();
15023
- ot2._navigationInterceptor = { callbacks, origPushState, origReplaceState };
15035
+ ot3._navigationInterceptor = { callbacks, origPushState, origReplaceState };
15024
15036
  history.pushState = function(...args) {
15025
15037
  origPushState(...args);
15026
15038
  for (const cb of callbacks.values()) {
@@ -15034,7 +15046,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15034
15046
  }
15035
15047
  };
15036
15048
  }
15037
- const interceptor = ot2._navigationInterceptor;
15049
+ const interceptor = ot3._navigationInterceptor;
15038
15050
  interceptor.callbacks.set("twitch", checkUrl);
15039
15051
  window.addEventListener("popstate", checkUrl);
15040
15052
  window.addEventListener("hashchange", checkUrl);
@@ -15089,5 +15101,5 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15089
15101
  };
15090
15102
  delete src_default.onDeactivate;
15091
15103
  }
15092
- })();(function(){var o=(globalThis).__openTabs;if(o&&o.adapters&&o.adapters["twitch"]){var a=o.adapters["twitch"];a.__adapterHash="3e5c28e5ba090d4da043a4375a541ebc1a359aba5e82cd181f5f5d3a241a50ab";if(a.tools&&Array.isArray(a.tools)){for(var i=0;i<a.tools.length;i++){Object.freeze(a.tools[i]);}Object.freeze(a.tools);}Object.freeze(a);Object.defineProperty(o.adapters,"twitch",{value:a,writable:false,configurable:false,enumerable:true});Object.defineProperty(o,"adapters",{value:o.adapters,writable:false,configurable:false});}})();
15104
+ })();(function(){var o=(globalThis).__openTabs;if(o&&o.adapters&&o.adapters["twitch"]){var a=o.adapters["twitch"];a.__adapterHash="2e9b431884577e773e8559ed8b56a94db8371459f3d6da2c8c47ad1c9c9d82b9";if(a.tools&&Array.isArray(a.tools)){for(var i=0;i<a.tools.length;i++){Object.freeze(a.tools[i]);}Object.freeze(a.tools);}Object.freeze(a);Object.defineProperty(o.adapters,"twitch",{value:a,writable:false,configurable:false,enumerable:true});Object.defineProperty(o,"adapters",{value:o.adapters,writable:false,configurable:false});}})();
15093
15105
  //# sourceMappingURL=adapter.iife.js.map