Long Task Completion Notifications
“Left for lunch while the model is training” — a classic. To avoid coming back in an hour only to find the job finished 50 minutes ago (or crashed after 30 seconds), just send a message to Notifly from the script itself or its wrapper.
Universal CLI wrapper
Section titled “Universal CLI wrapper”Run any long command through this wrapper — you’ll get a message about success/failure and duration:
#!/usr/bin/env bashset -euset -a; source ~/.notifly.env; set +a
CMD="$*"START=$(date +%s)HOST=$(hostname -s)LOG=$(mktemp)
if "$@" 2>&1 | tee "$LOG"; then DUR=$(( $(date +%s) - START )) LAST=$(tail -c 800 "$LOG") /usr/local/bin/notifly-send \ "✅ ${CMD:0:60}" \ "Длительность: ${DUR}sХост: $HOST
Конец вывода:$LAST" 5else RC=$? DUR=$(( $(date +%s) - START )) LAST=$(tail -c 800 "$LOG") /usr/local/bin/notifly-send \ "❌ ${CMD:0:60} (rc=$RC)" \ "Длительность: ${DUR}s
Конец вывода:$LAST" 9firm -f "$LOG"Usage:
notifly-run python train.py --epochs 100notifly-run rsync -aHAX /huge /backup/notifly-run docker compose pullWindows: PowerShell Notifly-Run
Section titled “Windows: PowerShell Notifly-Run”Windows equivalent of notifly-run. Use the Send-Notifly function from the reminders recipe.
param([Parameter(Mandatory, ValueFromRemainingArguments)] [string[]]$Cmd). $env:USERPROFILE\Documents\WindowsPowerShell\Notifly.ps1
$start = Get-Date$log = New-TemporaryFile$label = ($Cmd -join ' ').Substring(0, [Math]::Min(60, ($Cmd -join ' ').Length))
& $Cmd[0] $Cmd[1..($Cmd.Count-1)] *> $log$rc = $LASTEXITCODE$dur = ((Get-Date) - $start).TotalSeconds$tail = (Get-Content $log -Tail 20) -join "`n"
if ($rc -eq 0) { Send-Notifly -Title "✅ $label" ` -Message "Длительность: $([int]$dur)s`nХост: $env:COMPUTERNAME`n`nКонец вывода:`n$tail" ` -Priority 5} else { Send-Notifly -Title "❌ $label (rc=$rc)" ` -Message "Длительность: $([int]$dur)s`n`nКонец вывода:`n$tail" ` -Priority 9}Remove-Item $log -Forceexit $rc# Alias in $PROFILESet-Alias nrun C:\scripts\Notifly-Run.ps1
nrun python train.py --epochs 100nrun docker compose pullnrun robocopy C:\src D:\backup /MIRThe Python examples on this page (decorator, Jupyter magic) work on Windows without changes — install httpx and set the environment variables:
[Environment]::SetEnvironmentVariable("NOTIFLY_URL", "https://your-notifly.example.com", "User")[Environment]::SetEnvironmentVariable("NOTIFLY_TOKEN", "AGdjfk_L.dKe8q", "User")Python: decorator for a function
Section titled “Python: decorator for a function”You can embed a notification directly into code:
import os, time, traceback, functools, httpx
def notify_on_finish(name=None, priority=5): def deco(fn): @functools.wraps(fn) def wrap(*args, **kwargs): label = name or fn.__name__ t0 = time.time() try: result = fn(*args, **kwargs) _send(f"✅ {label} OK", f"Длительность: {time.time()-t0:.1f}s", priority) return result except Exception as e: _send( f"❌ {label} FAIL", f"{type(e).__name__}: {e}\n\n{traceback.format_exc()[-1200:]}", max(priority, 9), ) raise return wrap return deco
def _send(title, message, priority): try: httpx.post( f"{os.environ['NOTIFLY_URL']}/message", params={"token": os.environ["NOTIFLY_TOKEN"]}, json={"title": title, "message": message, "priority": priority}, timeout=5, ) except Exception: pass
@notify_on_finish("Обучение CIFAR-10")def train(): for epoch in range(100): ...
train()Jupyter notebook: one-line magic
Section titled “Jupyter notebook: one-line magic”Sometimes it’s convenient to notify after a long cell. Put this at the start of the notebook:
def notify(title, message="", priority=5): import os, httpx httpx.post( f"{os.environ['NOTIFLY_URL']}/message", params={"token": os.environ["NOTIFLY_TOKEN"]}, json={"title": title, "message": message, "priority": priority}, timeout=5, )And in any cell after a long run:
model.fit(train_ds, epochs=50, validation_data=val_ds)notify("✅ fit() готов", f"val_acc={history.history['val_acc'][-1]:.3f}")ML / experiments: key metrics in the message
Section titled “ML / experiments: key metrics in the message”Send a brief summary — it’s convenient to read on a phone:
notify( title=f"✅ run {run_id} done", message=( f"Model: {model_name}\n" f"Time: {minutes:.1f} min\n" f"Loss: {final_loss:.4f}\n" f"Acc: {final_acc:.4f}\n" f"Best: {best_acc:.4f} @ epoch {best_epoch}" ), priority=4,)Interim “pings”
Section titled “Interim “pings””For very long jobs it’s useful to send “pings” to show the job hasn’t stalled:
for epoch in range(100): train_one_epoch() if epoch % 10 == 0 and epoch > 0: notify(f"⏳ {epoch}/100 эпох", f"loss={current_loss:.4f}, eta {eta_min}m", priority=2)Benefits
Section titled “Benefits”- Time freedom. You can leave and do other things instead of watching the terminal.
- Context in the message. No need to log back into the server — the key metrics are already in your pocket.
- No missed failures. Script crashed → you immediately see
❌.
What to improve next
Section titled “What to improve next”- Include a link to the “full log” in the message (for example, to a gist/paste in a private S3).
- Attach a plot (as a link to an image) via the
extras["client::display"].