alexa-mcp 0.2.2 → 0.3.0
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.
- package/README.md +181 -117
- package/dist/cli.js +126 -35
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +27 -6
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +119 -73
- package/dist/client.js.map +1 -1
- package/dist/mcp-tools.d.ts.map +1 -1
- package/dist/mcp-tools.js +136 -15
- package/dist/mcp-tools.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,138 +1,149 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Alexa MCP
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/alexa-mcp)
|
|
4
|
+
[](https://github.com/m0nkmaster/alexa-mcp/actions/workflows/ci.yml)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- Node.js 18+
|
|
8
|
-
- Amazon Alexa account (amazon.com, amazon.co.uk, or amazon.de)
|
|
7
|
+
**Control your Alexa devices and smart home from the command line or AI assistants.**
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
MCP server and CLI for Alexa/Echo devices and smart home control via the unofficial Alexa API. Works with Claude Desktop, Cursor, VS Code, and other MCP-compatible tools.
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
npm install alexa-mcp # local install
|
|
15
|
-
npm install -g alexa-mcp # global install (adds alexa-mcp to PATH)
|
|
16
|
-
npx alexa-mcp auth # run without installing
|
|
17
|
-
```
|
|
11
|
+
## Features
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
- 🎙️ **Voice & Media Control** - TTS, announcements, playback control
|
|
14
|
+
- 💡 **Smart Home** - Control lights, plugs, and devices by name, pattern, or room group
|
|
15
|
+
- ✅ **State Verification** - Every control command returns live device state (power, brightness, colour temp)
|
|
16
|
+
- 🔍 **Device Status** - Query current state of any device by name without issuing a command
|
|
17
|
+
- 🏠 **Group Membership** - List all devices in a room group for post-action verification
|
|
18
|
+
- 🤖 **Routines** - List and run routines by name, partial name, or automation ID
|
|
19
|
+
- 🌍 **Multi-Region** - Supports US (amazon.com), UK (amazon.co.uk), and DE (amazon.de)
|
|
20
|
+
- 🔌 **MCP Integration** - Use with Claude, Cursor, and other AI assistants
|
|
21
|
+
- 🛠️ **CLI & Programmatic** - Command-line tool or Node.js library
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
alexa-mcp auth --token "Atnr|..."
|
|
28
|
-
alexa-mcp auth --token-file /path/to/token.txt
|
|
29
|
-
alexa-mcp auth --domain amazon.com # US account (default: amazon.co.uk)
|
|
30
|
-
alexa-mcp auth --no-save # validate token without saving
|
|
31
|
-
```
|
|
23
|
+
## Quick Start
|
|
32
24
|
|
|
33
|
-
|
|
25
|
+
### Installation
|
|
34
26
|
|
|
35
|
-
|
|
27
|
+
```bash
|
|
28
|
+
# Global install (recommended for CLI usage)
|
|
29
|
+
npm install -g alexa-mcp
|
|
36
30
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
| `ALEXA_DOMAIN` | Amazon domain when using env token (default: `amazon.co.uk`; options: `amazon.com`, `amazon.de`) |
|
|
41
|
-
| `ALEXA_DEBUG` | Set to any value to log API request/response details to stderr |
|
|
31
|
+
# Or use without installing
|
|
32
|
+
npx alexa-mcp auth
|
|
33
|
+
```
|
|
42
34
|
|
|
43
|
-
|
|
35
|
+
### Authentication
|
|
36
|
+
**Interactive (browser-based):**
|
|
37
|
+
```bash
|
|
38
|
+
alexa-mcp auth
|
|
39
|
+
```
|
|
40
|
+
Opens a URL for you to log in to Amazon. Works locally or on remote servers using automatic tunneling (cloudflared or localtunnel).
|
|
44
41
|
|
|
42
|
+
**Headless (token-based):**
|
|
45
43
|
```bash
|
|
46
|
-
alexa-mcp auth
|
|
47
|
-
alexa-mcp auth --token
|
|
48
|
-
alexa-mcp auth --
|
|
49
|
-
alexa-mcp auth --domain amazon.com # Specify Amazon domain (default: amazon.co.uk)
|
|
50
|
-
alexa-mcp auth --no-save # Validate token without saving
|
|
51
|
-
alexa-mcp auth status [--verify] # Show auth status (--verify calls API)
|
|
52
|
-
alexa-mcp auth logout # Remove credentials
|
|
53
|
-
alexa-mcp devices # List Echo devices
|
|
54
|
-
alexa-mcp devices --owners # Show device names and owner customer IDs (profile matching)
|
|
55
|
-
alexa-mcp speak "Hello" -d Office # Speak text on a specific device
|
|
56
|
-
alexa-mcp announce "Dinner ready" # Announce to all devices
|
|
57
|
-
alexa-mcp command -d Office "play jazz" # Voice command (no response returned)
|
|
58
|
-
alexa-mcp groups # List room/space groups (Kitchen, Living room, etc.)
|
|
59
|
-
alexa-mcp switch-group Kitchen off # Turn off all lights in a group
|
|
60
|
-
alexa-mcp switch-group Kitchen off --all # Turn off ALL appliances in group (not just lights)
|
|
61
|
-
alexa-mcp switch-room "kitchen lights" off # Turn off all devices matching name pattern
|
|
62
|
-
alexa-mcp switch "Lounge light 2" off # Turn off single device by name (direct control; -d for voice fallback)
|
|
63
|
-
alexa-mcp appliances # List smart home devices (endpointId + friendlyName when available)
|
|
64
|
-
alexa-mcp control <entityId> turnOn|turnOff|setBrightness [--brightness 50]
|
|
65
|
-
alexa-mcp routines # List routines
|
|
66
|
-
alexa-mcp run <automationId> # Run a routine
|
|
67
|
-
alexa-mcp now-playing -d Office # Now-playing state (EU/UK)
|
|
68
|
-
alexa-mcp media play|pause|resume|stop|next|previous -d Office # Transport control (EU/UK)
|
|
44
|
+
alexa-mcp auth --token "Atnr|..."
|
|
45
|
+
alexa-mcp auth --token-file /path/to/token.txt
|
|
46
|
+
alexa-mcp auth --domain amazon.com # US account (default: amazon.co.uk)
|
|
69
47
|
```
|
|
70
48
|
|
|
71
|
-
|
|
49
|
+
Configuration is stored in `~/.alexa-mcp/config.json`.
|
|
72
50
|
|
|
73
|
-
|
|
51
|
+
## Usage
|
|
74
52
|
|
|
75
|
-
|
|
53
|
+
### CLI Commands
|
|
76
54
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
55
|
+
**Authentication:**
|
|
56
|
+
```bash
|
|
57
|
+
alexa-mcp auth # Interactive auth
|
|
58
|
+
alexa-mcp auth status [--verify] # Check auth status
|
|
59
|
+
alexa-mcp auth logout # Remove credentials
|
|
60
|
+
```
|
|
83
61
|
|
|
84
|
-
|
|
62
|
+
**Devices & Voice:**
|
|
63
|
+
```bash
|
|
64
|
+
alexa-mcp devices # List Echo devices
|
|
65
|
+
alexa-mcp speak "Hello" -d Office # Text-to-speech on device
|
|
66
|
+
alexa-mcp announce "Dinner ready" # Announce to all devices
|
|
67
|
+
alexa-mcp command -d Office "play jazz" # Voice command
|
|
68
|
+
```
|
|
85
69
|
|
|
86
|
-
|
|
70
|
+
**Smart Home:**
|
|
71
|
+
```bash
|
|
72
|
+
alexa-mcp groups # List room groups
|
|
73
|
+
alexa-mcp group-members Kitchen # List all devices in a group
|
|
74
|
+
alexa-mcp appliances # List smart home devices (with …suffix for disambiguation)
|
|
75
|
+
alexa-mcp appliances --type light # Filter by type: light, switch, plug, sensor, camera
|
|
76
|
+
alexa-mcp status "Lounge lamp" # Get current state (power, brightness, colour temp)
|
|
77
|
+
alexa-mcp switch-group Kitchen off # Turn off lights in room group → returns JSON result
|
|
78
|
+
alexa-mcp switch-room "kitchen lights" off # Turn off devices by pattern → returns JSON result
|
|
79
|
+
alexa-mcp switch "Lounge light 2" off # Turn off single device → returns live state JSON
|
|
80
|
+
alexa-mcp control <entityId> turnOn # Direct device control → returns live state JSON
|
|
81
|
+
alexa-mcp batch-control turnOff e1 e2 e3 # Batch control → returns per-device result map
|
|
82
|
+
```
|
|
87
83
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
84
|
+
**Routines & Media:**
|
|
85
|
+
```bash
|
|
86
|
+
alexa-mcp routines # List routines (includes name field)
|
|
87
|
+
alexa-mcp run <automationId> # Run a routine by ID
|
|
88
|
+
alexa-mcp run --name "our bedtime" # Run a routine by exact name
|
|
89
|
+
alexa-mcp run --partial "bedtime" # Run a routine by partial name match
|
|
90
|
+
alexa-mcp now-playing -d Office # Show now-playing
|
|
91
|
+
alexa-mcp media play|pause|next -d Office # Media control
|
|
92
|
+
```
|
|
91
93
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
**Tips:**
|
|
95
|
+
- Use `switch-group` for "all lights in [room]" (e.g., `Kitchen`)
|
|
96
|
+
- Use `switch-room` for pattern matching — tries all-words first, falls back to any-word
|
|
97
|
+
- Use `switch` for single devices by exact name
|
|
98
|
+
- Use `status` to verify device state without issuing a command
|
|
99
|
+
- Use `group-members` after a `switch-group` to see which devices were targeted
|
|
100
|
+
- All control commands now return JSON state — no second API call needed to verify
|
|
101
|
+
- `appliances` output includes `displayName` with a 4-char endpoint suffix to disambiguate duplicates
|
|
102
|
+
- Direct control methods avoid voice profile issues
|
|
103
|
+
- See [docs/API.md](docs/API.md) for full API reference
|
|
95
104
|
|
|
96
|
-
|
|
97
|
-
`alexa-mcp auth status --verify`
|
|
98
|
-
Shows “Account (deviceOwnerCustomerId): …” for the current session. Control will work when this matches the owner of the Echo and the smart home device.
|
|
105
|
+
### MCP Server Setup
|
|
99
106
|
|
|
100
|
-
|
|
107
|
+
### Claude Desktop
|
|
101
108
|
|
|
102
|
-
|
|
109
|
+
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):
|
|
103
110
|
|
|
104
111
|
```json
|
|
105
112
|
{
|
|
106
113
|
"mcpServers": {
|
|
107
114
|
"alexa": {
|
|
108
|
-
"command": "
|
|
109
|
-
"args": ["
|
|
115
|
+
"command": "npx",
|
|
116
|
+
"args": ["alexa-mcp"]
|
|
110
117
|
}
|
|
111
118
|
}
|
|
112
119
|
}
|
|
113
120
|
```
|
|
114
121
|
|
|
115
|
-
|
|
122
|
+
### Cursor / VS Code
|
|
123
|
+
|
|
124
|
+
Edit `~/.cursor/mcp.json` or `.vscode/mcp.json`:
|
|
116
125
|
|
|
117
126
|
```json
|
|
118
127
|
{
|
|
119
128
|
"mcpServers": {
|
|
120
129
|
"alexa": {
|
|
121
|
-
"command": "
|
|
122
|
-
"args": ["
|
|
130
|
+
"command": "npx",
|
|
131
|
+
"args": ["alexa-mcp"]
|
|
123
132
|
}
|
|
124
133
|
}
|
|
125
134
|
}
|
|
126
135
|
```
|
|
127
136
|
|
|
128
|
-
|
|
137
|
+
### Using Environment Variables
|
|
138
|
+
|
|
139
|
+
Pass token directly (no config file needed):
|
|
129
140
|
|
|
130
141
|
```json
|
|
131
142
|
{
|
|
132
143
|
"mcpServers": {
|
|
133
144
|
"alexa": {
|
|
134
|
-
"command": "
|
|
135
|
-
"args": ["
|
|
145
|
+
"command": "npx",
|
|
146
|
+
"args": ["alexa-mcp"],
|
|
136
147
|
"env": {
|
|
137
148
|
"ALEXA_REFRESH_TOKEN": "Atnr|...",
|
|
138
149
|
"ALEXA_DOMAIN": "amazon.co.uk"
|
|
@@ -142,52 +153,105 @@ Each Echo and smart home device has a **deviceOwnerCustomerId** (Amazon’s inte
|
|
|
142
153
|
}
|
|
143
154
|
```
|
|
144
155
|
|
|
145
|
-
|
|
156
|
+
### Local Installation
|
|
157
|
+
|
|
158
|
+
If installed locally, use the full path:
|
|
146
159
|
|
|
147
160
|
```json
|
|
148
161
|
{
|
|
149
162
|
"mcpServers": {
|
|
150
163
|
"alexa": {
|
|
151
|
-
"command": "
|
|
152
|
-
"args": ["alexa-mcp"]
|
|
164
|
+
"command": "node",
|
|
165
|
+
"args": ["/path/to/node_modules/alexa-mcp/dist/index.js"]
|
|
153
166
|
}
|
|
154
167
|
}
|
|
155
168
|
}
|
|
156
169
|
```
|
|
157
170
|
|
|
158
|
-
### MCP Tools
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
171
|
+
### Available MCP Tools
|
|
172
|
+
|
|
173
|
+
**Devices & Voice:**
|
|
174
|
+
- `alexa_list_devices` - List Echo devices
|
|
175
|
+
- `alexa_speak` - Text-to-speech on a device
|
|
176
|
+
- `alexa_announce` - Announce to all devices
|
|
177
|
+
- `alexa_command` - Send voice command
|
|
178
|
+
|
|
179
|
+
**Smart Home:**
|
|
180
|
+
- `alexa_list_appliances` - List smart home devices; optional `type` filter (light/switch/plug/sensor/camera); includes `displayName` with 4-char endpoint suffix
|
|
181
|
+
- `alexa_device_status` - Get live state of a device by name (power, brightness, colour temp, reachability)
|
|
182
|
+
- `alexa_list_device_groups` - List room groups with member counts
|
|
183
|
+
- `alexa_group_members` - List all appliances in a named room group
|
|
184
|
+
- `alexa_control_by_group` - Control all lights in a room group
|
|
185
|
+
- `alexa_control_by_pattern` - Control devices by name pattern (fuzzy fallback: any-word match if all-word fails)
|
|
186
|
+
- `alexa_switch_by_name` - Control single device by name
|
|
187
|
+
- `alexa_control_appliance` - Direct control by entity/endpoint ID
|
|
188
|
+
- `alexa_batch_control_appliances` - Batch control with same action; returns per-device `{friendlyName, success, error}` map
|
|
189
|
+
- `alexa_batch_control_appliances_custom` - Batch control with per-device actions; returns per-device results
|
|
190
|
+
- `alexa_get_brightness_by_name` - Get device brightness and power state
|
|
191
|
+
- `alexa_set_brightness_by_name` - Set device brightness
|
|
192
|
+
- `alexa_get_color_temperature_by_name` - Get device colour temperature
|
|
193
|
+
- `alexa_set_color_temperature_by_name` - Set device colour temperature
|
|
194
|
+
|
|
195
|
+
**Routines & Media:**
|
|
196
|
+
- `alexa_list_routines` - List Alexa routines with names and automation IDs
|
|
197
|
+
- `alexa_run_routine` - Execute a routine by `automationId`, exact `name`, or `partial` name match
|
|
198
|
+
- `alexa_list_audio_groups` - List multi-room audio groups
|
|
199
|
+
- `alexa_now_playing` - Get now-playing state
|
|
200
|
+
- `alexa_media_control` - Control playback (play/pause/next/etc.)
|
|
201
|
+
- `alexa_get_volume` / `alexa_set_volume` - Volume control
|
|
202
|
+
|
|
203
|
+
**Authentication:**
|
|
204
|
+
- `alexa_auth_status` - Check authentication status
|
|
205
|
+
|
|
206
|
+
## Troubleshooting
|
|
207
|
+
|
|
208
|
+
### "Can't control – may need to switch user accounts"
|
|
209
|
+
|
|
210
|
+
This error occurs when the Amazon account you authenticated with doesn't own the device.
|
|
211
|
+
|
|
212
|
+
**Solution:**
|
|
213
|
+
1. Run `alexa-mcp auth logout`
|
|
214
|
+
2. Run `alexa-mcp auth` and sign in with the account that owns the device
|
|
215
|
+
3. Verify with `alexa-mcp auth status --verify`
|
|
216
|
+
|
|
217
|
+
**Check device ownership:**
|
|
218
|
+
```bash
|
|
219
|
+
alexa-mcp devices --owners # Show Echo device owners
|
|
220
|
+
alexa-mcp appliances # Show smart home device owners
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
The `deviceOwnerCustomerId` must match between your authenticated account and the device.
|
|
179
224
|
|
|
180
225
|
## Development
|
|
181
226
|
|
|
182
227
|
```bash
|
|
183
|
-
npm install
|
|
184
|
-
npm run build
|
|
185
|
-
npm test
|
|
186
|
-
npm run test:integration
|
|
228
|
+
npm install # Install dependencies
|
|
229
|
+
npm run build # Compile TypeScript
|
|
230
|
+
npm test # Run unit tests
|
|
231
|
+
npm run test:integration # Run integration tests (requires auth)
|
|
232
|
+
npm run lint # Check code style
|
|
233
|
+
npm run lint:fix # Fix code style issues
|
|
187
234
|
```
|
|
188
235
|
|
|
189
|
-
##
|
|
236
|
+
## Documentation
|
|
237
|
+
|
|
238
|
+
- **[API Reference](docs/API.md)** - Complete unofficial Alexa API documentation
|
|
239
|
+
- **[Device Capabilities](docs/DEVICE-CAPABILITIES.md)** - Device capability reference
|
|
240
|
+
- **[User Stories](docs/USER-STORIES.md)** - Usage examples and patterns
|
|
241
|
+
|
|
242
|
+
## Requirements
|
|
243
|
+
|
|
244
|
+
- Node.js 18+
|
|
245
|
+
- Amazon Alexa account (amazon.com, amazon.co.uk, or amazon.de)
|
|
246
|
+
|
|
247
|
+
## License
|
|
248
|
+
|
|
249
|
+
MIT - See LICENSE file for details
|
|
250
|
+
|
|
251
|
+
## Contributing
|
|
252
|
+
|
|
253
|
+
Contributions welcome! Please follow [conventional commits](https://www.conventionalcommits.org/) format.
|
|
190
254
|
|
|
191
|
-
|
|
255
|
+
## Acknowledgments
|
|
192
256
|
|
|
193
|
-
|
|
257
|
+
Built on the unofficial Alexa API. Uses [alexa-cookie2](https://github.com/Apollon77/alexa-cookie) for authentication.
|
package/dist/cli.js
CHANGED
|
@@ -191,7 +191,7 @@ program
|
|
|
191
191
|
});
|
|
192
192
|
program
|
|
193
193
|
.command("switch-group <group> <state>")
|
|
194
|
-
.description("Turn on/off all lights in a room group (e.g. Kitchen, Living room).
|
|
194
|
+
.description("Turn on/off all lights in a room group (e.g. Kitchen, Living room). Returns JSON {action, group, controlled, errors}.")
|
|
195
195
|
.option("--all", "Control all appliances in group, not just lights", false)
|
|
196
196
|
.action(async (group, state, opts) => {
|
|
197
197
|
const s = state.toLowerCase();
|
|
@@ -210,12 +210,7 @@ program
|
|
|
210
210
|
const { controlled, errors } = await client.controlAppliancesByGroup(group, action, {
|
|
211
211
|
lightsOnly: !opts.all,
|
|
212
212
|
});
|
|
213
|
-
|
|
214
|
-
console.log(`${action}: ${controlled.join(", ")}`);
|
|
215
|
-
}
|
|
216
|
-
if (errors.length > 0) {
|
|
217
|
-
console.error(errors.join("\n"));
|
|
218
|
-
}
|
|
213
|
+
console.log(JSON.stringify({ action, group, controlled, errors }));
|
|
219
214
|
if (controlled.length === 0 && errors.length === 0) {
|
|
220
215
|
console.error(`No lights in group "${group}". Try 'alexa-mcp groups' to see groups.`);
|
|
221
216
|
process.exit(1);
|
|
@@ -228,7 +223,7 @@ program
|
|
|
228
223
|
});
|
|
229
224
|
program
|
|
230
225
|
.command("switch-room <pattern> <state>")
|
|
231
|
-
.description("Turn on/off all smart home devices matching a pattern (e.g. 'kitchen lights', 'living room').
|
|
226
|
+
.description("Turn on/off all smart home devices matching a pattern (e.g. 'kitchen lights', 'living room'). Tries all-word match first; falls back to any-word. Returns JSON {action, pattern, controlled, errors}.")
|
|
232
227
|
.action(async (pattern, state) => {
|
|
233
228
|
const s = state.toLowerCase();
|
|
234
229
|
if (s !== "on" && s !== "off") {
|
|
@@ -243,12 +238,7 @@ program
|
|
|
243
238
|
const client = new AlexaClient({ refreshToken: cfg.refreshToken, domain: cfg.domain });
|
|
244
239
|
const action = s === "on" ? "turnOn" : "turnOff";
|
|
245
240
|
const { controlled, errors } = await client.controlAppliancesByPattern(pattern, action);
|
|
246
|
-
|
|
247
|
-
console.log(`${action}: ${controlled.join(", ")}`);
|
|
248
|
-
}
|
|
249
|
-
if (errors.length > 0) {
|
|
250
|
-
console.error(errors.join("\n"));
|
|
251
|
-
}
|
|
241
|
+
console.log(JSON.stringify({ action, pattern, controlled, errors }));
|
|
252
242
|
if (controlled.length === 0 && errors.length === 0) {
|
|
253
243
|
console.error(`No devices matched "${pattern}". Try 'alexa-mcp appliances' to see names.`);
|
|
254
244
|
process.exit(1);
|
|
@@ -256,7 +246,7 @@ program
|
|
|
256
246
|
});
|
|
257
247
|
program
|
|
258
248
|
.command("switch <name> <state>")
|
|
259
|
-
.description("Turn single smart home device on/off by name. For room/pattern (e.g. 'kitchen lights'), use switch-room instead.")
|
|
249
|
+
.description("Turn single smart home device on/off by name. Returns live device state JSON after applying. For room/pattern (e.g. 'kitchen lights'), use switch-room instead.")
|
|
260
250
|
.option("-d, --device <echo>", "Echo for voice fallback when direct control fails", "")
|
|
261
251
|
.action(async (name, state, opts) => {
|
|
262
252
|
const s = state.toLowerCase();
|
|
@@ -274,7 +264,8 @@ program
|
|
|
274
264
|
const app = await client.resolveApplianceByName(name);
|
|
275
265
|
if (app?.endpointId) {
|
|
276
266
|
await client.controlAppliance(app.endpointId, action);
|
|
277
|
-
|
|
267
|
+
const state = await client.getBrightnessState(app.endpointId);
|
|
268
|
+
console.log(JSON.stringify({ friendlyName: app.friendlyName, endpointId: app.endpointId, ...state }, null, 2));
|
|
278
269
|
return;
|
|
279
270
|
}
|
|
280
271
|
if (!opts.device) {
|
|
@@ -288,7 +279,7 @@ program
|
|
|
288
279
|
}
|
|
289
280
|
const text = s === "on" ? `turn on ${name}` : `turn off ${name}`;
|
|
290
281
|
await client.command(d.serialNumber, d.deviceType, d.deviceOwnerCustomerId, text);
|
|
291
|
-
console.log(
|
|
282
|
+
console.log(JSON.stringify({ friendlyName: name, action, method: "voice", device: d.accountName }));
|
|
292
283
|
});
|
|
293
284
|
program
|
|
294
285
|
.command("groups")
|
|
@@ -303,22 +294,80 @@ program
|
|
|
303
294
|
const groups = await client.listDeviceGroups();
|
|
304
295
|
console.log(JSON.stringify(groups, null, 2));
|
|
305
296
|
});
|
|
297
|
+
function filterAppliancesByType(appliances, type) {
|
|
298
|
+
return appliances.filter((a) => {
|
|
299
|
+
const caps = (a.capabilities ?? []).join(" ").toLowerCase();
|
|
300
|
+
const name = (a.friendlyName ?? "").toLowerCase();
|
|
301
|
+
switch (type) {
|
|
302
|
+
case "light": return caps.includes("brightness") || caps.includes("colortemperature") || /light|lamp|bulb/.test(name);
|
|
303
|
+
case "switch": return caps.includes("power") && !caps.includes("brightness");
|
|
304
|
+
case "plug": return /plug/.test(name);
|
|
305
|
+
case "sensor": return /sensor|motion|contact|temperature/.test(caps);
|
|
306
|
+
case "camera": return /camera|doorbell/.test(name);
|
|
307
|
+
default: return true;
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
306
311
|
program
|
|
307
312
|
.command("appliances")
|
|
308
313
|
.description("List smart home devices")
|
|
309
|
-
.
|
|
314
|
+
.option("--type <type>", "Filter by type: light, switch, plug, sensor, camera")
|
|
315
|
+
.action(async (opts) => {
|
|
310
316
|
const cfg = getAuthConfig();
|
|
311
317
|
if (!cfg) {
|
|
312
318
|
console.error("No refresh token.");
|
|
313
319
|
process.exit(1);
|
|
314
320
|
}
|
|
315
321
|
const client = new AlexaClient({ refreshToken: cfg.refreshToken, domain: cfg.domain });
|
|
316
|
-
|
|
317
|
-
|
|
322
|
+
let appliances = await client.listAppliances();
|
|
323
|
+
if (opts.type) {
|
|
324
|
+
appliances = filterAppliancesByType(appliances, opts.type.toLowerCase());
|
|
325
|
+
}
|
|
326
|
+
const output = appliances.map((a) => ({
|
|
327
|
+
...a,
|
|
328
|
+
displayName: `${a.friendlyName}${a.endpointId ? ` \u2026${a.endpointId.slice(-4)}` : ""}`,
|
|
329
|
+
}));
|
|
330
|
+
console.log(JSON.stringify(output, null, 2));
|
|
331
|
+
});
|
|
332
|
+
program
|
|
333
|
+
.command("status <name>")
|
|
334
|
+
.description("Get current state of a smart home device by name")
|
|
335
|
+
.action(async (name) => {
|
|
336
|
+
const cfg = getAuthConfig();
|
|
337
|
+
if (!cfg) {
|
|
338
|
+
console.error("No refresh token.");
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
const client = new AlexaClient({ refreshToken: cfg.refreshToken, domain: cfg.domain });
|
|
342
|
+
const app = await client.resolveApplianceByName(name);
|
|
343
|
+
if (!app) {
|
|
344
|
+
console.error(JSON.stringify({ error: `Device not found: "${name}". Try 'alexa-mcp appliances' to see names.` }));
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
const eid = app.endpointId ?? app.entityId;
|
|
348
|
+
const state = eid ? await client.getBrightnessState(eid) : {};
|
|
349
|
+
console.log(JSON.stringify({ friendlyName: app.friendlyName, endpointId: eid, isReachable: app.isReachable, ...state }, null, 2));
|
|
350
|
+
});
|
|
351
|
+
program
|
|
352
|
+
.command("group-members <group>")
|
|
353
|
+
.description("List all devices in a named room group")
|
|
354
|
+
.action(async (group) => {
|
|
355
|
+
const cfg = getAuthConfig();
|
|
356
|
+
if (!cfg) {
|
|
357
|
+
console.error("No refresh token.");
|
|
358
|
+
process.exit(1);
|
|
359
|
+
}
|
|
360
|
+
const client = new AlexaClient({ refreshToken: cfg.refreshToken, domain: cfg.domain });
|
|
361
|
+
const members = await client.listGroupMembers(group);
|
|
362
|
+
if (members.length === 0) {
|
|
363
|
+
console.error(`No group found matching "${group}". Try 'alexa-mcp groups' to see groups.`);
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
console.log(JSON.stringify(members, null, 2));
|
|
318
367
|
});
|
|
319
368
|
program
|
|
320
369
|
.command("control <entityId> <action>")
|
|
321
|
-
.description("Control smart home device (turnOn, turnOff, setBrightness, setColorTemperature)")
|
|
370
|
+
.description("Control smart home device (turnOn, turnOff, setBrightness, setColorTemperature). Returns live state JSON after applying.")
|
|
322
371
|
.option("-b, --brightness <0-100>", "Brightness for setBrightness", (v) => parseInt(v, 10))
|
|
323
372
|
.option("-k, --kelvin <2000-6500>", "Color temperature in Kelvin for setColorTemperature", (v) => parseInt(v, 10))
|
|
324
373
|
.action(async (entityId, action, opts) => {
|
|
@@ -342,7 +391,8 @@ program
|
|
|
342
391
|
}
|
|
343
392
|
const client = new AlexaClient({ refreshToken: cfg.refreshToken, domain: cfg.domain });
|
|
344
393
|
await client.controlAppliance(entityId, action, opts.brightness, opts.kelvin);
|
|
345
|
-
|
|
394
|
+
const state = entityId.startsWith("amzn1.alexa.endpoint.") ? await client.getBrightnessState(entityId) : {};
|
|
395
|
+
console.log(JSON.stringify({ entityId, action, ...state }, null, 2));
|
|
346
396
|
});
|
|
347
397
|
program
|
|
348
398
|
.command("routines")
|
|
@@ -358,24 +408,55 @@ program
|
|
|
358
408
|
console.log(JSON.stringify(routines, null, 2));
|
|
359
409
|
});
|
|
360
410
|
program
|
|
361
|
-
.command("run
|
|
362
|
-
.description("Run a routine by
|
|
363
|
-
.
|
|
411
|
+
.command("run [automationId]")
|
|
412
|
+
.description("Run a routine by ID, exact name (--name), or partial name (--partial)")
|
|
413
|
+
.option("--name <name>", "Run routine by exact name (case-insensitive)")
|
|
414
|
+
.option("--partial <text>", "Run routine by partial name match")
|
|
415
|
+
.action(async (automationId, opts) => {
|
|
364
416
|
const cfg = getAuthConfig();
|
|
365
417
|
if (!cfg) {
|
|
366
418
|
console.error("No refresh token.");
|
|
367
419
|
process.exit(1);
|
|
368
420
|
}
|
|
421
|
+
if (!automationId && !opts.name && !opts.partial) {
|
|
422
|
+
console.error("Provide automationId, --name <name>, or --partial <text>");
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
369
425
|
const client = new AlexaClient({ refreshToken: cfg.refreshToken, domain: cfg.domain });
|
|
370
426
|
const routines = await client.listRoutines();
|
|
371
|
-
|
|
372
|
-
if (
|
|
373
|
-
|
|
374
|
-
|
|
427
|
+
let r;
|
|
428
|
+
if (automationId) {
|
|
429
|
+
r = routines.find((x) => x.automationId === automationId);
|
|
430
|
+
if (!r) {
|
|
431
|
+
console.error(`Routine not found: ${automationId}`);
|
|
432
|
+
process.exit(1);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
else if (opts.name) {
|
|
436
|
+
const q = opts.name.toLowerCase();
|
|
437
|
+
r = routines.find((x) => x.name.toLowerCase() === q);
|
|
438
|
+
if (!r) {
|
|
439
|
+
console.error(`Routine not found with name: "${opts.name}"`);
|
|
440
|
+
process.exit(1);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
else if (opts.partial) {
|
|
444
|
+
const q = opts.partial.toLowerCase();
|
|
445
|
+
const matches = routines.filter((x) => x.name.toLowerCase().includes(q));
|
|
446
|
+
if (matches.length === 0) {
|
|
447
|
+
console.error(`No routines matched: "${opts.partial}"`);
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
if (matches.length > 1) {
|
|
451
|
+
console.log(JSON.stringify({ matches: matches.map((m) => ({ automationId: m.automationId, name: m.name })) }, null, 2));
|
|
452
|
+
console.error(`Multiple matches. Provide --name or a more specific --partial.`);
|
|
453
|
+
process.exit(1);
|
|
454
|
+
}
|
|
455
|
+
r = matches[0];
|
|
375
456
|
}
|
|
376
457
|
const sequenceJson = r.sequence != null ? JSON.stringify(r.sequence) : undefined;
|
|
377
458
|
await client.runRoutine(r.automationId, sequenceJson);
|
|
378
|
-
console.log(
|
|
459
|
+
console.log(JSON.stringify({ ran: r.name, automationId: r.automationId }));
|
|
379
460
|
});
|
|
380
461
|
program
|
|
381
462
|
.command("now-playing")
|
|
@@ -514,7 +595,7 @@ program
|
|
|
514
595
|
});
|
|
515
596
|
program
|
|
516
597
|
.command("batch-control <action>")
|
|
517
|
-
.description("Batch control multiple smart home devices with same action/value.
|
|
598
|
+
.description("Batch control multiple smart home devices with same action/value. Returns per-device result map {friendlyName, success, error?}.")
|
|
518
599
|
.argument("[entityIds...]", "Entity IDs or endpoint IDs (omit to read from stdin)")
|
|
519
600
|
.option("-b, --brightness <0-100>", "Brightness for setBrightness", (v) => parseInt(v, 10))
|
|
520
601
|
.option("-k, --kelvin <2000-6500>", "Color temperature in Kelvin for setColorTemperature", (v) => parseInt(v, 10))
|
|
@@ -552,10 +633,20 @@ program
|
|
|
552
633
|
process.exit(1);
|
|
553
634
|
}
|
|
554
635
|
const client = new AlexaClient({ refreshToken: cfg.refreshToken, domain: cfg.domain });
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
636
|
+
const [results, appliances] = await Promise.all([
|
|
637
|
+
client.batchControlAppliances(entityIds, action, opts.brightness, opts.kelvin),
|
|
638
|
+
client.listAppliances(),
|
|
639
|
+
]);
|
|
640
|
+
const nameMap = new Map(appliances.map((a) => [a.entityId, a.friendlyName]));
|
|
641
|
+
const output = Object.fromEntries(results.map((r) => [
|
|
642
|
+
r.entityId,
|
|
643
|
+
{
|
|
644
|
+
friendlyName: nameMap.get(r.entityId) ?? r.entityId,
|
|
645
|
+
success: r.success,
|
|
646
|
+
...(r.error ? { error: r.error } : {}),
|
|
647
|
+
},
|
|
648
|
+
]));
|
|
649
|
+
console.log(JSON.stringify(output, null, 2));
|
|
559
650
|
});
|
|
560
651
|
const mediaCmd = program
|
|
561
652
|
.command("media <command>")
|