Stream Deck plugin for AI-powered text processing. Captures selected text or clipboard, sends to AI API, and pastes the result.
Supported Providers: OpenAI, Anthropic, Google Gemini, OpenRouter, Custom endpoints
Actions:
- AI Text Action - Regular key with customizable prompts
- AI Prompt Selector - Stream Deck+ encoder with preset switching
com.dzarlax.ai-assistant.sdPlugin/
├── bin/
│ ├── launcher.js # Entry point (loads crash handler, then main)
│ ├── launcher.js.map
│ ├── main.js # Bundled plugin code (ESM)
│ └── main.js.map # Source map
├── imgs/ # Plugin icons
├── ui/
│ ├── index.html # Property Inspector
│ └── js/property-inspector.js
├── manifest.json
└── package.json # { "type": "module" }
src/
├── launcher.ts # Minimal entry point - loads crash handler, then main
├── crash-handler.ts # Catches uncaughtException / unhandledRejection, logs to /tmp/ai-assistant-crash.log
├── main.ts # Registers actions, connects to Stream Deck
├── plugin.ts # AITextAction, PromptSelectorAction classes
├── api-client.ts # AI API calls (OpenAI, Anthropic, Gemini, OpenRouter)
└── system-utils.ts # Clipboard & keyboard emulation
property-inspector/
├── index.html # Settings UI
└── js/property-inspector.js
UUID: com.dzarlax.ai-assistant.text-action
Regular key action with customizable prompts.
States:
- 0: Idle
- 1: Processing
- 2: Success
- 3: Error
UUID: com.dzarlax.ai-assistant.prompt-selector
Encoder/dial action for quick preset switching.
Controls:
- Rotate - Switch between presets
- Press (Dial) - Execute selected preset
- Tap (Touch) - Execute selected preset
LCD Display:
title- Preset namevalue- Position (e.g., "3/9") or statusindicator- Visual progress bar
| # | Key | Name | Post Action |
|---|---|---|---|
| 1 | fix-grammar | Fix Grammar | paste |
| 2 | translate-en | Translate EN | paste |
| 3 | translate-ru | Translate RU | paste |
| 4 | translate-sr | Translate SR | paste |
| 5 | translate-de | Translate DE | paste |
| 6 | summarize | Summarize | copy |
| 7 | explain-code | Explain Code | copy |
| 8 | professional | Professional | paste |
| 9 | casual | Casual | paste |
Presets are defined in src/plugin.ts as PRESETS array.
Add a new object to the PRESETS array:
const PRESETS = [
// ... existing presets ...
{
key: 'my-new-preset', // Unique identifier (kebab-case)
name: 'My New Preset', // Display name (shown on encoder LCD)
systemPrompt: 'You are a helpful assistant that...', // AI system instructions
userPromptTemplate: '{{text}}', // User prompt ({{text}} = captured text)
postAction: 'paste' as const // 'paste' or 'copy'
}
];Edit property-inspector/index.html - add option to the preset selector:
<select class="sdpi-item-value select" id="presetSelector">
<!-- ... existing options ... -->
<option value="my-new-preset">My New Preset</option>
</select>Edit property-inspector/js/property-inspector.js - add preset to PRESETS object:
const PRESETS = {
// ... existing presets ...
'my-new-preset': {
name: 'My New Preset',
systemPrompt: 'You are a helpful assistant that...',
userPromptTemplate: '{{text}}',
postAction: 'paste'
}
};npm run build
streamdeck restart com.dzarlax.ai-assistant| Field | Type | Description |
|---|---|---|
key |
string | Unique identifier, used in settings |
name |
string | Display name on encoder LCD |
systemPrompt |
string | AI system instructions |
userPromptTemplate |
string | Prompt template, use {{text}} for captured content |
postAction |
'paste' | 'copy' | What to do with result |
| Setting | Type | Default | Description |
|---|---|---|---|
| provider | string | "openai" | openai, anthropic, openrouter, custom |
| apiKey | string | - | API key |
| baseUrl | string | - | Custom endpoint |
| model | string | "gpt-4.1" | Model identifier |
| temperature | number | 0.7 | 0-2 |
| maxTokens | number | 4096 | Max response length |
| timeout | number | 30 | Seconds |
| Setting | Type | Default |
|---|---|---|
| systemPrompt | string | "You are a helpful assistant." |
| userPromptTemplate | string | "{{text}}" |
| inputMode | string | "selection" |
| postAction | string | "paste" |
| Setting | Type | Description |
|---|---|---|
| presetIndex | number | Current preset index within active presets |
| encoderPresets | EncoderPresetConfig[] | Per-preset config: { key, enabled, order }. If empty, all 9 presets are active |
gpt-5, gpt-4.1, gpt-4.1-mini, o3, o4-mini
claude-sonnet-4-20250514, claude-3-7-sonnet-latest, claude-3-5-haiku-20241022, claude-3-opus-20240229
gemini-3-flash-preview, gemini-2.5-pro, gemini-2.5-flash, gemini-2.5-flash-lite
anthropic/claude-sonnet-4, openai/gpt-4.1, openai/gpt-5, google/gemini-2.5-pro, meta-llama/llama-3.1-70b-instruct
Both actions use shared processWithAI() function:
- Get global settings (API key, model, etc.)
- Capture input (selection or clipboard)
- Build prompt from template
- Call AI API
- Output result (paste or copy)
copyToClipboard(text)- spawn + pbcopy with UTF-8 encodingpasteFromClipboard()- pbpaste with 10MB maxBuffersimulateCopy()- key code 8 + Cmd (layout-independent)simulatePaste()- key code 9 + Cmd (layout-independent)
await action.setFeedback({
title: preset.name,
value: `${index + 1}/9`,
indicator: Math.round(((index + 1) / 9) * 100)
});npm run build # Build plugin
npm run watch # Watch mode with auto-restart
npm run link # Link to Stream Deck# Plugin logs (crash handler)
cat /tmp/ai-assistant-crash.log
# Stream Deck logs
cat ~/Library/Logs/ElgatoStreamDeck/StreamDeck.json | grep ai-assistant
# Restart plugin
streamdeck restart com.dzarlax.ai-assistant{
"@elgato/streamdeck": "^1.4.1",
"axios": "^1.6.0"
}