@David-Coulson, I fixed it. Now you won't get these "TRIGGERcmd - Your command didn't run" emails if the trigger was from Home Assistant.

Thanks again for reporting the problem.
@David-Coulson, I fixed it. Now you won't get these "TRIGGERcmd - Your command didn't run" emails if the trigger was from Home Assistant.

Thanks again for reporting the problem.
@Nuno-Gomes, thanks for telling me. Please try again. It should work now.
@Asit-Mishra, thanks for telling me. Please try again. It should work now.
@Yoni, thanks for letting me know. I'll make it case-insensitive in the next version.
@Matt-Lodder, what OS are you running? I just tried echo on my Windows laptop and it did not pop up a window.
@David-Coulson, I'll look into it.
One thing you can check in the meantime make sure this checkbox is unchecked in your profile:

Can you show me one of the emails? I wonder if it's reminding you to subscribe because you're running more than 1 command per minute. I'll add an option to disable those emails too.
Also, did you enable "Home Assistant Offline Configuration"?
EDIT: I added this checkbox that you can uncheck to stop the subscription reminder emails:

My PC talks to my wifi light bulb directly, not via Tuya's cloud API.
I can say, "Alexa, bulb 1 red" or "Alexa bulb 1 off" etc.
I can also say, "Use @TRIGGERcmd to turn bulb 1 blue" using the MCP tool with ChatGPT.
Example commands:
Turn the bulb on:
python3 c:\tools\tuyabulb.py --id abcdefghijklmnop123456 --key "123456!@ABCcdefh" --ip 192.168.86.25 on
Set the color to red:
python3 c:\tools\tuyabulb.py --id abcdefghijklmnop123456 --key "123456!@ABCcdefh" --ip 192.168.86.25 red
Set the brightness to 60 percent:
python3 c:\tools\tuyabulb.py --id abcdefghijklmnop123456 --key "123456!@ABCcdefh" --ip 192.168.86.25 60
Here's the script:
#!/usr/bin/env python3
import argparse
import ipaddress
import platform
import subprocess
import sys
import tinytuya
# Predefined color presets (RGB values)
COLOR_PRESETS = {
"red": (255, 0, 0),
"green": (0, 255, 0),
"blue": (0, 0, 255),
"white": (255, 255, 255),
"yellow": (255, 255, 0),
"cyan": (0, 255, 255),
"magenta": (255, 0, 255),
"orange": (255, 165, 0),
"purple": (128, 0, 128),
"pink": (255, 192, 203),
}
def ping_host(ip: str, timeout: int = 1) -> bool:
"""Ping a host to check if it's reachable. Returns True if ping succeeds."""
param = "-n" if platform.system().lower() == "windows" else "-c"
timeout_param = "-w" if platform.system().lower() == "windows" else "-W"
timeout_value = str(timeout * 1000) if platform.system().lower() == "windows" else str(timeout)
command = ["ping", param, "1", timeout_param, timeout_value, ip]
try:
result = subprocess.run(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=timeout + 1)
return result.returncode == 0
except (subprocess.TimeoutExpired, Exception):
return False
def make_device(device_id: str, ip: str, local_key: str, version: str | None):
# BulbDevice works for most Tuya bulbs. If yours isn't a "bulb" type, use Device instead.
d = tinytuya.BulbDevice(device_id, ip, local_key)
d.set_socketPersistent(True) # keep socket open for reliability
d.set_socketTimeout(5) # seconds
# Many Tuya WiFi bulbs are 3.3. Some are 3.1. If you're unsure, try 3.3 first.
if version:
d.set_version(float(version))
else:
d.set_version(3.3)
return d
def main():
p = argparse.ArgumentParser(description="Local control of a Tuya bulb via tinytuya (no cloud).")
p.add_argument("--id", required=True, help="Tuya device id")
p.add_argument("--ip", help="Bulb IP address on your LAN")
p.add_argument("--subnet", help="Subnet to scan (e.g. 192.168.1.0/24)")
p.add_argument("--start-from", help="IP address to start scanning from (only with --subnet)")
p.add_argument("--key", required=True, help="Tuya localKey (16+ chars)")
p.add_argument("--ver", default=None, help="Protocol version (e.g. 3.3 or 3.1). Default: 3.3")
p.add_argument("cmd", help="Command: off, on, status, color name (red, green, etc.), or brightness 0-100")
args = p.parse_args()
# Determine command type
cmd_lower = args.cmd.lower()
if cmd_lower in ["off", "on", "status"]:
cmd_type = cmd_lower
elif cmd_lower in COLOR_PRESETS:
cmd_type = "color"
preset_color = cmd_lower
elif args.cmd.isdigit():
brightness_val = int(args.cmd)
if 0 <= brightness_val <= 100:
cmd_type = "brightness"
else:
p.error("Brightness must be between 0-100")
else:
p.error(f"Invalid command: {args.cmd}. Use: off, on, status, color name, or brightness (0-100)")
# Validate that either --ip or --subnet is provided
if not args.ip and not args.subnet:
p.error("Either --ip or --subnet must be specified")
if args.ip and args.subnet:
p.error("Cannot specify both --ip and --subnet")
if args.start_from and not args.subnet:
p.error("--start-from can only be used with --subnet")
# Determine which IPs to try
if args.subnet:
try:
network = ipaddress.ip_network(args.subnet, strict=False)
all_ips = [str(ip) for ip in network.hosts()]
# Filter to start from specified IP if provided
if args.start_from:
start_ip = ipaddress.ip_address(args.start_from)
# Verify start IP is in the subnet
if start_ip not in network:
print(f"ERROR: Start IP {args.start_from} is not in subnet {args.subnet}", file=sys.stderr)
return 1
# Filter to IPs >= start_from
ips_to_try = [ip for ip in all_ips if ipaddress.ip_address(ip) >= start_ip]
print(f"Scanning subnet {args.subnet} from {args.start_from} ({len(ips_to_try)} hosts)...", file=sys.stderr)
else:
ips_to_try = all_ips
print(f"Scanning subnet {args.subnet} ({len(ips_to_try)} hosts)...", file=sys.stderr)
except ValueError as e:
print(f"ERROR: Invalid subnet or IP format: {e}", file=sys.stderr)
return 1
else:
ips_to_try = [args.ip]
# Try each IP until one works
last_error = None
for ip in ips_to_try:
if args.subnet:
print(f"Trying {ip}...", file=sys.stderr)
# Quick ping check to skip unreachable hosts
if not ping_host(ip):
continue
dev = make_device(args.id, ip, args.key, args.ver)
dev = make_device(args.id, ip, args.key, args.ver)
try:
if cmd_type == "off":
r = dev.turn_off()
elif cmd_type == "on":
r = dev.turn_on()
elif cmd_type == "brightness":
r = dev.set_brightness_percentage(brightness_val)
elif cmd_type == "color":
# Get RGB values from preset
rgb = COLOR_PRESETS[preset_color]
# Set the color
r = dev.set_colour(rgb[0], rgb[1], rgb[2])
else:
r = dev.status()
# Check if the device responded with an errora
if isinstance(r, dict) and r.get("Error"):
if args.subnet:
# During subnet scan, continue to next IP on error
last_error = r.get("Error")
continue
else:
# For direct IP, print error and exit
print(r)
return 2
# Success! Print result and exit
if args.subnet:
print(f"SUCCESS: Device found at {ip}", file=sys.stderr)
print(r)
return 0
except Exception as e:
last_error = e
if not args.subnet:
print(f"ERROR: {e}", file=sys.stderr)
return 1
# For subnet scan, continue to next IP
continue
finally:
try:
dev.set_socketPersistent(False)
except Exception:
pass
# If we get here with subnet scan, none of the IPs worked
if args.subnet:
print(f"ERROR: Device not found in subnet {args.subnet}. Last error: {last_error}", file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())
This is my commands.json entry:
{
"trigger": "Tuya Bulb 1",
"command": "python3 c:\\tools\\tuyabulb.py --id abcdefghijklmnop123456 --key \"123456!@ABCcdefh\" --ip 192.168.86.25",
"offCommand": "",
"ground": "foreground",
"voice": "bulb 1",
"voiceReply": "",
"allowParams": "true",
"mcpToolDescription": "Controls the state of light bulb 1. Parameters are: on, off, red, green, blue, white, yellow, cyan, magenta, orange, purple, pink, or brightness percentage from 0 to 100"
}
@Timothy-Martin, please try again. It should work now. Thanks again for telling me it was broken.
@Timothy-Martin thanks for telling me! It works now.
I recently tightened my Content-Security-Policy and I needed to add something to make that Paypal link work.
Thanks @kellanist. I'll take a look.
I hoped Microsoft would whitelist apps signed by my code signing certificate by now. Maybe I'm missing something.
If it's new, I think it must be because I recently renewed my certificate.
EDIT: I submitted the agent to Microsoft just now, so hopefully they'll whitelist it soon. Otherwise I just have to wait for its reputation to build as users install it.

This is what I saw when I submitted the agent installer:
"The SmartScreen warning that you reported indicates that the application and/or certificate used to sign it do not yet have a reputation established in our system. Applications without a known reputation will show warnings when downloaded or installed. This does not prevent users from still clicking through to download or run the application. To do so, they can select "More info" -> "Run anyway" option within SmartScreen prompt window.
Reputation is established by how your download is used by Windows, Edge, Internet Explorer users and the SmartScreen
Service intelligence algorithms. Downloads are assigned a reputation rating based on many criteria, such as download traffic, download history, past anti-virus results and URL reputation. This reputation may be based on the downloaded program or can also be assigned to the publisher, based on digital certificate information.
Once your signing certificate has gained reputation in our system, all applications or releases signed with your certificate should have warn-free experience"
@Kevin-0, it should work now, but only if you have a paid ChatGPT "plus" subscription or greater.
To add the TRIGGERcmd MCP server (https://www.triggercmd.com/mcp) to your ChatGPT account:
Enable development mode here: https://chatgpt.com/#settings/Connectors/Advanced

Click "Create app" and set it up like this:

I paid for the ChatGPT plus upgrade and tested it:

EDIT: I submitted the MCP server to OpenAI to be included in their app store. That should solve the issue I've seen where ChatGPT runs the command twice. If they reject the app I'll do something to debounce it.
Old behavior:
Before, the TRIGGERcmd Smart Home Alexa skill/ Google Assistant action logic wouldn't create devices unless the computer had a Voice Name or it was your default computer. Also, devices on your non-default computers always had " on [computer voice name]" appended to them, which is a lot to have to say to Alexa or Google Assistant.
New behavior:
Now you'll get a device with a shorter name (without "on laptop" or something similar) for all commands as long as the command's voice name is unique.
If there are multiple commands with the same voice field value, you'll still get a device, but with a name like "calculator on laptop" assuming the computer has "laptop" as its Voice Name.
Commands with unique voice words create devices (even without computer Voice Name) with the command's voice field value only.
Commands on your default computer always create devices for commands, as long as they have a voice field filled in.
Commands on computers with Voice Name always create devices, but if the short name has a conflict, " on (computer voice name)" will be appended.
Commands with conflicts on non-default computers without Voice Name are skipped
I hope that helps.
Thanks @Kevin-0. Now that you asked, I'll definitely look into it.
@mega_big, on a Raspberry Pi, assuming you installed the agent using these instructions, your commands are in this file:
/root/.TRIGGERcmdData/commands.json
You'll see the default commands in there.
You can run these commands to edit it:
sudo su -
cd /root/.TRIGGERcmdData
nano commands.json
While the agent is running, it will detect changes in that commands.json file and create/remove triggers for those commands in your TRIGGERcmd account. If you mess up the json at all (an extra comma even), the agent will restore the last backup it took. You'll see that in your debug.log.
If you don't want to deal with editing text, you could create a command on your Windows PC using the "GUI Command Editor" then copy/paste the json text of that command from the "Text Command Editor" into your commands.json file.
@JoshuaJSlone, that's a known issue with the windows timeout command. It doesn't work in contexts that don't have an interactive terminal, like when running it via TRIGGERcmd. Here's one method that does work:
start /wait timeout 3
I've also added delays with the ping command like this:
ping 127.0.0.1 -n 2
@jett, you'll need to cancel your subscription from your Paypal account.
@Smart-Communities, why not click that Configure button for your short bookmark, and re-select the computer/command combo?
@Smart-Communities, I think you need to edit your short bookmark and re-select your reboot command.
I suspect maybe you renamed your computer?
I just did that - I added a space between New and Laptop. It made my short bookmark red because it no longer matched the computer name:

Then I fixed it by editing my short bookmark and re-selecting my reboot command:

About your idea to have the ability to view all standard and short bookmarks - it's not possible to show the standard bookmarks because they're not stored in the database. The standard bookmarks use a JWT, which is what makes them so long.
@Smart-Communities the red on the Short Bookmarks page is not showing the online/offline status of the computer. It's telling you that the command/computer combo no longer exists.
For example, I have a command on my DS computer that no longer exists. I probably renamed it.

If you click Configure, it will show you a blank Select Trigger field with a pulldown to re-select something for that short bookmark URL.

About your other questions:
Q: Possible to display short bookmarks on the main " View Triggers" screen?
A: It's currently not possible. How about I move the "Create Short Bookmark" button to the top of the Bookmark page?
Q: Create shorter bookmarks by default?
A: I think there's a legit use case for the original bookmarks. They're meant to be ephemeral - they can expire after a set number of hours. They're also invalidated if the command is deleted or renamed. Short bookmarks are valid as long as the computer name and command name they refer to exist.
Q: If I delete offline devices will they come back if eventually online again?
A: No, so if you delete a computer, you'll need to re-input a token when the agent runs on it again, and it will create a new computer record for it at that point.
@JRSF-Home, sorry, I forgot (again) that timeout command doesn't wait if the script is running in a context that doesn't have a console, like when the TRIGGERcmd agent runs it. It just moves on immediately.
This chatgpt session explains it: https://chatgpt.com/share/691cf13c-2f64-8004-8b12-78dd516285a9
You can see here that another user had the same problem.
My suggestion is to use the ping command like this:
ping 127.0.0.1 -n 3
Adjust that 3 number depending on how long you want to wait.
Here's my improved script that uses ping to wait, and it uses CTRL-SHIFT-J to go directly to the Console tab:
"C:\Program Files\Google\Chrome\Application\chrome.exe" --new-window https://www.youtube.com/watch?v=1I9qC0Zo_PM
ping 127.0.0.1 -n 5
REM x y width height
nircmd win setsize stitle "Allow friends to control Spotify" 200 100 800 1000
nircmd win activate stitle "Allow friends to control Spotify"
REM open dev tools to the Console tab
nircmd sendkeypress ctrl+shift+j
ping 127.0.0.1 -n 3
REM type document.querySelector('like-button-view-model button[aria-label*="like"]').click()
nircmd sendkeypress d o c u m e n t 0xBE q u e r y Shift+s e l e c t o r Shift+0x39 0xDE l i k e 0xBD b u t t o n 0xBD v i e w 0xBD m o d e l spc b u t t o n 0xDB
nircmd sendkeypress a r i a 0xBD l a b e l Shift+0x38 0xBB Shift+0xDE l i k e Shift+0xDE 0xDD 0xDE Shift+0x30 0xBE c l i c k Shift+0x39 Shift+0x30
REM press enter
nircmd sendkeypress 0x0D
ping 127.0.0.1 -n 2
REM close dev tools
nircmd sendkeypress F12
Here's a video of it working on my laptop: https://youtu.be/p-uJ8j1__zw