elfdeckai 1.0.4__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Garth Elfstrom
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,230 @@
1
+ Metadata-Version: 2.4
2
+ Name: elfdeckai
3
+ Version: 1.0.4
4
+ Summary: Virtual keyboard, gamepad and mouse for AI agents on Linux / Steam Deck โ€” zero-dependency uinput daemons with an MCP server.
5
+ Author: Garth Elfstrom
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Garthnesss/ElfDeckAI
8
+ Project-URL: Repository, https://github.com/Garthnesss/ElfDeckAI
9
+ Project-URL: Issues, https://github.com/Garthnesss/ElfDeckAI/issues
10
+ Keywords: mcp,model-context-protocol,llm,ai-agent,steam-deck,uinput,gamepad,automation
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Programming Language :: Python :: 3
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Dynamic: license-file
18
+
19
+ # ๐ŸŽฎ ElfdeckAI
20
+
21
+ ### Your AI can *see* the game. Now it can hold the controller.
22
+
23
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
24
+ ![Python](https://img.shields.io/badge/python-3.8+-blue.svg)
25
+ ![Dependencies](https://img.shields.io/badge/dependencies-zero-brightgreen.svg)
26
+ ![Tests](https://img.shields.io/badge/tests-146%20passing-brightgreen.svg)
27
+
28
+ **ElfdeckAI gives an LLM agent hands on real hardware.** Three tiny
29
+ daemons turn one-line commands โ€” from a shell, a script, or native
30
+ **MCP tool calls** โ€” into real keyboard, gamepad and mouse input on a
31
+ Linux machine:
32
+
33
+ - โŒจ๏ธ **ElfKey** โ€” a virtual keyboard on `/dev/uinput`
34
+ - ๐Ÿ•น๏ธ **ElfPad** โ€” a virtual gamepad (Xbox-class: buttons, analog triggers,
35
+ two sticks, D-pad)
36
+ - ๐Ÿ–ฑ๏ธ **ElfMouse** โ€” a virtual mouse (relative pointer, buttons, wheel)
37
+
38
+ Every "LLM plays Pokรฉmon" project you've seen plugs into an **emulator's
39
+ API**. ElfdeckAI presses buttons on a **real device** โ€” proven live when an
40
+ LLM played Pokรฉmon FireRed on a physical Steam Deck end-to-end through
41
+ ElfKey: full intro, starter picked, first rival battle won, game saved.
42
+ No emulator *API*, no RAM reading, no capture card โ€” the game runs on
43
+ the real device (inside RetroArch, in that run); the agent's only
44
+ channel in is real input, and its only view is a stream for eyes.
45
+ ([How it drives](notes/driving.md))
46
+
47
+ ## โšก Run it yourself (60 seconds)
48
+
49
+ ```sh
50
+ pip install git+https://github.com/Garthnesss/ElfDeckAI
51
+
52
+ elfkey & # virtual keyboard (command: elfkey)
53
+ elfpad & # virtual gamepad (command: elfpad)
54
+ elfmouse & # virtual mouse (command: elfmouse)
55
+
56
+ echo "key x 150" > /tmp/elfkey.fifo # tap X
57
+ echo "press a" > /tmp/elfpad.fifo # tap A
58
+ echo "move 300 -100" > /tmp/elfmouse.fifo # flick the pointer
59
+ echo "click left" > /tmp/elfmouse.fifo # click
60
+
61
+ grep -E "ElfKey|ElfPad|ElfMouse" /proc/bus/input/devices # they're really there
62
+ ```
63
+
64
+ That's the whole install. Three single-file Python programs, **standard
65
+ library only** โ€” nothing to build, no dependencies to resolve. State
66
+ lives in `/tmp`; reboot and it's gone. Prefer no pip at all? Clone the
67
+ repo and run `python3 elfkey.py` โ€” same thing.
68
+
69
+ For your agent, the pip install also provides the MCP server as a
70
+ command: `"command": "elfdeckai-mcp"` (details below).
71
+
72
+ > **Steam Deck?** You're already done โ€” `/dev/uinput` is user-writable
73
+ > there out of the box. On other Linux machines, install the included
74
+ > udev rule once (see [Install](#install)).
75
+
76
+ ## ๐Ÿค– Give it to your agent (MCP)
77
+
78
+ `elfdeckai_mcp.py` is a zero-dependency [Model Context
79
+ Protocol](https://modelcontextprotocol.io) server that exposes all
80
+ three devices as 18 native tools โ€” `key_tap`, `pad_press`, `pad_stick`,
81
+ `mouse_move`, `mouse_click`, `pad_reset`, `key_release`, โ€ฆ โ€” for Claude,
82
+ Hermes, or any MCP client:
83
+
84
+ ```json
85
+ {"mcpServers": {"elfdeckai": {"command": "elfdeckai-mcp"}}}
86
+ ```
87
+
88
+ (No pip? `"command": "python3", "args": ["/path/to/elfdeckai_mcp.py"]`
89
+ works identically.)
90
+
91
+ Start the daemons, add that to your client's config, and your agent can
92
+ press buttons the same way it calls any other tool. Every call is
93
+ validated by the daemons' own input contract before it touches a FIFO,
94
+ and a stopped daemon fails the call cleanly instead of hanging your agent.
95
+
96
+ ## ๐ŸŽ›๏ธ Commands
97
+
98
+ ### ElfKey โ€” `/tmp/elfkey.fifo`
99
+
100
+ | Command | Effect |
101
+ |---|---|
102
+ | `key <name> [ms]` | tap a key (default 120 ms) |
103
+ | `down <name>` / `up <name>` | hold / release |
104
+ | `release` | let go of every held key at once (the unstick command) |
105
+ | `quit` | release everything and exit |
106
+
107
+ Keys: `a`โ€“`z`, `0`โ€“`9`, arrows, `enter`, `esc`, `space`, `tab`, `shift`,
108
+ `rshift`, `ctrl`, `alt`, punctuation, `f1`โ€“`f10`, `home`, `end`, `insert`,
109
+ `delete`, โ€ฆ (full table in the source).
110
+
111
+ ### ElfPad โ€” `/tmp/elfpad.fifo`
112
+
113
+ | Command | Effect |
114
+ |---|---|
115
+ | `press <btn> [ms]` | tap a button โ€” or a D-pad direction |
116
+ | `down <btn>` / `up <btn>` | hold / release |
117
+ | `stick left\|right <x> <y> [ms]` | analog stick, โˆ’100..100 % per axis; `ms` = auto-recenter after |
118
+ | `trigger left\|right <0..100> [ms]` | analog trigger; `ms` = auto-release after |
119
+ | `center` | sticks, triggers, D-pad to neutral |
120
+ | `reset` | release everything |
121
+ | `quit` | release everything and exit |
122
+
123
+ Buttons: `a b x y lb rb l2 r2 select start guide l3 r3`. ElfPad presents
124
+ as an Xbox-360-class pad, so SDL, Steam Input and RetroArch map it the
125
+ standard way with zero configuration. `l2`/`r2` are analog-only, exactly
126
+ like the real pad โ€” there are deliberately no digital trigger buttons,
127
+ because declaring them shifts Select/Start/Guide two button indices off
128
+ the stock layout (verified against a real 360 pad on a Deck).
129
+
130
+ ### ElfMouse โ€” `/tmp/elfmouse.fifo`
131
+
132
+ | Command | Effect |
133
+ |---|---|
134
+ | `move <dx> <dy> [glide_ms]` | move the pointer (relative); `glide_ms` = glide there over time |
135
+ | `click <btn> [ms]` | click `left` / `right` / `middle` |
136
+ | `down <btn>` / `up <btn>` | hold / release a button โ€” drags are `down`, `move`, `up` |
137
+ | `scroll <ticks>` / `hscroll <ticks>` | wheel / horizontal wheel (positive = up / right) |
138
+ | `release` | let go of every held button at once (the unstick command) |
139
+ | `quit` | release any held button and exit |
140
+
141
+ Movement is relative, like a real mouse โ€” pair it with screenshots and
142
+ the same look โ†’ move โ†’ verify loop the rest of the set uses. Sign
143
+ convention: negative `dy` is up, positive is down. Units are relative
144
+ counts, not guaranteed screen pixels โ€” pointer acceleration stretches
145
+ fast moves โ€” so small moves plus a fresh screenshot beat one big leap. Per-command
146
+ bounds (ยฑ10 000 px, ยฑ120 ticks) keep a confused call from flinging the
147
+ pointer into orbit; chain commands for long travel.
148
+
149
+ ## ๐Ÿ›ก๏ธ Built for callers that hallucinate
150
+
151
+ Letting a language model press buttons is safe only if the keyboard
152
+ itself can't be talked into a bad state. So:
153
+
154
+ - **Bad input never kills the daemon.** Every line passes a strict
155
+ parser; malformed commands are logged and refused. (`key x abc` and
156
+ `key x 999999999` were real crash/wedge bugs in the first version โ€”
157
+ the tests now pin them shut.)
158
+ - **Holds are bounded.** A tap is at most 5 s. Longer holds use
159
+ `down`/`up`, which the caller controls.
160
+ - **Nothing gets stuck.** Timed moves restore in a `finally` block; held
161
+ inputs are tracked; on `quit`, SIGTERM or SIGINT, everything is
162
+ released and recentered before the device is destroyed.
163
+ - **The FIFO is owner-only (0600).** Only your user can inject input.
164
+ - **The server survives garbage.** Malformed JSON and non-object
165
+ requests get proper JSON-RPC protocol errors; one bad line โ€” or a
166
+ caller dying mid-hold โ€” can't wedge the session. `key_release`,
167
+ `mouse_release` and `pad_reset` are the unstick buttons โ€” a release
168
+ queues behind at most the one hold currently executing (bounded,
169
+ โ‰ค 5 s), and every accepted command is logged to stderr, so what the
170
+ agent pressed is auditable afterwards.
171
+ - **Scope honesty:** these are real OS-level input devices. They type
172
+ and click in whatever window has focus โ€” a game, but equally a
173
+ terminal or a browser. ElfdeckAI makes that input *un-mess-up-able*
174
+ (bounded, validated, releasable); deciding *where* an agent may act
175
+ is the operator's call. Run agents you trust, on sessions you'd
176
+ watch.
177
+
178
+ ## ๐Ÿค” Why notโ€ฆ?
179
+
180
+ | Approach | Why it falls short |
181
+ |---|---|
182
+ | Emulator APIs (PyBoy, mGBA scripting) | Only exists inside an emulator. Your Deck is not an emulator. |
183
+ | Sending keys through Moonlight/streaming | Clicks get forwarded; synthetic keyboard events don't (verified). |
184
+ | xdotool / desktop automation | Can't reach native Wayland clients. |
185
+ | ydotool / dotool | The real incumbents โ€” same uinput trick, keyboard + mouse. But: raw keycodes, a root daemon, no gamepad, and nothing agent-facing (no MCP, no safety bounds, no unstick). |
186
+ | KVM gadget / controller-spoofing hardware | Works, but it's extra hardware on your desk doing what 200 lines of Python do on the device itself. |
187
+
188
+ ## Install
189
+
190
+ **Requirements:** Linux (64-bit) with `/dev/uinput`, Python 3.8+. Nothing else.
191
+
192
+ Anywhere `/dev/uinput` isn't user-writable, install the udev rule once:
193
+
194
+ ```sh
195
+ sudo cp 70-elfdeckai-uinput.rules /etc/udev/rules.d/
196
+ sudo udevadm control --reload-rules && sudo udevadm trigger
197
+ getfacl /dev/uinput # verify: your user should appear in the ACL
198
+ ```
199
+
200
+ Run persistently as user services (expects the repo at `~/elfdeckai/` โ€” clone with `git clone https://github.com/Garthnesss/ElfDeckAI ~/elfdeckai`):
201
+
202
+ ```sh
203
+ cp systemd/*.service ~/.config/systemd/user/
204
+ systemctl --user enable --now elfkey.service elfpad.service elfmouse.service
205
+ ```
206
+
207
+ ## Tests
208
+
209
+ ```sh
210
+ python3 -m pytest # 146 tests: all three input contracts, device behaviour
211
+ # against a fake uinput, and the MCP server spoken
212
+ # to over real JSON-RPC
213
+ ```
214
+
215
+ ## Roadmap
216
+
217
+ - One combined daemon (keyboard + pad + mouse in a single process).
218
+ - PyPI release, so `pip install elfdeckai` works without the git URL.
219
+ - Demo clip: the Pokรฉmon run, on camera.
220
+
221
+ ## License
222
+
223
+ MIT โ€” see [LICENSE](LICENSE). Steal it, ship it, teach your agent to
224
+ speedrun something.
225
+
226
+ ## Author
227
+
228
+ Created and maintained by **Garth Elfstrom**
229
+ ([@Garthnesss](https://github.com/Garthnesss)). Issues and PRs welcome
230
+ on the repo.
@@ -0,0 +1,212 @@
1
+ # ๐ŸŽฎ ElfdeckAI
2
+
3
+ ### Your AI can *see* the game. Now it can hold the controller.
4
+
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
6
+ ![Python](https://img.shields.io/badge/python-3.8+-blue.svg)
7
+ ![Dependencies](https://img.shields.io/badge/dependencies-zero-brightgreen.svg)
8
+ ![Tests](https://img.shields.io/badge/tests-146%20passing-brightgreen.svg)
9
+
10
+ **ElfdeckAI gives an LLM agent hands on real hardware.** Three tiny
11
+ daemons turn one-line commands โ€” from a shell, a script, or native
12
+ **MCP tool calls** โ€” into real keyboard, gamepad and mouse input on a
13
+ Linux machine:
14
+
15
+ - โŒจ๏ธ **ElfKey** โ€” a virtual keyboard on `/dev/uinput`
16
+ - ๐Ÿ•น๏ธ **ElfPad** โ€” a virtual gamepad (Xbox-class: buttons, analog triggers,
17
+ two sticks, D-pad)
18
+ - ๐Ÿ–ฑ๏ธ **ElfMouse** โ€” a virtual mouse (relative pointer, buttons, wheel)
19
+
20
+ Every "LLM plays Pokรฉmon" project you've seen plugs into an **emulator's
21
+ API**. ElfdeckAI presses buttons on a **real device** โ€” proven live when an
22
+ LLM played Pokรฉmon FireRed on a physical Steam Deck end-to-end through
23
+ ElfKey: full intro, starter picked, first rival battle won, game saved.
24
+ No emulator *API*, no RAM reading, no capture card โ€” the game runs on
25
+ the real device (inside RetroArch, in that run); the agent's only
26
+ channel in is real input, and its only view is a stream for eyes.
27
+ ([How it drives](notes/driving.md))
28
+
29
+ ## โšก Run it yourself (60 seconds)
30
+
31
+ ```sh
32
+ pip install git+https://github.com/Garthnesss/ElfDeckAI
33
+
34
+ elfkey & # virtual keyboard (command: elfkey)
35
+ elfpad & # virtual gamepad (command: elfpad)
36
+ elfmouse & # virtual mouse (command: elfmouse)
37
+
38
+ echo "key x 150" > /tmp/elfkey.fifo # tap X
39
+ echo "press a" > /tmp/elfpad.fifo # tap A
40
+ echo "move 300 -100" > /tmp/elfmouse.fifo # flick the pointer
41
+ echo "click left" > /tmp/elfmouse.fifo # click
42
+
43
+ grep -E "ElfKey|ElfPad|ElfMouse" /proc/bus/input/devices # they're really there
44
+ ```
45
+
46
+ That's the whole install. Three single-file Python programs, **standard
47
+ library only** โ€” nothing to build, no dependencies to resolve. State
48
+ lives in `/tmp`; reboot and it's gone. Prefer no pip at all? Clone the
49
+ repo and run `python3 elfkey.py` โ€” same thing.
50
+
51
+ For your agent, the pip install also provides the MCP server as a
52
+ command: `"command": "elfdeckai-mcp"` (details below).
53
+
54
+ > **Steam Deck?** You're already done โ€” `/dev/uinput` is user-writable
55
+ > there out of the box. On other Linux machines, install the included
56
+ > udev rule once (see [Install](#install)).
57
+
58
+ ## ๐Ÿค– Give it to your agent (MCP)
59
+
60
+ `elfdeckai_mcp.py` is a zero-dependency [Model Context
61
+ Protocol](https://modelcontextprotocol.io) server that exposes all
62
+ three devices as 18 native tools โ€” `key_tap`, `pad_press`, `pad_stick`,
63
+ `mouse_move`, `mouse_click`, `pad_reset`, `key_release`, โ€ฆ โ€” for Claude,
64
+ Hermes, or any MCP client:
65
+
66
+ ```json
67
+ {"mcpServers": {"elfdeckai": {"command": "elfdeckai-mcp"}}}
68
+ ```
69
+
70
+ (No pip? `"command": "python3", "args": ["/path/to/elfdeckai_mcp.py"]`
71
+ works identically.)
72
+
73
+ Start the daemons, add that to your client's config, and your agent can
74
+ press buttons the same way it calls any other tool. Every call is
75
+ validated by the daemons' own input contract before it touches a FIFO,
76
+ and a stopped daemon fails the call cleanly instead of hanging your agent.
77
+
78
+ ## ๐ŸŽ›๏ธ Commands
79
+
80
+ ### ElfKey โ€” `/tmp/elfkey.fifo`
81
+
82
+ | Command | Effect |
83
+ |---|---|
84
+ | `key <name> [ms]` | tap a key (default 120 ms) |
85
+ | `down <name>` / `up <name>` | hold / release |
86
+ | `release` | let go of every held key at once (the unstick command) |
87
+ | `quit` | release everything and exit |
88
+
89
+ Keys: `a`โ€“`z`, `0`โ€“`9`, arrows, `enter`, `esc`, `space`, `tab`, `shift`,
90
+ `rshift`, `ctrl`, `alt`, punctuation, `f1`โ€“`f10`, `home`, `end`, `insert`,
91
+ `delete`, โ€ฆ (full table in the source).
92
+
93
+ ### ElfPad โ€” `/tmp/elfpad.fifo`
94
+
95
+ | Command | Effect |
96
+ |---|---|
97
+ | `press <btn> [ms]` | tap a button โ€” or a D-pad direction |
98
+ | `down <btn>` / `up <btn>` | hold / release |
99
+ | `stick left\|right <x> <y> [ms]` | analog stick, โˆ’100..100 % per axis; `ms` = auto-recenter after |
100
+ | `trigger left\|right <0..100> [ms]` | analog trigger; `ms` = auto-release after |
101
+ | `center` | sticks, triggers, D-pad to neutral |
102
+ | `reset` | release everything |
103
+ | `quit` | release everything and exit |
104
+
105
+ Buttons: `a b x y lb rb l2 r2 select start guide l3 r3`. ElfPad presents
106
+ as an Xbox-360-class pad, so SDL, Steam Input and RetroArch map it the
107
+ standard way with zero configuration. `l2`/`r2` are analog-only, exactly
108
+ like the real pad โ€” there are deliberately no digital trigger buttons,
109
+ because declaring them shifts Select/Start/Guide two button indices off
110
+ the stock layout (verified against a real 360 pad on a Deck).
111
+
112
+ ### ElfMouse โ€” `/tmp/elfmouse.fifo`
113
+
114
+ | Command | Effect |
115
+ |---|---|
116
+ | `move <dx> <dy> [glide_ms]` | move the pointer (relative); `glide_ms` = glide there over time |
117
+ | `click <btn> [ms]` | click `left` / `right` / `middle` |
118
+ | `down <btn>` / `up <btn>` | hold / release a button โ€” drags are `down`, `move`, `up` |
119
+ | `scroll <ticks>` / `hscroll <ticks>` | wheel / horizontal wheel (positive = up / right) |
120
+ | `release` | let go of every held button at once (the unstick command) |
121
+ | `quit` | release any held button and exit |
122
+
123
+ Movement is relative, like a real mouse โ€” pair it with screenshots and
124
+ the same look โ†’ move โ†’ verify loop the rest of the set uses. Sign
125
+ convention: negative `dy` is up, positive is down. Units are relative
126
+ counts, not guaranteed screen pixels โ€” pointer acceleration stretches
127
+ fast moves โ€” so small moves plus a fresh screenshot beat one big leap. Per-command
128
+ bounds (ยฑ10 000 px, ยฑ120 ticks) keep a confused call from flinging the
129
+ pointer into orbit; chain commands for long travel.
130
+
131
+ ## ๐Ÿ›ก๏ธ Built for callers that hallucinate
132
+
133
+ Letting a language model press buttons is safe only if the keyboard
134
+ itself can't be talked into a bad state. So:
135
+
136
+ - **Bad input never kills the daemon.** Every line passes a strict
137
+ parser; malformed commands are logged and refused. (`key x abc` and
138
+ `key x 999999999` were real crash/wedge bugs in the first version โ€”
139
+ the tests now pin them shut.)
140
+ - **Holds are bounded.** A tap is at most 5 s. Longer holds use
141
+ `down`/`up`, which the caller controls.
142
+ - **Nothing gets stuck.** Timed moves restore in a `finally` block; held
143
+ inputs are tracked; on `quit`, SIGTERM or SIGINT, everything is
144
+ released and recentered before the device is destroyed.
145
+ - **The FIFO is owner-only (0600).** Only your user can inject input.
146
+ - **The server survives garbage.** Malformed JSON and non-object
147
+ requests get proper JSON-RPC protocol errors; one bad line โ€” or a
148
+ caller dying mid-hold โ€” can't wedge the session. `key_release`,
149
+ `mouse_release` and `pad_reset` are the unstick buttons โ€” a release
150
+ queues behind at most the one hold currently executing (bounded,
151
+ โ‰ค 5 s), and every accepted command is logged to stderr, so what the
152
+ agent pressed is auditable afterwards.
153
+ - **Scope honesty:** these are real OS-level input devices. They type
154
+ and click in whatever window has focus โ€” a game, but equally a
155
+ terminal or a browser. ElfdeckAI makes that input *un-mess-up-able*
156
+ (bounded, validated, releasable); deciding *where* an agent may act
157
+ is the operator's call. Run agents you trust, on sessions you'd
158
+ watch.
159
+
160
+ ## ๐Ÿค” Why notโ€ฆ?
161
+
162
+ | Approach | Why it falls short |
163
+ |---|---|
164
+ | Emulator APIs (PyBoy, mGBA scripting) | Only exists inside an emulator. Your Deck is not an emulator. |
165
+ | Sending keys through Moonlight/streaming | Clicks get forwarded; synthetic keyboard events don't (verified). |
166
+ | xdotool / desktop automation | Can't reach native Wayland clients. |
167
+ | ydotool / dotool | The real incumbents โ€” same uinput trick, keyboard + mouse. But: raw keycodes, a root daemon, no gamepad, and nothing agent-facing (no MCP, no safety bounds, no unstick). |
168
+ | KVM gadget / controller-spoofing hardware | Works, but it's extra hardware on your desk doing what 200 lines of Python do on the device itself. |
169
+
170
+ ## Install
171
+
172
+ **Requirements:** Linux (64-bit) with `/dev/uinput`, Python 3.8+. Nothing else.
173
+
174
+ Anywhere `/dev/uinput` isn't user-writable, install the udev rule once:
175
+
176
+ ```sh
177
+ sudo cp 70-elfdeckai-uinput.rules /etc/udev/rules.d/
178
+ sudo udevadm control --reload-rules && sudo udevadm trigger
179
+ getfacl /dev/uinput # verify: your user should appear in the ACL
180
+ ```
181
+
182
+ Run persistently as user services (expects the repo at `~/elfdeckai/` โ€” clone with `git clone https://github.com/Garthnesss/ElfDeckAI ~/elfdeckai`):
183
+
184
+ ```sh
185
+ cp systemd/*.service ~/.config/systemd/user/
186
+ systemctl --user enable --now elfkey.service elfpad.service elfmouse.service
187
+ ```
188
+
189
+ ## Tests
190
+
191
+ ```sh
192
+ python3 -m pytest # 146 tests: all three input contracts, device behaviour
193
+ # against a fake uinput, and the MCP server spoken
194
+ # to over real JSON-RPC
195
+ ```
196
+
197
+ ## Roadmap
198
+
199
+ - One combined daemon (keyboard + pad + mouse in a single process).
200
+ - PyPI release, so `pip install elfdeckai` works without the git URL.
201
+ - Demo clip: the Pokรฉmon run, on camera.
202
+
203
+ ## License
204
+
205
+ MIT โ€” see [LICENSE](LICENSE). Steal it, ship it, teach your agent to
206
+ speedrun something.
207
+
208
+ ## Author
209
+
210
+ Created and maintained by **Garth Elfstrom**
211
+ ([@Garthnesss](https://github.com/Garthnesss)). Issues and PRs welcome
212
+ on the repo.