Open-source engine for programmatically creating, editing, and repacking Elgato Stream Deck .streamDeckProfile files. Define your shortcuts in JSON, generate icons, and produce ready-to-import profiles — no manual button-by-button setup required.
- MCP server — 9 tools for Claude Desktop and Claude Code (extract, pack, validate, list, add shortcut, generate icons, list shortcuts, quality check, live test)
- Claude Code plugin — install once, get all tools + the
generate-profileskill - Extract → Modify → Pack pipeline for
.streamDeckProfileZIP archives - Hotkey buttons with full modifier support (Ctrl, Shift, Alt, Win)
- Multi-action buttons for chord shortcuts (e.g.,
Ctrl+KthenCtrl+M) - Template system — clone a blank profile and populate it programmatically
- Icon generation — 144×144 SVG icons, color-coded by shortcut category
- Validation — verify profile structure, image refs, and grid bounds before packing
- Multi-device support — MK.2, XL, Mini, Stream Deck +, and Neo
- Pre-commit hook — auto-validates staged profiles on
git commit
# Install
npm install
# Extract an existing profile to inspect/edit
node src/index.js extract "My Profile.streamDeckProfile" my-profile
# View the button grid
node src/index.js list my-profile
# Validate structure
node src/index.js validate my-profile
# Pack back to importable file
node src/index.js pack my-profile "My Profile.streamDeckProfile"| Command | Description |
|---|---|
extract <profile> [outdir] |
Unzip .streamDeckProfile to an editable directory (default: _extracted) |
pack <dir> <output> |
Pack a directory back into a .streamDeckProfile ZIP |
validate <dir> |
Validate profile structure, manifests, image refs, and grid bounds |
list <dir> |
Print ASCII grid layout showing all pages and button labels |
All commands are run via node src/index.js <command> <args>.
src/mcp-server.js exposes 9 tools via stdio transport. Add to claude_desktop_config.json:
{
"mcpServers": {
"opendeck": {
"command": "node",
"args": ["C:/path/to/opendeck-factory/src/mcp-server.js"]
}
}
}| Tool | Description |
|---|---|
extract_profile |
Unzip a .streamDeckProfile to an editable directory |
pack_profile |
Pack an extracted directory back to .streamDeckProfile |
validate_profile |
Validate structure, manifests, image refs, and grid bounds |
list_profile |
Print ASCII grid of all pages and button labels |
add_shortcut |
Add a hotkey button at a specific grid position (bounds-validated) |
generate_icons |
Generate SVG icons from a data/shortcuts/<app>.json file |
list_shortcuts |
Read shortcut definitions for an app |
quality_check |
Run QC gate on a built profile (file size, button count, icon resolution) |
live_test_profile |
Install profile into ProfilesV3, restart Stream Deck app, verify button structure |
Install the plugin to get the MCP server + generate-profile skill in one step:
{
"plugins": [
{ "path": "C:/path/to/opendeck-factory/.claude-plugin/plugin.json" }
]
}npx skills add thisis-romar/opendeck-factory --skill generate-profile
npx skills add thisis-romar/opendeck-factory --skill capture-streamdeck-reference
npx skills add thisis-romar/opendeck-factory --skill drive-windows-guiSkills install to ~/.claude/skills/ (global) or .claude/skills/ (project). Works with vercel-labs/skills and any compatible agent runner.
Instead of manually placing buttons, define shortcuts in a JSON file and generate the entire profile automatically.
# Generate icons from shortcut definitions
node scripts/generate-icons.js vs-code
# Generate profile from shortcuts (creates from template if profile doesn't exist)
node scripts/generate-from-shortcuts.js vs-code --profile vs-code-colored
# Validate and pack
node src/index.js validate profiles/vs-code-colored
node src/index.js pack profiles/vs-code-colored "builds/VS Code Colored.streamDeckProfile"Create data/shortcuts/<app-name>.json:
{
"metadata": {
"displayName": "VS Code",
"source": "VS Code Default Keybindings (Windows)",
"categoryStyles": {
"general": { "titleColor": "#4FC3F7", "fontSize": 11 },
"navigation": { "titleColor": "#81C784", "fontSize": 11 }
}
},
"shortcuts": [
{
"command": "workbench.action.showCommands",
"label": "Command\nPalette\n",
"key": "P",
"modifiers": { "ctrl": true, "shift": true },
"category": "general",
"priority": 1
},
{
"command": "workbench.action.toggleMaximizeEditorGroup",
"label": "Maximize\nGroup\n",
"type": "chord",
"chordKeys": [
{ "key": "K", "ctrl": true },
{ "key": "M", "ctrl": true }
],
"category": "chord",
"priority": 1
}
]
}Fields:
label— Button title. Use\nfor line breaks (max 3 lines). Must end with\n.key— Key name matching a key insrc/constants.jsKEY_CODES(e.g.,P,F5,BACKTICK)modifiers—{ ctrl, shift, alt, win }booleanscategory— Used for sorting and icon color:general,navigation,view,editing,debug,terminal,search,file,editor,chordpriority— Sort order within category (lower = first)type: "chord"+chordKeys— For multi-action buttons that send sequential keypresses
opendeck-factory/
├── src/ Core modules
│ ├── index.js CLI entry point
│ ├── profile.js ProfileEditor class
│ ├── hotkey.js Hotkey builder (key codes → action JSON)
│ ├── constants.js Key codes, device models, plugin templates
│ ├── extract.js ZIP extraction
│ ├── pack.js ZIP packing
│ ├── validate.js Profile structure validation
│ └── images.js Image file handling
├── scripts/ Generation & utility scripts
│ ├── generate-from-shortcuts.js Build profile from shortcut JSON
│ ├── generate-icons.js Generate category-colored SVG icons
│ ├── fill-grid.js Fill empty slots with placeholders
│ └── fix-typo.js Fix label typos in profiles
├── profiles/ Extracted profile directories
│ └── _template/ Blank template (cloned for new profiles)
├── data/ Shortcut data and icon assets (see catalog repo)
├── docs/
│ └── obsidian-vault/ Technical docs (file format, key codes, API, patterns)
└── package.json Node.js ESM, single dependency: adm-zip
The ProfileEditor class (src/profile.js) provides the programmatic interface for building profiles.
import { ProfileEditor } from './src/profile.js';
// Create a new profile from the blank template
const editor = ProfileEditor.initFromTemplate(
'profiles/_template',
'profiles/my-app',
'My App'
);
// Or load an existing profile
const editor = new ProfileEditor('profiles/my-app');
// Add a hotkey button
editor.addHotkeyButton(pageUUID, col, row, {
label: 'Save\n',
key: 'S',
ctrl: true,
imagePath: '/path/to/icon.svg',
titleColor: '#4FC3F7',
});
// Add a multi-action button (chord shortcut)
editor.addMultiActionButton(pageUUID, col, row, {
label: 'Maximize\nGroup\n',
steps: [
{ key: 'K', ctrl: true },
{ key: 'M', ctrl: true },
],
});
// Query the grid
const pages = editor.getPageUUIDs();
const emptySlots = editor.getEmptyPositions(pageUUID);
const action = editor.getAction(pageUUID, col, row);
// Add pages dynamically
const newPages = editor.addPages(3);
// Save all changes
editor.save();| Method | Description |
|---|---|
initFromTemplate(templateDir, targetDir, name) |
Clone template, generate UUIDs, return new editor |
addHotkeyButton(page, col, row, opts) |
Add a single-keypress hotkey button |
addMultiActionButton(page, col, row, opts) |
Add a multi-action button for chord shortcuts |
addPages(count) |
Create additional blank pages, returns new UUIDs |
getPageUUIDs() |
List all page UUIDs in the profile |
getEmptyPositions(page) |
Find empty grid slots on a page |
getAction(page, col, row) |
Get the action definition at a position |
setAction(page, col, row, def) |
Set an action at a position |
removeAction(page, col, row) |
Remove an action from a position |
updateTitle(page, col, row, title) |
Change a button's label |
save() |
Write all changes to disk |
| Model ID | Device | Grid |
|---|---|---|
20GBA9901 / 20GBA9902 |
Stream Deck MK.2 | 5 × 3 (15 buttons) |
20GBA9911 |
Stream Deck XL | 8 × 4 (32 buttons) |
20GBA9903 |
Stream Deck Mini | 3 × 2 (6 buttons) |
10GBD9901 |
Stream Deck + | 4 × 2 (8 buttons) |
Letters: A–Z | Numbers: 0–9 | Function: F1–F12
Navigation: UP, DOWN, LEFT, RIGHT, HOME, END, PAGEUP, PAGEDOWN
Special: ENTER, ESCAPE, SPACE, TAB, BACKSPACE, DELETE
Punctuation: BACKTICK, MINUS, EQUALS, LBRACKET, RBRACKET, BACKSLASH, SEMICOLON, QUOTE, COMMA, PERIOD, SLASH
Modifiers: Ctrl, Shift, Alt, Win (bitmask flags: 2, 1, 4, 8)
Full mappings in src/constants.js.
Detailed technical documentation lives in docs/obsidian-vault/ as an Obsidian vault:
- File Format — ZIP structure, manifest JSON schema, action types, image naming
- Key Codes — Windows VKey codes, Qt key codes, modifier bitmask
- ProfileEditor API — Loading, adding buttons, querying, saving
- Patterns — Extract-Modify-Pack, Batch Add, Find and Fix
- Hardware — Stream Deck MK.2 specifications
- Always validate before packing:
node src/index.js validate <dir> - Profile version must be
"3.0"for Stream Deck app 7.1+ - Positions are
"col,row"format (column-first, 0-indexed) - Images should be 144×144 pixels (retina @2x for 72×72 display buttons)
See CONTRIBUTING.md. All commits must be signed off under the DCO 1.1.
The engine is released under the Functional Source License, Version 1.1 (FSL-1.1-ALv2) — source-available with a non-compete restriction that auto-expires two years after each release, at which point the code becomes available under the Apache License, Version 2.0. See LICENSE and NOTICE for full terms.
The commercial asset catalog (profiles, icon packs, templates) is maintained in a separate private repository under a proprietary commercial license. This repository contains only the open-source engine.
"Stream Deck" and "Elgato" are trademarks of Corsair Memory, Inc. and are used nominatively to describe compatibility. This project is not affiliated with, endorsed by, or sponsored by Corsair Memory, Inc. or Elgato.