What if your support team could get ticket alerts as soon as they hit the queue—no lag, no extra dashboard, just a silent ping on their phones? I built a tiny CLI that hooks into the client‑side notification API, turns a REST endpoint into a real‑time notification bus, and delivers those alerts right to the device that needs them. The tool’s name? tm-cli (Ticket Master CLI). It costs $50, but the payoff is a single line that tells you “New ticket, priority A” before you even open your inbox.
The Problem
Ticket‑management systems are great, but they suck when the data lives behind a web app that never pushes anything. Every engineer had to scrape the page or open a dashboard, only to wait 5–10 minutes for the next priority ticket. That delay sabotages triage, skews metrics, and turns a fast‑moving support squad into a stagnant one. I needed a way to turn the static API I’d already been calling from browser‑side code into a stream of zero‑lag, client‑side notifications.
The Build
TL;DR: Listen for the API, expose the token, ask for permission, push!
The architecture is deceptively simple.
-
API Wrapper – I forked the existing client‑side wrapper for the ticket API so the CLI could issue identical GET requests
/api/tickets?new=true. -
Device Token Hook – Using the PushManager API, I attach a service worker‑like shim that registers a push subscription token with our lightweight push relay (Firebase Cloud Messaging under the hood). The token lives in
~/.tmcli/token.jsonso it can be reused without prompting the user every run. -
Notification Permission Prompt – Unlike the web, a CLI can’t natively show a UI prompt. I solved this by invoking
osascripton macOS orzenityon Linux to pop a small native dialog. The user must grant “Allow notifications from tm-cli” once; thereafter the binary reads the config file. -
Polling Loop – Rather than a long‑polling server, I used Server‑Sent Events on a lightweight Node server that forwards the JSON payload to any registered CLI via HTTP. The CLI simply opens a
fetch('http://localhost:4000/events')stream, stays alive, and prints anything that appears. -
CLI Integration – The CLI accepts a
--liveflag to kick the event listener on startup. If you runtm-cli --live, it stays open, listens, and triggers the system notification dispatcher (osascript -e 'display notification "$msg" with title "Ticket Alert"'). For brevity, the code keeps a queue of the last 10 alerts to avoid flooding.
Key Decisions
- No websockets: MTU and firewall constraints on corporate networks made websockets fragile. SSE over HTTP/2 kept it robust.
- Local push relay: Setting up a PRISM‑style push service is overkill for a dev‑tool. I opted for Firebase because it has an open‑source web SDK and is free for the volume I use.
-
Single‑file distribution: Users grab a binary from the releases page, unzip to
/usr/local/binor./. No npm or pip install; I wanted zero friction.
Challenges
The hardest part was handling notification permissions in a non‑browser environment. I tried to intercept the browser prompt via a headless Chromium instance, but that introduced a massive dependency chain. The stub native dialog approach was the only cross‑platform path that truly felt native.
The Result
Running tm-cli --live on my Mac now gives me instant, apple‑style notifications for any ticket with priority: A or higher. No more drifting across windows. If I miss it, the CLI logs the event to a tm-cli.log file. The wake‑up latency is <350 ms, from the API to my phone. The tool also exposes an --export flag to dump all alerts to a JSON file for audit.
What makes it special is that I could deliver this in a single commit, pushed to a repo, released as a binary, and had dozens of team members glued to their desks because the tickets just happen.
Code Snippet (≈15 lines)
js
#!/usr/bin/env node
const { http } = require('node:http')
const ora = require('ora')
const notifier = require('node-notifier')
const fs = require('fs')
const { execSync } = require('child_process')
const token = JSON.parse(fs.readFileSync(process.env.HOME + '/.tmcli/token.json', 'utf8')).pushToken
if (!fs.existsSync(process.env.HOME + '/.tmcli/token.json')) {
execSync('osascript -e \'display dialog "Authorize notifications?" with title "tm-cli" buttons {"Cancel", "Authorize"} default button 2\'')
fs.writeFileSync(process.env.HOME + '/.tmcli/token.json', JSON.stringify({pushToken:'ABC123'}))
}
http.get('http://localhost:4000/events', res => {
const spinner = ora('Listening for tickets…').start()
res.on('data', chunk => {
const msg = JSON.parse(chunk.toString())
if (msg.priority >=
---
## Get the Production-Ready Version
Don't want to build it yourself? We have production-ready versions at [https://against-surrounded-washington-solaris.trycloudflare.com](https://against-surrounded-washington-solaris.trycloudflare.com).
**What you get:**
- Complete, tested Python code
- Documentation and setup guides
- Instant delivery after crypto payment
- Free updates
[Browse the collection →](https://against-surrounded-washington-solaris.trycloudflare.com)
Top comments (0)