roku-mcp 1.6.0 → 1.8.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 (37) hide show
  1. package/README.md +129 -25
  2. package/dist/assets/streamprobe/components/StreamProbe.xml +96 -0
  3. package/dist/assets/streamprobe/manifest +11 -0
  4. package/dist/assets/streamprobe/source/main.brs +29 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +5 -2
  7. package/dist/index.js.map +1 -1
  8. package/dist/stream/charles.d.ts +50 -0
  9. package/dist/stream/charles.d.ts.map +1 -0
  10. package/dist/stream/charles.js +268 -0
  11. package/dist/stream/charles.js.map +1 -0
  12. package/dist/stream/diagnose.d.ts +54 -0
  13. package/dist/stream/diagnose.d.ts.map +1 -0
  14. package/dist/stream/diagnose.js +568 -0
  15. package/dist/stream/diagnose.js.map +1 -0
  16. package/dist/stream/error-info.d.ts +50 -0
  17. package/dist/stream/error-info.d.ts.map +1 -0
  18. package/dist/stream/error-info.js +168 -0
  19. package/dist/stream/error-info.js.map +1 -0
  20. package/dist/stream/live.d.ts +43 -0
  21. package/dist/stream/live.d.ts.map +1 -0
  22. package/dist/stream/live.js +258 -0
  23. package/dist/stream/live.js.map +1 -0
  24. package/dist/stream/manifest.d.ts +90 -0
  25. package/dist/stream/manifest.d.ts.map +1 -0
  26. package/dist/stream/manifest.js +480 -0
  27. package/dist/stream/manifest.js.map +1 -0
  28. package/dist/stream/roku-specs.json +94 -0
  29. package/dist/tools/bsprof.d.ts +4 -0
  30. package/dist/tools/bsprof.d.ts.map +1 -1
  31. package/dist/tools/bsprof.js +89 -19
  32. package/dist/tools/bsprof.js.map +1 -1
  33. package/dist/tools/stream.d.ts +3 -0
  34. package/dist/tools/stream.d.ts.map +1 -0
  35. package/dist/tools/stream.js +194 -0
  36. package/dist/tools/stream.js.map +1 -0
  37. package/package.json +10 -3
package/README.md CHANGED
@@ -2,23 +2,34 @@
2
2
 
3
3
  A [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server for Roku device automation. Exposes tools for app deployment, ECP remote control, screenshot capture, SceneGraph node inspection, and BrightScript debug console access.
4
4
 
5
- ## Installation
5
+ ## Install
6
+
7
+ The server ships as a single `npx` command. Install once globally if you prefer, or let your MCP client launch it on demand via `npx -y roku-mcp` — both work.
6
8
 
7
9
  ```bash
8
- npm install -g roku-mcp
10
+ npm install -g roku-mcp # optional, global install
11
+ # or just point your client at: npx -y roku-mcp
9
12
  ```
10
13
 
11
- Or run directly with npx (no install required):
14
+ ### Plugin mode (Claude Code and Cursor)
12
15
 
13
- ```bash
14
- npx roku-mcp
15
- ```
16
+ > Unofficial community tool. Not affiliated with or endorsed by Roku, Inc.
17
+
18
+ The repo also ships as a plugin: the MCP server, workflow skills (`skills/`), and Cursor rules, registered together.
19
+
20
+ - **Claude Code:** `/plugin marketplace add jack1590/RokuMCP` then `/plugin install roku-mcp@roku-mcp-marketplace`. Manifests: `.claude-plugin/`, `.mcp.json`.
21
+ - **Cursor:** manifests in `.cursor-plugin/` and `mcp.json`. Skills in `skills/`, rules in `rules/cursor/`.
22
+
23
+ Claude Code asks for the device host and developer password when the plugin is enabled (the password is stored as sensitive). Both clients use the entry point `bootstrap.mjs`. Both run `npm ci` and `npm run build` on first start (Node 18+). Outside plugin mode, `ROKU_DEVICE_HOST` / `ROKU_DEVICE_PASSWORD` or a project `.env` still work.
24
+
25
+ Configure your AI client below. Every platform uses the same launch line; only the config file and reload step change.
16
26
 
17
- ## Client Configuration
27
+ <details>
28
+ <summary><strong>Cursor</strong> — agent-mode tools, auto-discovers MCP servers</summary>
18
29
 
19
- ### Cursor
30
+ **Prerequisites:** Cursor 0.45+ with Agent mode.
20
31
 
21
- 1. Create `.cursor/mcp.json` in your project root:
32
+ **Install:** create `.cursor/mcp.json` in your project root (or `~/.cursor/mcp.json` for global):
22
33
 
23
34
  ```json
24
35
  {
@@ -35,14 +46,22 @@ npx roku-mcp
35
46
  }
36
47
  ```
37
48
 
38
- 2. Reload the window: `Cmd+Shift+P` (macOS) / `Ctrl+Shift+P` (Windows/Linux) → **Developer: Reload Window**
39
- 3. Go to **Cursor Settings → MCP** and verify the "roku" server shows a green status. If it appears disabled, click the toggle to enable it.
49
+ Then reload the window (`Cmd+Shift+P` / `Ctrl+Shift+P` → **Developer: Reload Window**).
40
50
 
41
- ### VS Code
51
+ **Verify:** open **Cursor Settings → MCP** and confirm the `roku` row shows a green dot. If it appears disabled, click the toggle. In a new agent chat, type `list roku tools` — you should see `roku_deploy`, `roku_screenshot`, etc.
42
52
 
43
- Requires VS Code **1.99+** with the GitHub Copilot extension.
53
+ **Routing rule (recommended):** the repo also ships an [`AGENTS.md`](./AGENTS.md). Cursor picks it up automatically, so the agent knows the canonical *observe → act → wait → verify* loop and the RTA `uiElementId` gotcha without you having to re-explain it every session.
44
54
 
45
- 1. Create `.vscode/mcp.json` in your project root:
55
+ **Troubleshoot:** if the server stays grey/disabled, check `View → Output → MCP Logs` — the most common cause is `ROKU_DEVICE_HOST` unreachable on the LAN. Omit the `env` block entirely to fall back to SSDP auto-discovery.
56
+
57
+ </details>
58
+
59
+ <details>
60
+ <summary><strong>VS Code (GitHub Copilot)</strong> — agent mode, MCP via <code>.vscode/mcp.json</code></summary>
61
+
62
+ **Prerequisites:** VS Code 1.99+ with the GitHub Copilot extension and an active Copilot subscription that includes Agent mode.
63
+
64
+ **Install:** create `.vscode/mcp.json` in your project root:
46
65
 
47
66
  ```json
48
67
  {
@@ -59,12 +78,21 @@ Requires VS Code **1.99+** with the GitHub Copilot extension.
59
78
  }
60
79
  ```
61
80
 
62
- 2. Reload the window: `Cmd+Shift+P` / `Ctrl+Shift+P` → **Developer: Reload Window**
63
- 3. Open Copilot Chat and switch to **Agent mode** (select from the chat mode dropdown). The roku tools will be available there.
81
+ Reload the window (`Cmd+Shift+P` / `Ctrl+Shift+P` → **Developer: Reload Window**).
82
+
83
+ **Verify:** open Copilot Chat, switch the chat-mode dropdown to **Agent**, and ask "what roku tools do you have?". You should see the full `roku_*` list. The MCP panel (`Cmd+Shift+P` → **MCP: List Servers**) should show `roku` as `Running`.
84
+
85
+ **Troubleshoot:** if the server fails to start, run `npx -y roku-mcp --help` in a regular terminal first — that confirms Node 18+ and `npx` are working before VS Code launches it.
64
86
 
65
- ### Claude Desktop
87
+ </details>
88
+
89
+ <details>
90
+ <summary><strong>Claude Desktop</strong> — official Anthropic desktop app</summary>
91
+
92
+ **Prerequisites:** Claude Desktop (latest from claude.ai/download), Node.js 18+ on `PATH`.
93
+
94
+ **Install:** edit your Claude Desktop config:
66
95
 
67
- Add to your Claude Desktop config file:
68
96
  - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
69
97
  - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
70
98
 
@@ -83,11 +111,20 @@ Add to your Claude Desktop config file:
83
111
  }
84
112
  ```
85
113
 
86
- Restart Claude Desktop after saving.
114
+ Fully quit and restart Claude Desktop (the menu-bar icon must close — `Cmd+Q` on macOS).
115
+
116
+ **Verify:** open a new chat. The 🛠️ icon in the input bar should list `roku-mcp` with all its tools. Ask "take a Roku screenshot" — Claude will request approval for `roku_screenshot` and run it.
117
+
118
+ **Troubleshoot:** if no tool icon appears, check `~/Library/Logs/Claude/mcp-server-roku.log` (macOS) — `command not found: npx` means Node isn't on the GUI app's `PATH`. Either install Node via the official `.pkg`, or hard-code the absolute paths: `"command": "/usr/local/bin/node"`, `"args": ["/usr/local/bin/npx", "-y", "roku-mcp"]`.
119
+
120
+ </details>
121
+
122
+ <details>
123
+ <summary><strong>Windsurf</strong> — Codeium's agentic IDE</summary>
87
124
 
88
- ### Windsurf
125
+ **Prerequisites:** Windsurf with Cascade enabled.
89
126
 
90
- Create `.windsurf/mcp.json` in your project root:
127
+ **Install:** create `.windsurf/mcp.json` in your project root (or `~/.codeium/windsurf/mcp_config.json` globally):
91
128
 
92
129
  ```json
93
130
  {
@@ -104,18 +141,75 @@ Create `.windsurf/mcp.json` in your project root:
104
141
  }
105
142
  ```
106
143
 
107
- Reload the window after saving.
144
+ Reload the window. In Cascade settings, refresh the MCP server list.
108
145
 
109
- ### Any MCP-compatible client
146
+ **Verify:** ask Cascade "list available MCP tools" — `roku_*` entries should appear.
110
147
 
111
- The server uses **stdio transport**. Any client that supports MCP can launch it with:
148
+ **Troubleshoot:** Windsurf occasionally caches a stale server. If reloads don't pick up edits, fully quit Windsurf and reopen.
149
+
150
+ </details>
151
+
152
+ <details>
153
+ <summary><strong>Any MCP-compatible client</strong> (Continue, Cline, Zed, Codex, Claude Code, OpenCode, …)</summary>
154
+
155
+ The server speaks the standard MCP **stdio** transport. Anywhere you can declare:
112
156
 
113
157
  ```
114
158
  command: npx
115
159
  args: ["-y", "roku-mcp"]
160
+ env: { ROKU_DEVICE_HOST: "...", ROKU_DEVICE_PASSWORD: "..." }
161
+ ```
162
+
163
+ …the server runs. Both env vars are optional: omit `ROKU_DEVICE_HOST` to use SSDP auto-discovery on the LAN, and pass `host` / `password` on each tool call to override.
164
+
165
+ The repo-root [`AGENTS.md`](./AGENTS.md) is picked up automatically by Claude Code, OpenCode, Codex CLI, and any other tool that follows the AGENTS.md convention, so those agents inherit the same usage guidance without extra setup.
166
+
167
+ </details>
168
+
169
+ ## Agent rules
170
+
171
+ For better agent behavior (the `observe → act → wait → verify` loop, the RTA `uiElementId` gotcha that silently breaks `roku_edit_node`, when to use which tool), the repo ships drop-in rules pre-formatted for each major agent. The body is identical — only the wrapper differs to match what each agent auto-loads.
172
+
173
+ | Agent | File | Install path |
174
+ |---|---|---|
175
+ | Cursor | [`rules/cursor/roku-mcp.mdc`](./rules/cursor/roku-mcp.mdc) | `.cursor/rules/roku-mcp.mdc` |
176
+ | Claude Code | [`rules/claude-code/CLAUDE.md`](./rules/claude-code/CLAUDE.md) | `CLAUDE.md` (repo root) |
177
+ | Claude Desktop | [`rules/claude-desktop/roku-mcp.md`](./rules/claude-desktop/roku-mcp.md) | Settings → System Prompt |
178
+ | Windsurf | [`rules/windsurf/roku-mcp.md`](./rules/windsurf/roku-mcp.md) | `.windsurf/rules/roku-mcp.md` |
179
+ | Codex CLI / OpenCode / Aider | [`AGENTS.md`](./AGENTS.md) (repo root) | `AGENTS.md` (repo root) |
180
+
181
+ See [`rules/README.md`](./rules/README.md) for one-line copy commands per agent.
182
+
183
+ ## Quickstart
184
+
185
+ Once your client is wired up, these three prompts work out of the box and exercise the main capability groups.
186
+
187
+ **1. Confirm the device is reachable**
188
+
189
+ ```
190
+ Use roku-mcp to query my Roku device info and tell me the model, firmware, and IP.
191
+ ```
192
+
193
+ The agent should call `roku_query_device_info` (and possibly `roku_discover` if no host is configured) and reply in one turn.
194
+
195
+ **2. Drive the UI with screenshots**
196
+
197
+ ```
198
+ Press Home, wait 2 seconds, take a screenshot, then move Right twice and
199
+ screenshot again. Describe what changed between the two screenshots.
200
+ ```
201
+
202
+ This exercises `roku_keypress`, `roku_sleep`, and `roku_screenshot` — the standard *observe → act → wait → verify* loop.
203
+
204
+ **3. Live-edit a running channel (requires RTA — see [Runtime UI Editing](#runtime-ui-editing))**
205
+
206
+ ```
207
+ On the currently running dev channel, find the label that shows the page
208
+ title, change its text to "Hello from MCP", verify the change with
209
+ roku_get_value, then change it back.
116
210
  ```
117
211
 
118
- Pass `ROKU_DEVICE_HOST` and `ROKU_DEVICE_PASSWORD` as environment variables, or omit the host to use SSDP auto-discovery.
212
+ The agent will `roku_query_app_ui` to locate the node, grab the `uiElementId`, call `roku_edit_node` + `roku_get_value`, and roll back — proving end-to-end RTA wiring.
119
213
 
120
214
  ## Environment Variables
121
215
 
@@ -281,6 +375,16 @@ Record, analyze, and compare Perfetto traces from Roku devices. Requires **Roku
281
375
 
282
376
  Workflow: enable tracing → start recording → interact with app → stop recording → analyze. The `.trace` files can also be opened at [ui.perfetto.dev](https://ui.perfetto.dev/).
283
377
 
378
+ ### Stream Diagnosis
379
+
380
+ Figure out why an HLS/DASH stream that plays everywhere else fails on Roku.
381
+
382
+ | Tool | Description |
383
+ |---|---|
384
+ | `roku_diagnose_stream` | Correlate the Video-node `errorInfo`, the manifest (a fetchable `url` or pasted `content`), and an optional Charles/HAR capture into ranked Roku-specific root causes (cause, evidence, confidence, severity, fix, doc link). Optionally `captureLive=true` deploys the bundled StreamProbe harness to capture the device error first. |
385
+
386
+ It detects Roku-specific gotchas such as muxed audio+video in fMP4/CMAF HLS (plays video but silent), video codecs/levels beyond the hardware decoder (e.g. 4K H.264 or 8K HEVC), token/403-gated CDNs, and DRM license rejections. The static manifest checks are treated as hints; when you supply the real device `errorInfo` the diagnosis is corroborated to high confidence.
387
+
284
388
  ## Requirements
285
389
 
286
390
  - Node.js 18+
@@ -0,0 +1,96 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+
3
+ <!-- StreamProbe scene: plays the stream described by deep-link args and exposes
4
+ the Video node error fields (errorCode/errorMsg/errorInfo) for RTA capture. -->
5
+
6
+ <component name="StreamProbe" extends="Scene">
7
+
8
+ <interface>
9
+ <field id="launchArgs" type="assocarray" onChange="onLaunchArgs" />
10
+ <!-- Mirror fields the diagnoser reads via RTA getValue. -->
11
+ <field id="probeState" type="string" value="idle" />
12
+ <field id="probeErrorCode" type="integer" value="0" />
13
+ <field id="probeErrorMsg" type="string" value="" />
14
+ <field id="probeErrorInfo" type="string" value="" />
15
+ </interface>
16
+
17
+ <script type="text/brightscript">
18
+ <![CDATA[
19
+
20
+ sub init()
21
+ m.video = m.top.findNode("probeVideo")
22
+ m.video.observeField("state", "onVideoState")
23
+ end sub
24
+
25
+ ' Re-entrant: launch args may arrive after init.
26
+ sub onLaunchArgs()
27
+ startPlayback(m.top.launchArgs)
28
+ end sub
29
+
30
+ sub startPlayback(args as dynamic)
31
+ if args = invalid then return
32
+
33
+ url = pickArg(args, "input_url", "url")
34
+ if url = invalid or url = "" then return
35
+
36
+ format = pickArg(args, "input_format", "format")
37
+ if format = invalid or format = "" then format = "hls"
38
+
39
+ content = createObject("RoSGNode", "ContentNode")
40
+ content.title = "StreamProbe"
41
+ content.streamFormat = format
42
+ content.url = url
43
+
44
+ keySystem = pickArg(args, "input_drm", "drm")
45
+ licenseUrl = pickArg(args, "input_license_url", "licenseServerURL")
46
+ if keySystem <> invalid and keySystem <> "" then
47
+ drmParams = { KeySystem: keySystem }
48
+ if licenseUrl <> invalid and licenseUrl <> "" then
49
+ drmParams.licenseServerURL = licenseUrl
50
+ end if
51
+ headerName = pickArg(args, "input_license_header_name", invalid)
52
+ headerValue = pickArg(args, "input_license_header_value", invalid)
53
+ if headerName <> invalid and headerName <> "" then
54
+ drmParams.licenseReqHeaders = [ { name: headerName, value: headerValue } ]
55
+ end if
56
+ content.drmParams = drmParams
57
+ end if
58
+
59
+ m.video.content = content
60
+ m.video.setFocus(true)
61
+ m.video.control = "play"
62
+ m.top.probeState = "requested"
63
+ end sub
64
+
65
+ function pickArg(args as object, primary as string, fallback as dynamic) as dynamic
66
+ if args.doesExist(primary) then return args[primary]
67
+ if fallback <> invalid and args.doesExist(fallback) then return args[fallback]
68
+ return invalid
69
+ end function
70
+
71
+ sub onVideoState()
72
+ st = m.video.state
73
+ m.top.probeState = st
74
+ ' Mirror to console (port 8085) so the error can be captured without RTA.
75
+ print "[StreamProbe] state changed "; st
76
+ if st = "error" then
77
+ m.top.probeErrorCode = m.video.errorCode
78
+ m.top.probeErrorMsg = m.video.errorMsg
79
+ info = m.video.errorInfo
80
+ print "[StreamProbe] errorCode = "; m.video.errorCode
81
+ print "[StreamProbe] errorMsg = "; m.video.errorMsg
82
+ if info <> invalid then
83
+ m.top.probeErrorInfo = formatJSON(info)
84
+ print "[StreamProbe] errorInfo = "; m.top.probeErrorInfo
85
+ end if
86
+ end if
87
+ end sub
88
+
89
+ ]]>
90
+ </script>
91
+
92
+ <children>
93
+ <Video id="probeVideo" width="1920" height="1080" />
94
+ </children>
95
+
96
+ </component>
@@ -0,0 +1,11 @@
1
+ title=StreamProbe
2
+ subtitle=roku-mcp stream diagnoser harness
3
+ major_version=1
4
+ minor_version=0
5
+ build_version=00001
6
+
7
+ ui_resolutions=fhd
8
+ rsg_version=1.2
9
+
10
+ requires_widevine_drm=1
11
+ requires_widevine_version=1.0
@@ -0,0 +1,29 @@
1
+ ' StreamProbe — minimal harness used by roku-mcp's roku_diagnose_stream tool to
2
+ ' attempt playback of an arbitrary stream and expose the Video node's error
3
+ ' fields for capture over RTA.
4
+
5
+ sub Main(args as dynamic)
6
+ showStreamProbe(args)
7
+ end sub
8
+
9
+ sub showStreamProbe(args as dynamic)
10
+ screen = CreateObject("roSGScreen")
11
+ m.port = CreateObject("roMessagePort")
12
+ screen.setMessagePort(m.port)
13
+ scene = screen.CreateScene("StreamProbe")
14
+ screen.show()
15
+ ' vscode_rdb_on_device_component_entry
16
+
17
+ ' Forward deep-link launch args (input_url, input_format, input_*) to the scene.
18
+ if args <> invalid
19
+ scene.launchArgs = args
20
+ end if
21
+
22
+ while true
23
+ msg = wait(0, m.port)
24
+ msgType = type(msg)
25
+ if msgType = "roSGScreenEvent"
26
+ if msg.isScreenClosed() then return
27
+ end if
28
+ end while
29
+ end sub
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC;AAUvB,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,QAAQ,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAelF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC;AAWvB,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,QAAQ,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgBlF"}
package/dist/index.js CHANGED
@@ -8,10 +8,11 @@ import { registerConsoleTools } from './tools/console.js';
8
8
  import { registerBsprofTools } from './tools/bsprof.js';
9
9
  import { registerPerfettoTools } from './tools/perfetto.js';
10
10
  import { registerEditUiTools } from './tools/edit-ui.js';
11
+ import { registerStreamTools } from './tools/stream.js';
11
12
  export default function createServer(_options) {
12
13
  const server = new McpServer({
13
14
  name: 'roku-mcp',
14
- version: '1.6.0',
15
+ version: '1.8.3',
15
16
  });
16
17
  registerDeployTools(server);
17
18
  registerEcpTools(server);
@@ -20,6 +21,7 @@ export default function createServer(_options) {
20
21
  registerBsprofTools(server);
21
22
  registerPerfettoTools(server);
22
23
  registerEditUiTools(server);
24
+ registerStreamTools(server);
23
25
  return server.server;
24
26
  }
25
27
  const isDirectRun = typeof process !== 'undefined' &&
@@ -30,7 +32,7 @@ if (isDirectRun) {
30
32
  const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
31
33
  const server = new McpServer({
32
34
  name: 'roku-mcp',
33
- version: '1.6.0',
35
+ version: '1.8.3',
34
36
  });
35
37
  registerDeployTools(server);
36
38
  registerEcpTools(server);
@@ -39,6 +41,7 @@ if (isDirectRun) {
39
41
  registerBsprofTools(server);
40
42
  registerPerfettoTools(server);
41
43
  registerEditUiTools(server);
44
+ registerStreamTools(server);
42
45
  const transport = new StdioServerTransport();
43
46
  await server.connect(transport);
44
47
  })();
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,QAA8C;IACjF,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACzB,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAChC,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAE5B,OAAO,MAAM,CAAC,MAAM,CAAC;AACvB,CAAC;AAED,MAAM,WAAW,GACf,OAAO,OAAO,KAAK,WAAW;IAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACf,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;AAEnF,IAAI,WAAW,EAAE,CAAC;IAChB,CAAC,KAAK,IAAI,EAAE;QACV,MAAM,EAAE,oBAAoB,EAAE,GAAG,MAAM,MAAM,CAAC,2CAA2C,CAAC,CAAC;QAC3F,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;YAC3B,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE,OAAO;SACjB,CAAC,CAAC;QAEH,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAC5B,gBAAgB,CAAC,MAAM,CAAC,CAAC;QACzB,uBAAuB,CAAC,MAAM,CAAC,CAAC;QAChC,oBAAoB,CAAC,MAAM,CAAC,CAAC;QAC7B,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAC5B,qBAAqB,CAAC,MAAM,CAAC,CAAC;QAC9B,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAE5B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;QAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAClC,CAAC,CAAC,EAAE,CAAC;AACP,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,QAA8C;IACjF,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACzB,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAChC,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAE5B,OAAO,MAAM,CAAC,MAAM,CAAC;AACvB,CAAC;AAED,MAAM,WAAW,GACf,OAAO,OAAO,KAAK,WAAW;IAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACf,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;AAEnF,IAAI,WAAW,EAAE,CAAC;IAChB,CAAC,KAAK,IAAI,EAAE;QACV,MAAM,EAAE,oBAAoB,EAAE,GAAG,MAAM,MAAM,CAAC,2CAA2C,CAAC,CAAC;QAC3F,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;YAC3B,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE,OAAO;SACjB,CAAC,CAAC;QAEH,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAC5B,gBAAgB,CAAC,MAAM,CAAC,CAAC;QACzB,uBAAuB,CAAC,MAAM,CAAC,CAAC;QAChC,oBAAoB,CAAC,MAAM,CAAC,CAAC;QAC7B,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAC5B,qBAAqB,CAAC,MAAM,CAAC,CAAC;QAC9B,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAC5B,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAE5B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;QAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAClC,CAAC,CAAC,EAAE,CAAC;AACP,CAAC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Parse a Charles Proxy session (or HAR capture) into normalized HTTP entries
3
+ * and classify each request as manifest / segment / license / key / other.
4
+ *
5
+ * Supported export formats:
6
+ * - Charles ".chlsj" (JSON Session File): either a single JSON array of
7
+ * request objects, or newline-delimited JSON (one object per line).
8
+ * - ".har" (HTTP Archive): the standard `{ log: { entries: [...] } }` shape
9
+ * produced by Charles "Export > HAR" and by browser devtools.
10
+ *
11
+ * NOTE: the binary ".chls" format is NOT supported (it is a proprietary binary
12
+ * blob). Callers should instruct the user to export as ".chlsj" or ".har".
13
+ *
14
+ * Charles .chlsj entry shape (varies slightly by version) roughly looks like:
15
+ * {
16
+ * "host": "...", "method": "GET", "path": "/x.m3u8",
17
+ * "scheme": "https", "status": 200,
18
+ * "request": { "header": { "headers": [{ "name", "value" }] }, "body": {...} },
19
+ * "response": { "header": { "headers": [...] }, "body": { "text"|"encoded", "contentType" } },
20
+ * "times": { "totalDuration": 123 }
21
+ * }
22
+ */
23
+ export interface NormalizedHttpEntry {
24
+ url: string;
25
+ host: string;
26
+ path: string;
27
+ method: string;
28
+ status: number;
29
+ reqHeaders: Record<string, string>;
30
+ respHeaders: Record<string, string>;
31
+ /** Decoded response body text when available (manifests, license errors). */
32
+ respBody?: string;
33
+ /** Response content-type, lowercased. */
34
+ mimeType?: string;
35
+ durationMs?: number;
36
+ kind: HttpEntryKind;
37
+ }
38
+ export type HttpEntryKind = 'manifest' | 'segment' | 'license' | 'key' | 'other';
39
+ export interface CharlesSession {
40
+ entries: NormalizedHttpEntry[];
41
+ /** Entries with a non-2xx status or otherwise suspicious response. */
42
+ failing: NormalizedHttpEntry[];
43
+ format: 'chlsj' | 'har';
44
+ notes: string[];
45
+ }
46
+ /** Parse the raw text of a Charles .chlsj or .har file. */
47
+ export declare function parseCharlesText(text: string): CharlesSession;
48
+ /** A request is "failing" if it has a non-2xx status, or an empty body where one is expected. */
49
+ export declare function isFailing(entry: NormalizedHttpEntry): boolean;
50
+ //# sourceMappingURL=charles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"charles.d.ts","sourceRoot":"","sources":["../../src/stream/charles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,aAAa,CAAC;CACrB;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,KAAK,GAAG,OAAO,CAAC;AAEjF,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,sEAAsE;IACtE,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,MAAM,EAAE,OAAO,GAAG,KAAK,CAAC;IACxB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAqKD,2DAA2D;AAC3D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CA4D7D;AAQD,iGAAiG;AACjG,wBAAgB,SAAS,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAQ7D"}