roku-mcp 1.6.0 → 1.7.0

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
@@ -2,23 +2,23 @@
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
6
 
7
- ```bash
8
- npm install -g roku-mcp
9
- ```
10
-
11
- Or run directly with npx (no install required):
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.
12
8
 
13
9
  ```bash
14
- npx roku-mcp
10
+ npm install -g roku-mcp # optional, global install
11
+ # or just point your client at: npx -y roku-mcp
15
12
  ```
16
13
 
17
- ## Client Configuration
14
+ Configure your AI client below. Every platform uses the same launch line; only the config file and reload step change.
15
+
16
+ <details>
17
+ <summary><strong>Cursor</strong> — agent-mode tools, auto-discovers MCP servers</summary>
18
18
 
19
- ### Cursor
19
+ **Prerequisites:** Cursor 0.45+ with Agent mode.
20
20
 
21
- 1. Create `.cursor/mcp.json` in your project root:
21
+ **Install:** create `.cursor/mcp.json` in your project root (or `~/.cursor/mcp.json` for global):
22
22
 
23
23
  ```json
24
24
  {
@@ -35,14 +35,22 @@ npx roku-mcp
35
35
  }
36
36
  ```
37
37
 
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.
38
+ Then reload the window (`Cmd+Shift+P` / `Ctrl+Shift+P` → **Developer: Reload Window**).
39
+
40
+ **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.
41
+
42
+ **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.
43
+
44
+ **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.
40
45
 
41
- ### VS Code
46
+ </details>
42
47
 
43
- Requires VS Code **1.99+** with the GitHub Copilot extension.
48
+ <details>
49
+ <summary><strong>VS Code (GitHub Copilot)</strong> — agent mode, MCP via <code>.vscode/mcp.json</code></summary>
44
50
 
45
- 1. Create `.vscode/mcp.json` in your project root:
51
+ **Prerequisites:** VS Code 1.99+ with the GitHub Copilot extension and an active Copilot subscription that includes Agent mode.
52
+
53
+ **Install:** create `.vscode/mcp.json` in your project root:
46
54
 
47
55
  ```json
48
56
  {
@@ -59,12 +67,21 @@ Requires VS Code **1.99+** with the GitHub Copilot extension.
59
67
  }
60
68
  ```
61
69
 
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.
70
+ Reload the window (`Cmd+Shift+P` / `Ctrl+Shift+P` → **Developer: Reload Window**).
71
+
72
+ **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`.
73
+
74
+ **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.
75
+
76
+ </details>
77
+
78
+ <details>
79
+ <summary><strong>Claude Desktop</strong> — official Anthropic desktop app</summary>
80
+
81
+ **Prerequisites:** Claude Desktop (latest from claude.ai/download), Node.js 18+ on `PATH`.
64
82
 
65
- ### Claude Desktop
83
+ **Install:** edit your Claude Desktop config:
66
84
 
67
- Add to your Claude Desktop config file:
68
85
  - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
69
86
  - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
70
87
 
@@ -83,11 +100,20 @@ Add to your Claude Desktop config file:
83
100
  }
84
101
  ```
85
102
 
86
- Restart Claude Desktop after saving.
103
+ Fully quit and restart Claude Desktop (the menu-bar icon must close — `Cmd+Q` on macOS).
87
104
 
88
- ### Windsurf
105
+ **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.
89
106
 
90
- Create `.windsurf/mcp.json` in your project root:
107
+ **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"]`.
108
+
109
+ </details>
110
+
111
+ <details>
112
+ <summary><strong>Windsurf</strong> — Codeium's agentic IDE</summary>
113
+
114
+ **Prerequisites:** Windsurf with Cascade enabled.
115
+
116
+ **Install:** create `.windsurf/mcp.json` in your project root (or `~/.codeium/windsurf/mcp_config.json` globally):
91
117
 
92
118
  ```json
93
119
  {
@@ -104,18 +130,75 @@ Create `.windsurf/mcp.json` in your project root:
104
130
  }
105
131
  ```
106
132
 
107
- Reload the window after saving.
133
+ Reload the window. In Cascade settings, refresh the MCP server list.
134
+
135
+ **Verify:** ask Cascade "list available MCP tools" — `roku_*` entries should appear.
136
+
137
+ **Troubleshoot:** Windsurf occasionally caches a stale server. If reloads don't pick up edits, fully quit Windsurf and reopen.
108
138
 
109
- ### Any MCP-compatible client
139
+ </details>
110
140
 
111
- The server uses **stdio transport**. Any client that supports MCP can launch it with:
141
+ <details>
142
+ <summary><strong>Any MCP-compatible client</strong> (Continue, Cline, Zed, Codex, Claude Code, OpenCode, …)</summary>
143
+
144
+ The server speaks the standard MCP **stdio** transport. Anywhere you can declare:
112
145
 
113
146
  ```
114
147
  command: npx
115
148
  args: ["-y", "roku-mcp"]
149
+ env: { ROKU_DEVICE_HOST: "...", ROKU_DEVICE_PASSWORD: "..." }
116
150
  ```
117
151
 
118
- Pass `ROKU_DEVICE_HOST` and `ROKU_DEVICE_PASSWORD` as environment variables, or omit the host to use SSDP auto-discovery.
152
+ …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.
153
+
154
+ 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.
155
+
156
+ </details>
157
+
158
+ ## Agent rules
159
+
160
+ 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.
161
+
162
+ | Agent | File | Install path |
163
+ |---|---|---|
164
+ | Cursor | [`rules/cursor/roku-mcp.mdc`](./rules/cursor/roku-mcp.mdc) | `.cursor/rules/roku-mcp.mdc` |
165
+ | Claude Code | [`rules/claude-code/CLAUDE.md`](./rules/claude-code/CLAUDE.md) | `CLAUDE.md` (repo root) |
166
+ | Claude Desktop | [`rules/claude-desktop/roku-mcp.md`](./rules/claude-desktop/roku-mcp.md) | Settings → System Prompt |
167
+ | Windsurf | [`rules/windsurf/roku-mcp.md`](./rules/windsurf/roku-mcp.md) | `.windsurf/rules/roku-mcp.md` |
168
+ | Codex CLI / OpenCode / Aider | [`AGENTS.md`](./AGENTS.md) (repo root) | `AGENTS.md` (repo root) |
169
+
170
+ See [`rules/README.md`](./rules/README.md) for one-line copy commands per agent.
171
+
172
+ ## Quickstart
173
+
174
+ Once your client is wired up, these three prompts work out of the box and exercise the main capability groups.
175
+
176
+ **1. Confirm the device is reachable**
177
+
178
+ ```
179
+ Use roku-mcp to query my Roku device info and tell me the model, firmware, and IP.
180
+ ```
181
+
182
+ The agent should call `roku_query_device_info` (and possibly `roku_discover` if no host is configured) and reply in one turn.
183
+
184
+ **2. Drive the UI with screenshots**
185
+
186
+ ```
187
+ Press Home, wait 2 seconds, take a screenshot, then move Right twice and
188
+ screenshot again. Describe what changed between the two screenshots.
189
+ ```
190
+
191
+ This exercises `roku_keypress`, `roku_sleep`, and `roku_screenshot` — the standard *observe → act → wait → verify* loop.
192
+
193
+ **3. Live-edit a running channel (requires RTA — see [Runtime UI Editing](#runtime-ui-editing))**
194
+
195
+ ```
196
+ On the currently running dev channel, find the label that shows the page
197
+ title, change its text to "Hello from MCP", verify the change with
198
+ roku_get_value, then change it back.
199
+ ```
200
+
201
+ 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
202
 
120
203
  ## Environment Variables
121
204
 
@@ -281,6 +364,16 @@ Record, analyze, and compare Perfetto traces from Roku devices. Requires **Roku
281
364
 
282
365
  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
366
 
367
+ ### Stream Diagnosis
368
+
369
+ Figure out why an HLS/DASH stream that plays everywhere else fails on Roku.
370
+
371
+ | Tool | Description |
372
+ |---|---|
373
+ | `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. |
374
+
375
+ 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.
376
+
284
377
  ## Requirements
285
378
 
286
379
  - 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.7.0',
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.7.0',
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"}