Skip to content

Commit abdedcf

Browse files
Merge pull request #40 from FritzBlignaut/feature/discord-plugin
feat: add Discord plugin with keybinding functionality and pre-flight…
2 parents 61d8c2b + 9c8f43a commit abdedcf

6 files changed

Lines changed: 452 additions & 0 deletions

File tree

.github/copilot-instructions.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22

33
> **Precedence rule:** In case of conflict, Core Principles override Workflow Orchestration, which overrides Task Management defaults.
44
5+
---
6+
7+
## PRE-FLIGHT — MANDATORY ON EVERY PROMPT, NO EXCEPTIONS
8+
9+
Before writing any plan, any code, any response — even for read-only or trivial requests — execute ALL of the following steps in order:
10+
11+
1. **Read lessons** — Use `read_file` to load `.github/instructions/lessons.instructions.md` in full. Apply every relevant lesson to the current task.
12+
2. **Check branch** — Run `git branch --show-current` in the terminal. Show the result to the user.
13+
3. **Branch gate** — If the current branch is `develop` or `main`:
14+
- If the task could modify ANY file → invoke the **Branch Manager** agent immediately to create an appropriate branch, then run `git branch --show-current` again to confirm the switch before proceeding.
15+
- If the task is purely read-only (no file changes) → proceed, but state "read-only — no changes will be made."
16+
4. **After Branch Manager** — Always verify the branch switched with `git branch --show-current`. Never trust the agent's return message alone.
17+
18+
**This pre-flight is not optional, not skippable, and not shortened for "simple" tasks. Every prompt. Always.**
19+
20+
---
21+
522
## Workflow Orchestration
623

724
### 1. Plan Before Implementing

.github/instructions/lessons.instructions.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,16 @@ This ensures work is isolated, traceable to the issue, and can be reviewed via a
8888
- **Explore** — use for codebase research to keep the main context clean.
8989

9090
**Never skip an agent** because the task "seems simple." Predictability and consistency are the goal.
91+
92+
## 2026-05-25 — Pre-flight checklist is step 0, not optional, not abbreviated
93+
94+
**Mistake:** Trusted the Branch Manager agent's return message ("Branch created: feature/discord-plugin") as proof that the terminal was on that branch. Did not run `git branch --show-current` before making changes. Also did not read `lessons.instructions.md` at the start of the response. Changes landed on the correct branch by luck, but the process was broken.
95+
96+
**Rule:** The pre-flight block in `copilot-instructions.md` executes for EVERY prompt, including trivial ones. It is not a "session start" thing — it fires at the top of every single response before any planning or code. Steps are non-negotiable:
97+
98+
1. `read_file``.github/instructions/lessons.instructions.md` (full file, every prompt)
99+
2. Run `git branch --show-current` in the terminal, print the result
100+
3. If on `develop` or `main` and task may touch files → Branch Manager immediately, then verify branch AGAIN with `git branch --show-current`
101+
4. **Never trust an agent's success message as proof of branch switch** — always verify with the terminal command
102+
103+
**Enforcement:** The user must NEVER have to remind GitHub Copilot to follow this workflow. It is automatic, silent, and unconditional.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* com.discord.streamdeck — plugin.cjs
3+
*
4+
* Runs as a child process forked by the host app.
5+
* Controls Discord via xdotool keyboard shortcuts.
6+
*
7+
* Each button stores a `hotkey` in its settings (e.g. "ctrl+shift+m").
8+
* On keyDown the plugin fires `xdotool key <hotkey>`.
9+
* Push-to-Talk and Push-to-Mute use `xdotool keydown` / `xdotool keyup`
10+
* so the key is held for the duration of the button press.
11+
*
12+
* Requirements: xdotool must be installed (sudo apt install xdotool).
13+
*/
14+
15+
'use strict'
16+
17+
const { spawnSync, execFileSync } = require('child_process')
18+
19+
const pluginUUID = process.env.PLUGIN_UUID || 'com.discord.streamdeck'
20+
21+
// Actions that use hold-and-release rather than a single tap
22+
const HOLD_ACTIONS = new Set([
23+
'com.discord.streamdeck.ptt',
24+
'com.discord.streamdeck.ptm',
25+
])
26+
27+
// Verify xdotool is available at startup
28+
let xdotoolAvailable = false
29+
try {
30+
execFileSync('which', ['xdotool'], { stdio: 'ignore' })
31+
xdotoolAvailable = true
32+
console.log(`[${pluginUUID}] xdotool found — ready`)
33+
} catch {
34+
console.warn(`[${pluginUUID}] WARNING: xdotool not found. Install it with: sudo apt install xdotool`)
35+
}
36+
37+
function xdotoolKey(hotkey) {
38+
if (!xdotoolAvailable) return
39+
const result = spawnSync('xdotool', ['key', hotkey], { stdio: 'pipe' })
40+
if (result.status !== 0) {
41+
console.warn(`[${pluginUUID}] xdotool key failed for "${hotkey}":`, result.stderr?.toString().trim())
42+
}
43+
}
44+
45+
function xdotoolKeyDown(hotkey) {
46+
if (!xdotoolAvailable) return
47+
const result = spawnSync('xdotool', ['keydown', hotkey], { stdio: 'pipe' })
48+
if (result.status !== 0) {
49+
console.warn(`[${pluginUUID}] xdotool keydown failed for "${hotkey}":`, result.stderr?.toString().trim())
50+
}
51+
}
52+
53+
function xdotoolKeyUp(hotkey) {
54+
if (!xdotoolAvailable) return
55+
const result = spawnSync('xdotool', ['keyup', hotkey], { stdio: 'pipe' })
56+
if (result.status !== 0) {
57+
console.warn(`[${pluginUUID}] xdotool keyup failed for "${hotkey}":`, result.stderr?.toString().trim())
58+
}
59+
}
60+
61+
process.on('message', (msg) => {
62+
if (!msg?.event) return
63+
64+
const hotkey = msg.settings?.hotkey
65+
const actionUUID = msg.actionUUID || ''
66+
67+
if (msg.event === 'keyDown') {
68+
if (!hotkey) {
69+
console.warn(`[${pluginUUID}] keyDown on ${actionUUID} — no hotkey configured`)
70+
return
71+
}
72+
console.log(`[${pluginUUID}] keyDown ${actionUUID}${HOLD_ACTIONS.has(actionUUID) ? 'keydown' : 'key'} "${hotkey}"`)
73+
if (HOLD_ACTIONS.has(actionUUID)) {
74+
xdotoolKeyDown(hotkey)
75+
} else {
76+
xdotoolKey(hotkey)
77+
}
78+
}
79+
80+
if (msg.event === 'keyUp') {
81+
if (!hotkey) return
82+
if (HOLD_ACTIONS.has(actionUUID)) {
83+
console.log(`[${pluginUUID}] keyUp ${actionUUID} → keyup "${hotkey}"`)
84+
xdotoolKeyUp(hotkey)
85+
}
86+
}
87+
})
88+
89+
// Keep the process alive
90+
setInterval(() => {}, 60_000)
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
{
2+
"UUID": "com.discord.streamdeck",
3+
"Name": "Discord",
4+
"Version": "1.0.0",
5+
"Author": "tech-stack-streamdeck",
6+
"Description": "Control Discord from your Stream Deck. Mute, deafen, push-to-talk, switch channels, and more.",
7+
"Category": "Discord",
8+
"CodePath": "bin/plugin.cjs",
9+
"Actions": [
10+
{
11+
"UUID": "com.discord.streamdeck.mute",
12+
"Name": "Mute / Unmute",
13+
"PropertyInspectorPath": "ui/inspector.html",
14+
"States": [
15+
{ "Title": "Mute", "Image": "" }
16+
]
17+
},
18+
{
19+
"UUID": "com.discord.streamdeck.deafen",
20+
"Name": "Deafen / Undeafen",
21+
"PropertyInspectorPath": "ui/inspector.html",
22+
"States": [
23+
{ "Title": "Deafen", "Image": "" }
24+
]
25+
},
26+
{
27+
"UUID": "com.discord.streamdeck.ptt",
28+
"Name": "Push to Talk",
29+
"PropertyInspectorPath": "ui/inspector.html",
30+
"States": [
31+
{ "Title": "PTT", "Image": "" }
32+
]
33+
},
34+
{
35+
"UUID": "com.discord.streamdeck.ptm",
36+
"Name": "Push to Mute",
37+
"PropertyInspectorPath": "ui/inspector.html",
38+
"States": [
39+
{ "Title": "PTM", "Image": "" }
40+
]
41+
},
42+
{
43+
"UUID": "com.discord.streamdeck.voice-channel",
44+
"Name": "Join Voice Channel",
45+
"PropertyInspectorPath": "ui/inspector.html",
46+
"States": [
47+
{ "Title": "Voice", "Image": "" }
48+
]
49+
},
50+
{
51+
"UUID": "com.discord.streamdeck.text-channel",
52+
"Name": "Go to Text Channel",
53+
"PropertyInspectorPath": "ui/inspector.html",
54+
"States": [
55+
{ "Title": "Text", "Image": "" }
56+
]
57+
},
58+
{
59+
"UUID": "com.discord.streamdeck.video",
60+
"Name": "Toggle Video",
61+
"PropertyInspectorPath": "ui/inspector.html",
62+
"States": [
63+
{ "Title": "Video", "Image": "" }
64+
]
65+
},
66+
{
67+
"UUID": "com.discord.streamdeck.stream",
68+
"Name": "Toggle Stream",
69+
"PropertyInspectorPath": "ui/inspector.html",
70+
"States": [
71+
{ "Title": "Stream", "Image": "" }
72+
]
73+
}
74+
]
75+
}

0 commit comments

Comments
 (0)