BrightScript & Kopytko language support for Visual Studio Code — syntax highlighting, IntelliSense, diagnostics, formatting, linting, and a built-in Roku debugger.
- Syntax highlighting — full TextMate grammar for
.brsfiles including@import/@mockannotations,as <type>, andmvariable scopes - IntelliSense — completions for 63
ro*components, 81 interfaces, 662 methods, 59 built-in functions, keywords, and user-defined functions CreateObjecttype inference — member completions appear automatically after typing.on a variable created viaCreateObjectm.topmember completion — auto-completes fields from your XML interface, parent components, and the 88-type SceneGraph node catalog- Kopytko module support — module export completions with auto-insert
@import,@import/@mockpath completions and snippets - Hover, go-to-definition, signature help — for components, methods, built-ins, user functions, and Kopytko module exports
- Find All References, Rename, Call Hierarchy — workspace-wide and safe across files
- Type Hierarchy — navigate SceneGraph
extendschains up and down, from a component's.xmlor its.brs - Outline & Workspace Symbols — functions, subs, AA methods, and test cases via the Outline view or
Ctrl+T - Semantic tokens — parser-driven highlighting that separates params, locals, calls, and
m-fields - 32 diagnostic rules — undefined functions/variables, wrong argument count, unused imports/parameters,
CreateObjectvalidation,@importresolution, test framework checks, and more - Inline suppression —
' kopytko-disable-next-line <rule>and' kopytko-disable-line <rule>(glob patterns andremstyle supported; usedisable-next-lineon the preceding line for@import/@mockannotations) - Code actions — quick fixes for import diagnostics and unused parameters
- Document formatting — multi-pass engine with 49 configurable options (indentation, spacing, casing, blank lines, keyword style, and more)
- Configurable identifier casing — 10 casing dimensions (builtins, keywords, types, methods, user functions) with per-identifier overrides
- Kopytko Unit Testing Framework — test file detection,
@mocksupport,expect()matcher completions, test case symbols in Outline
- Roku device discovery — SSDP network scanning with auto-rescan, manual entry, password management, and per-device environment selection
- BrightScript debugger — deploy, set breakpoints (conditional, hit-count, exception), inspect variables, step through code, REPL, multi-thread support
- Web-admin automation — install, delete, rekey, package, screenshot, download profiling data, check for updates, and reboot, straight from the editor
- Device Manager — on-screen remote, keyboard mode, saved text entries, RASP automation scripts, and a device abilities hub
- Kopytko Console — interactive debug terminal for the Roku consoles (ports 8085 and 8080) with command completion, severity colouring, and filtering
- Runtime diagnostics — live CPU, memory, SceneGraph node, object, texture, and rendezvous charts recorded to replayable NDJSON sessions
- Kopytko Perfetto — app tracing streamed from the device into an embedded Perfetto UI, plus heap snapshots
- Network Inspector — intercepting HTTP proxy with no-CA
https↔httpbridging, transparent OS-level redirect, rewrite/block/latency/breakpoint rules, and HAR export - SceneGraph Tree — fetch the live node tree as XML or an icicle chart; Edit mode pushes field changes to the running app via RALE TrackerTask
- Deep Linking — launch channels with
contentId/roInputparameters and saved parameter sets - Roku Pay Web Services — call Roku Pay cloud endpoints with saved credential profiles and a request history
See docs/features.md for the full feature list with status and links to detailed documentation.
The extension is built on four packages, each published to npm and usable on its own in CI pipelines and build tools:
| Package | Description |
|---|---|
| kopytko-brightscript-parser | Lossless BrightScript lexer, CST, typed AST, scope/type analysis, and the built-in + component + SceneGraph catalogs |
| kopytko-formatter | BrightScript formatter — kopytko-format --check / --write for CI, plus a library API |
| kopytko-linter | BrightScript linter with 32 rules and inline suppression comments — kopytko-lint --check for CI, SARIF output for GitHub Code Scanning |
| kopytko-roku-device | Roku device communication toolkit — SSDP, ECP, debug console/protocol, web-admin automation, diagnostic collectors, Perfetto, and the kopytko-roku CLI |
All settings use the kopytko. prefix in .vscode/settings.json or VS Code user settings.
The 50 formatting options (kopytko.format.*) are documented in the kopytko-formatter README, and lint rule severities in the kopytko-linter README. For every setting in one table, see docs/reference/commands-and-settings.md.
| Setting | Type | Default | Description |
|---|---|---|---|
kopytko.languageServer.enabled |
boolean |
true |
Enable the Kopytko language server. |
kopytko.languageServer.trace |
enum |
"off" |
Trace LSP communication for debugging. |
| Setting | Type | Default | Description |
|---|---|---|---|
kopytko.debug.trace |
enum |
"off" |
Trace the Roku debug protocol (port 8081) to the Kopytko output channel. Use when a debug session disconnects or the channel freezes on resume. |
| Setting | Type | Default | Description |
|---|---|---|---|
kopytko.imports.resolveModules |
boolean |
true |
Resolve @import annotations from installed Kopytko NPM modules. |
kopytko.imports.sourceDir |
string |
"app" |
Root source directory for resolving internal @import paths (matches .kopytkorc sourceDir). |
kopytko.imports.generatedPaths |
array |
[] |
Glob patterns for @import paths that are generated during the build process and do not exist as source files on disk (e.g. by a Kopytko plugin or code-generation tool). Unresolved imports matching these patterns are shown as informational hints instead of warnings, so the developer is aware of the situation without it being flagged as an error. Supported wildcards: * matches any characters except /; ** matches any characters including /. Examples: - /components/generated/** - **/auto-generated/*.brs |
kopytko.imports.generatedModules |
array |
[] |
Declarations for @import paths that are generated during the build process and whose function names should be known to the language server. Each entry has a path glob pattern and a functions list. When a file imports a path matching path, the listed functions are added to the known function scope — so they are not flagged as undefined — and the import itself is shown as an informational hint rather than an unresolved warning. Use this when a plugin or code-generation tool creates .brs files that are not present as source files on disk but will exist at build time. Example: json [ { "path": "/components/generated/PluginApi.brs", "functions": ["PluginInit", "PluginGetData"] } ] |
kopytko.imports.siblingPatterns |
array |
[] |
Groups of file name patterns whose .brs files share import scope for the import/unused diagnostic check. Files in the same group are always loaded together by the same XML component, so an @import in one file is considered used if any file in the group references one of its exported functions. Pattern syntax: each pattern may contain a single * wildcard; files with the same wildcard value in the same directory are considered siblings. Example — Kopytko component/template pairs: json [ ["*.component.brs", "*.template.brs"], ["*.view.brs", "*.template.brs"] ] With this config, if Foo.component.brs imports helperFn and only Foo.template.brs calls it, the import is not flagged as unused. |
| Setting | Type | Default | Description |
|---|---|---|---|
kopytko.casing.builtin |
enum |
"preserve" |
Casing applied to BrightScript built-in function names when a completion is inserted. |
kopytko.casing.keyword |
enum |
"preserve" |
Casing applied to BrightScript keywords when a completion is inserted. Acts as fallback for type, literal, and operator casing when those are not set. |
kopytko.casing.type |
enum |
"preserve" |
Casing for BrightScript type names (boolean, integer, string, void, …). Falls back to kopytko.casing.keyword when not set. |
kopytko.casing.literal |
enum |
"preserve" |
Casing for literal values (true, false, invalid). Falls back to kopytko.casing.keyword when not set. |
kopytko.casing.logicOperator |
enum |
"preserve" |
Casing for logic operators (and, or, not). Falls back to kopytko.casing.keyword when not set. |
kopytko.casing.mathOperator |
enum |
"preserve" |
Casing for math operators (mod). Falls back to kopytko.casing.keyword when not set. |
kopytko.casing.method |
enum |
"preserve" |
Casing applied to component method names when a completion is inserted. |
kopytko.casing.userFunction |
enum |
"preserve" |
Casing applied to user-defined function/sub names in completions and formatting. |
kopytko.casing.userMethod |
enum |
"preserve" |
Casing applied to user-defined associative array method names in completions. |
kopytko.casing.exact |
object |
{} |
Per-identifier casing overrides applied after all other casing rules. Keys are identifier names (case-insensitive), values are the exact output string. Example: json { "invalid": "Invalid", "getglobalaa": "GetGlobalAA" } |
| Setting | Type | Default | Description |
|---|---|---|---|
kopytko.deviceDiscovery.enabled |
boolean |
true |
Enable automatic Roku device discovery via SSDP. |
kopytko.deviceDiscovery.scanTimeout |
number |
5000 |
Timeout in milliseconds for active SSDP device scans. |
kopytko.deviceDiscovery.showNotifications |
boolean |
true |
Show notification messages when devices come online or go offline. |
| Setting | Type | Default | Description |
|---|---|---|---|
kopytko.deviceManager.holdThresholdMs |
number |
1000 |
Remote Control: how long (ms) a button must be held before a press becomes a keydown/keyup hold instead of a single keypress. |
kopytko.deviceManager.runner.pollIntervalMs |
number |
500 |
Script runner: how often (ms) to poll the device for wait_for_player_state / validate_streaming / launch completion. |
kopytko.deviceManager.runner.waitTimeoutSec |
number |
30 |
Script runner: how long (seconds) wait_for_player_state and validate_streaming wait for the expected player state before failing the step. |
| Setting | Type | Default | Description |
|---|---|---|---|
kopytko.diagnostics.outputDir |
string |
"debug" |
Folder (relative to the workspace root, or absolute) where diagnostics sessions are saved. Each session gets its own timestamped subfolder. |
kopytko.diagnostics.maxLivePoints |
number |
3600 |
Maximum number of samples kept in memory per stream for the live view. Older points are dropped from the live charts but remain on disk. |
kopytko.diagnostics.debugConsolePort |
number |
8080 |
TCP port of the Roku SceneGraph debug server used for chanperf / sgnodes / texture metrics. |
kopytko.diagnostics.collectors.memCpu.enabled |
boolean |
true |
Collect per-channel CPU and memory usage (chanperf). |
kopytko.diagnostics.collectors.memCpu.intervalMs |
number |
1000 |
Polling interval in milliseconds for per-channel CPU/memory. |
kopytko.diagnostics.collectors.nodeCounts.enabled |
boolean |
true |
Collect SceneGraph node counts by type (sgnodes counts). |
kopytko.diagnostics.collectors.nodeCounts.intervalMs |
number |
2000 |
Polling interval in milliseconds for node counts. |
kopytko.diagnostics.collectors.objectCounts.enabled |
boolean |
true |
Collect BrightScript object counts by type via ECP /query/app-object-counts. Only polled while the Objects chart or table is visible in the panel (both hidden by default). |
kopytko.diagnostics.collectors.objectCounts.intervalMs |
number |
2000 |
Polling interval in milliseconds for object counts. |
kopytko.diagnostics.collectors.rendezvous.enabled |
boolean |
true |
Collect render-thread rendezvous via ECP. |
kopytko.diagnostics.collectors.rendezvous.intervalMs |
number |
1000 |
Polling interval in milliseconds for rendezvous events. |
kopytko.diagnostics.collectors.systemMem.enabled |
boolean |
false |
Collect device-wide memory (free). Off by default; per-channel memory is usually more useful. |
kopytko.diagnostics.collectors.systemMem.intervalMs |
number |
5000 |
Polling interval in milliseconds for device-wide memory. |
kopytko.diagnostics.collectors.textures.enabled |
boolean |
true |
Collect GPU texture/bitmap memory (r2d2_bitmaps). Enabled by default; the Textures chart/table only shows when selected in the Charts/Tables dropdowns. |
kopytko.diagnostics.collectors.textures.intervalMs |
number |
5000 |
Polling interval in milliseconds for texture memory. |
kopytko.diagnostics.collectors.appState.enabled |
boolean |
true |
Track app foreground/background state via ECP /query/app-state, shown as background shading on the charts. Requires "Control by mobile apps" enabled on the device — degrades to no shading (not an error) when unavailable. |
kopytko.diagnostics.collectors.appState.intervalMs |
number |
2000 |
Polling interval in milliseconds for app state. |
kopytko.diagnostics.collectors.fwBeacon.enabled |
boolean |
true |
Track framework beacon markers (AppLaunch/AppResume/VODStart Initiate/Complete) via ECP /fwbeacons. Enabled by default, including the Beacons checkbox. |
kopytko.diagnostics.collectors.fwBeacon.intervalMs |
number |
1000 |
Polling interval in milliseconds for framework beacon events. |
kopytko.diagnostics.defaultVisibleCharts |
array |
["memory","cpu","nodes"] |
Charts shown by default when the diagnostics panel opens. The corresponding data is only collected while at least one visible chart or table needs it. |
kopytko.diagnostics.defaultVisibleTables |
array |
["nodes","rendezvous"] |
Tables shown by default when the diagnostics panel opens. |
kopytko.diagnostics.memoryLimits.backgroundMB |
number |
100 |
Reference line on the Memory chart for Roku's published background-app DRAM guidance. Not device-reported (unlike the foreground limit, which comes from chanperf) — Roku publishes this as a fixed recommendation that may change between OS versions, hence configurable here. |
| Setting | Type | Default | Description |
|---|---|---|---|
kopytko.perfetto.ecpPort |
number |
8060 |
ECP port used for Perfetto tracing control and the WebSocket connection. |
kopytko.perfetto.refreshIntervalMs |
number |
3000 |
How often (in milliseconds) the live buffer is pushed to the Perfetto UI iframe. |
kopytko.perfetto.startCommand |
string |
"" |
Build and deploy command for Perfetto sessions. Defaults to npx kopytko start when empty. |
| Setting | Type | Default | Description |
|---|---|---|---|
kopytko.console.defaultPort |
enum |
8085 |
Debug console port the Kopytko Console selects when it opens. |
kopytko.console.autoConnect |
boolean |
false |
Connect to the active device automatically when the Kopytko Console panel opens. |
kopytko.console.maxLines |
number |
20000 |
Maximum number of output lines kept per console port. Older lines are dropped from the buffer. |
kopytko.console.reconnect |
boolean |
true |
Reconnect automatically (with backoff) when the console connection drops. |
kopytko.console.colorize |
boolean |
true |
Colour console output by severity — errors, warnings, beacons, rendezvous logs, and debugger frames. |
kopytko.console.logToFile |
boolean |
false |
Append every console line to a log file as it arrives, in addition to the in-memory buffer. |
kopytko.console.outputDir |
string |
"debug" |
Folder (relative to the workspace root, or absolute) where console logs and saved output are written. |
kopytko.console.historySize |
number |
200 |
Number of submitted commands remembered per console port for history recall. |
| Setting | Type | Default | Description |
|---|---|---|---|
kopytko.network.proxyPort |
number |
8888 |
Local port the Network Inspector capture proxy listens on. Device traffic is redirected here. |
kopytko.network.redirectPorts |
array |
[80] |
Device-side ports whose traffic is transparently redirected into the capture proxy (HTTP only). |
kopytko.network.maxEntries |
number |
5000 |
Maximum number of captured requests kept in memory before the oldest are dropped. |
kopytko.network.maxBufferBytes |
number |
52428800 |
Approximate memory budget in bytes for the captured-flow buffer (retained bodies + header overhead). Oldest requests are evicted when exceeded. 0 disables the byte cap; kopytko.network.maxEntries still applies. |
kopytko.network.upstreamKeepAlive |
boolean |
true |
Reuse upstream connections (HTTP keep-alive) between the capture proxy and origin servers, avoiding a fresh TCP/TLS handshake per request. The device-side connection is always closed per request. Disable if an origin misbehaves with connection reuse. |
kopytko.network.filterToActiveDevice |
boolean |
true |
Only show traffic originating from the active Roku device's IP (hides the dev machine's own traffic). |
kopytko.network.maxBodyBytes |
number |
262144 |
Maximum bytes of each request/response body retained in memory for inline display and HAR export. The full body is always forwarded to the device, and is persisted uncapped to the capture session folder (see kopytko.network.outputDir) for the “Open full body in editor” action. |
kopytko.network.outputDir |
string |
"debug" |
Folder (relative to the workspace root, or absolute) where Network Inspector capture sessions are saved — flow metadata plus full request/response bodies. Each capture session gets its own timestamped subfolder. |
kopytko.network.defaultUpstreamScheme |
enum |
"https" |
Scheme the proxy uses to reach an origin when the device called it over HTTP. https bridges the plaintext device call to a secure upstream connection. |
kopytko.network.winDivertDir |
string |
"" |
Windows only, and usually unnecessary — this extension bundles a working WinDivert driver for 64-bit Windows. Only set this to override it with a different WinDivert build/version, or on a non-x64 architecture the bundled driver doesn't cover. A local driver path is inherently per-machine, so this can only be set in User Settings, never in a workspace/team settings.json. |
kopytko.network.rewriteRules |
array |
[] |
Body rewrite rules applied to text request/response bodies. Each item: { enabled, direction: 'response'|'request', hostPattern?, pathPattern?, contentTypePattern?, find, replace, isRegex? }. When empty, the built-in https:// → http:// and wss:// → ws:// response rules are used. |
kopytko.network.rewriteExcludeRules |
array |
[] |
Opt specific host+path combinations out of ALL body rewriting (every enabled rewrite rule, both directions) — for a response that must reach the device byte-for-byte even though a broad rule like the built-in https→http one would otherwise match it. Each item: { enabled?, hostPattern?, pathPattern? }. |
kopytko.network.upstreamSchemes |
array |
[] |
Per-host upstream scheme overrides. Each item: { hostPattern, scheme: 'https'|'http'|'auto' }. |
kopytko.network.mapLocalRules |
array |
[] |
Serve a local file or inline body instead of contacting the upstream when host/path match. Each item: { enabled?, hostPattern, pathPattern?, filePath?, body?, contentType?, status? }. filePath wins over body; a rule with neither is ignored. |
kopytko.network.latencyRules |
array |
[] |
Delay matched responses by delayMs before sending them to the device — simulate slow backends. Each item: { enabled?, hostPattern, pathPattern?, delayMs }. |
kopytko.network.headerRules |
array |
[] |
Add/set/remove request or response headers for matched hosts. Each item: { enabled?, direction: 'request'|'response', hostPattern?, op: 'set'|'add'|'remove', name, value? }. Proxy-managed headers (content-length, transfer-encoding, connection, host) cannot be changed. |
kopytko.network.blockRules |
array |
[] |
Abort matched requests before they reach the upstream — the device sees a connection reset, for testing the channel's network-error handling. Each item: { enabled?, hostPattern, pathPattern? }. |
kopytko.network.breakpointRules |
array |
[] |
Pause matched requests and/or responses so you can edit them live before continuing. Each item: { enabled?, hostPattern, pathPattern?, onRequest?, onResponse? }. |
kopytko.network.breakpointTimeoutMs |
number |
30000 |
How long a breakpoint pause waits for you to act before auto-continuing the request/response unmodified, so a forgotten breakpoint can't hang the device. |
| Setting | Type | Default | Description |
|---|---|---|---|
kopytko.lint.readOnlyPaths |
array |
[] |
Glob patterns for files that the linter should skip. When set, overrides kopytko.readOnlyPaths for linting. When empty, falls back to kopytko.readOnlyPaths. |
| Setting | Type | Default | Description |
|---|---|---|---|
kopytko.readOnlyPaths |
array |
[] |
Glob patterns for files that the extension should treat as read-only. Acts as a shared fallback — applies to both formatting and linting unless overridden by kopytko.format.readOnlyPaths or kopytko.lint.readOnlyPaths. Supported wildcards: * matches any characters except /; ** matches any characters including /. Examples: json [ "**/node_modules/**", "**/generated/**", "**/vendor/*.brs" ] |
The Roku Devices panel in the Kopytko sidebar automatically discovers Roku devices on your local network using SSDP. Devices are listed with their model, serial number, and firmware version.
- Auto-scan — rescans on network changes and wake from sleep
- Manual entry — add a device by IP if not discovered automatically
- Secure passwords — stored in your OS keychain via VS Code's SecretStorage
- Active device — right-click → Set as Active Device to set the default deploy target
- Environment selection — choose a
.kopytkorcenvironment per device - Context menu — copy IP, open web portal, set password, view registry, upload package
Enable developer mode on the Roku: on the remote press Home × 3, Up, Right, Left, Right, Left, Right. Note the device IP and set a developer password.
Create .vscode/launch.json (or use Run → Add Configuration):
{
"version": "0.2.0",
"configurations": [
{
"type": "kopytko",
"request": "launch",
"name": "Run on Roku",
"rootDir": "${workspaceFolder}"
}
]
}Tip: If you have an active device selected in the Roku Devices panel with a stored password and environment,
host,password, andenvare all filled in automatically. A minimal config only needstype,request,name, androotDir.
| Property | Required | Default | Description |
|---|---|---|---|
host |
No | — | IP address of the Roku device. Auto-filled from the active device if omitted. |
password |
No | — | Developer password. Auto-filled from the active device's stored credentials if omitted. |
rootDir |
No | ${workspaceFolder} |
Project root where .kopytkorc lives. |
env |
No | — | Kopytko environment to build (matches .kopytkorc environments key). Auto-filled from the active device's environment selection if omitted. |
stopOnEntry |
No | false |
Pause execution at the first line of main on launch. |
startCommand |
No | npx kopytko start |
Command to build and deploy. Must accept env as a positional argument and ROKU_IP, ROKU_DEV_PASSWORD, ENV as environment variables. |
- Injects
remotedebug=1into the manifest (enables socket-based debug protocol) - Runs
kopytko startto build and deploy the app to the Roku - Restores the original manifest
- Connects to the debug protocol on TCP port 8081
| Capability | How |
|---|---|
| Inspect variables | Variables panel — Local scope with typed, expandable containers |
| View call stack | Call Stack panel — multi-thread support (SceneGraph threads) |
| Set breakpoints | Click the gutter — conditional, hit-count, and exception breakpoints supported |
| Step over / into / out | F10 / F11 / Shift+F11 |
| Continue / Pause | F5 / F6 |
| Evaluate expressions | Hover over a variable, or use the Debug Console (REPL) |
See print output |
Debug Console — stdout forwarded in real time |
| See compilation errors | Shown as VS Code diagnostics with file and line info |
See docs/roku-debug.md for full architecture and troubleshooting.
Start here: Feature overview · Commands & settings reference
Contributing and release process: CONTRIBUTING.md · docs/publishing.md
See CONTRIBUTING.md for development setup, building, testing, and project layout.
Five packages, each released via its own GitHub Actions workflow (Actions tab → Run workflow → pick patch/minor/major). Every workflow runs tests, bumps the version, updates the changelog, tags the release, publishes, creates a GitHub Release, and — for the four npm packages — auto-bumps the root package.json dependency once npm has propagated the new version.
| Workflow | Publishes | Tag |
|---|---|---|
| Release kopytko-brightscript-parser | npm (OIDC provenance) | kopytko-brightscript-parser-v{x.y.z} |
| Release kopytko-formatter | npm (OIDC provenance) | kopytko-formatter-v{x.y.z} |
| Release kopytko-linter | npm (OIDC provenance) | kopytko-linter-v{x.y.z} |
| Release kopytko-roku-device | npm (OIDC provenance) | kopytko-roku-device-v{x.y.z} |
| Release vscode-kopytko | VS Code Marketplace | v{x.y.z} |
kopytko-brightscript-parser has no dependency on the other packages, but kopytko-formatter and kopytko-linter both depend on it — kopytko-roku-device is fully independent (see Standalone Tools).
- If the parser changed: release
kopytko-brightscript-parserfirst - Release
kopytko-formatter/kopytko-linter(if changed) — their dependency on the parser auto-bumps once it's published - Release
kopytko-roku-device(if changed) — independent of the above - Last: release
vscode-kopytko, once every package it depends on that changed has been published and its root dependency bump has landed onmain
Skipping a package release before releasing the extension is the most common way to ship a broken build — the extension's own tests run against whatever is currently on npm, not local source, so a source fix with no matching package release doesn't reach users and can fail the extension's own CI.
| Secret | Purpose |
|---|---|
VSCE_PAT |
Azure DevOps PAT with Marketplace (Manage) scope |
npm uses OIDC provenance — no token needed. See docs/publishing.md for full setup, the complete publishing order, and manual (non-Actions) release steps.
