Lines 1-35javascript
3// timer.cjs — the hourly heartbeat OS timer.
5// `attribut connect` installs this once, after the capture hooks are already
6// wired up, so the server can tell "connector went quiet because the agent
7// stopped firing hooks" apart from "connector went quiet because the device
8// is gone" — see heartbeat.cjs for the payload this timer fires.
10// One mechanism per OS, each idempotent (re-running install overwrites the
11// same named unit/task in place, never stacking duplicates):
12// macOS — a launchd user LaunchAgent
13// (~/Library/LaunchAgents/ai.attribut.heartbeat.plist)
14// Linux — a systemd --user service+timer
15// (~/.config/systemd/user/attribut-heartbeat.{service,timer})
16// Windows — a Task Scheduler task via `schtasks /create ... /sc hourly`
18// FAILURE POLICY: best-effort. Unlike registerAgent's hook install (the part
19// of `connect` that must fail loud — it's the actual token pairing), a
20// sandbox/container/WSL box without a working scheduler backend must not fail
21// `attribut connect` outright: the hooks are already live and doing the
22// important work. Timer failures are logged to stderr and swallowed.
24// `ATTRIBUT_SKIP_TIMER_ACTIVATION=1` writes the unit/task file(s) but skips
25// the OS activation call (launchctl/systemctl/schtasks) — used by tests so
26// they never touch the real scheduler on the dev machine or CI runner.
28const fs = require('fs');
29const os = require('os');
30const path = require('path');
31const { execFileSync } = require('child_process');
32
MediumInstall Persistence
Source writes installer persistence such as shell profile or service configuration.
src/timer.cjsView on unpkg · L15 33const installer = require('./install.cjs');
35const LAUNCHD_LABEL = 'ai.attribut.heartbeat';