Vigil
Skill JakubKontra/skills/vigil
Security scanning, automated web testing, and SEO diagnostics — three Claude Code skills that work out of the box
npx -y skills add JakubKontra/skills --skill vigilAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
macOS-native self-supervising watchdog that monitors a project autonomously in the background. Launch it once and it keeps watching after you close the terminal, sleep, or restart — a launchd agent periodically runs read-only health probes (tests, build, dependency audit, a localhost health URL, git drift, disk space) and fires native macOS notifications plus an optional spoken summary when something changes or breaks. Read-only by default with code-enforced safety rails; no LLM runs in the watch loop. Use when the user wants to "watch", "monitor", or "keep an eye on" their project, asks to "notify me when" tests break or the build fails, wants a "self-watching" or "macOS background" monitor or an overnight/unattended health guardian, or wants to install, check on, triage, or stop a background watcher. Has a second mode — "task completion": give a plain-language task and Vigil periodically uses a read-only AI judge to check whether it's done, then notifies you and uninstalls itself (one-shot). Use task mode when the user says "tell me when this task is done", "check if X is finished", "watch until the feature is complete", or "let me know when the work is done". Configured via vigil.config.json in the project root.
SKILL.md
15.0 KB, as published. Nobody here has run it
Vigil
You set up and operate a macOS-native self-supervising watchdog. The operator launches it once;
it then watches their project on its own — surviving terminal close, sleep, and restart — and
notifies them natively when something changes or breaks. launchd owns the loop, not the Claude
session: a per-user LaunchAgent runs cli.mjs tick every N minutes, and each tick is pure,
deterministic Node — no LLM in the loop, so it costs nothing and works offline while watching.
You are involved only at the edges: at setup (detect the project, design good probes, install the agent) and on on-demand triage (the operator later asks "what broke?"). You are read-only by default with code-enforced safety rails — you never commit, deploy, or run destructive commands.
Prerequisites
Verify in order:
- macOS: Run
uname -s— must beDarwin. Vigil relies onlaunchd,osascript/say, andcaffeinate. If not macOS, stop and tell the operator Vigil is macOS-only. - Node.js: Run
node --version. The persistence CLI needs Node 18+. - Config (optional at first): There may be no
vigil.config.jsonyet — that is expected; you build one in Step 2. Readreferences/config-schema.mdfor the full schema andreferences/macos-gotchas.mdfor the launchd/notification/sudo caveats.
Two Tools
1. Claude's built-in tools — detect, design, triage
Use Read, Glob, Grep, and read-only Bash to detect the project type, propose sensible probe commands, and later triage a failing probe by reading the offending code.
2. Persistence + launchd CLI
node <skill-directory>/scripts/cli.mjs <command> [args...]
All commands output JSON to stdout. The launchd loop calls tick directly — no model involved.
CLI Command Reference
| Command | Description | Input |
|---|---|---|
config | Resolved config (merges vigil.config.json + defaults), emits _configFound | — |
init | Write a starter vigil.config.json (refuses overwrite without --force) | stdin: probes JSON |
task | Task mode: one command — write a one-shot AI-judge config for a plain-language task and install it | "<description>", --interval, --model, --speak, --force |
install | Generate + load the launchd agent so it watches across restarts (--dry-run previews) | — |
status | Is it watching? launchd loaded, last/next run, heartbeat staleness, failing probes | — |
tick / run-once | One watch cycle: run probes → compare to last snapshot → record → notify on change. launchd calls this | --no-notify, --probe <name> |
probe <name> | Run one probe ad hoc with stdout/stderr tail; no snapshot, no notify (triage) | <name> |
history | Recent tick events for trend/check-in | --limit N |
notify | Fire a TEST notification (+ optional --speak) to verify macOS permission | --message, --speak |
halt / resume | Pause/unpause ticks without uninstalling (touches .vigil/HALT) | — |
stop / uninstall | Unload + remove the launchd agent | --keep-plist |
When invoked from launchd, every command receives --project <abs> so it never relies on cwd.
Execution Protocol
Step 0: Load configuration
node <skill-directory>/scripts/cli.mjs config
If _configFound is false, there is no config yet — go to Step 1 to build one. If true, an
existing watcher is configured; branch on what the operator asked (the argument-hint verb):
status/triage → Step 5/6, stop → Step 7, otherwise re-install from Step 3.
Step 1: Verify macOS + prerequisites
uname -s # must be Darwin
node --version
sw_vers -productVersion
If uname -s is not Darwin, stop here — Vigil only runs on macOS.
Step 2: Detect the project & design probes
Inspect the project to propose probes tailored to its stack. Read in parallel:
ls -a
cat package.json 2>/dev/null
Use Glob/Grep to confirm which test/build scripts actually exist and whether there's a dev-server health endpoint. Propose probes by stack:
- Node/JS →
npm test,npm run build,npm audit --omit=dev(only scripts that exist inpackage.json). - Python →
pytest -q,pip-audit. - Go/Rust →
go test ./.../go build ./..., orcargo test/cargo build. - Health URL → if a dev server / port is configured, add an
httpprobe tohttp://localhost:<port>/health(or/). - Always add a
git-driftprobe and adiskprobe.
Present the proposed probe list, interval, and notification settings, and ask the operator to
confirm or edit. Make clear that every shell command you propose will be added to
safety.commandAllowlist — and nothing outside that allowlist can ever run. Only after they
confirm, write the config:
echo '{"schedule":{"intervalMinutes":30},"notifications":{"enabled":true,"minSeverity":"medium","speak":false},"probes":[{"name":"tests","type":"shell","command":"npm test","severity":"high","timeout":120000},{"name":"git-drift","type":"git","severity":"low"},{"name":"disk","type":"disk","threshold":"10GB","severity":"medium"}]}' \
| node <skill-directory>/scripts/cli.mjs init
Expected:
{ "command": "init", "status": "ok", "path": "/abs/vigil.config.json", "created": true, "probeCount": 3 }
Then validate the config with one full pass — no notifications, no snapshot pollution:
node <skill-directory>/scripts/cli.mjs tick --no-notify
Review results[]. If a probe is skipped (e.g. a command not on the allowlist) or fails due to
mis-detection (wrong command, wrong port), fix the config — re-run init --force with corrected
probes — and re-run. A probe legitimately failing (e.g. tests currently red) is fine; a
skipped/error from bad config is not. Do not install a config that errors on setup.
Step 3: Install the launchd agent
node <skill-directory>/scripts/cli.mjs install
Expected:
{ "command": "install", "status": "ok", "label": "com.vigil.<slug>",
"plistPath": "/Users/me/Library/LaunchAgents/com.vigil.<slug>.plist",
"intervalMinutes": 30, "wakeForRun": false, "loaded": true, "kicked": true,
"notes": ["Notification permission may need approval on first fire — run `notify --test` and click Allow.",
"wakeForRun=false: probes run on the next interval after the Mac wakes ..."] }
To show exactly what gets installed first, run install --dry-run (returns the plist XML and the
launchctl commands without loading). Relay the notes[] honestly — especially that with
wakeForRun:false, Vigil watches whenever the Mac is awake but does not wake a sleeping Mac. If
the operator wants guaranteed overnight runs, surface the exact sudo pmset repeat wake ...
command from references/macos-gotchas.md for them to run (Vigil never runs sudo itself).
Step 4: Verify it's watching + test the notification
node <skill-directory>/scripts/cli.mjs status
node <skill-directory>/scripts/cli.mjs notify --test
status should show "watching": true, "launchdLoaded": true. Ask the operator whether the macOS
banner from notify appeared. If not, relay the note: macOS notification permission must be
granted to the terminal app (or Script Editor / terminal-notifier) under System Settings →
Notifications, and Focus/Do-Not-Disturb suppresses banners. Vigil keeps watching either way — the
event log is the source of truth.
Step 5: How the operator checks in later
Tell them they can return any time — even with no terminal open — and run:
node <skill-directory>/scripts/cli.mjs status # is it still watching? what's failing now?
node <skill-directory>/scripts/cli.mjs history --limit 10
A notification fires only when a probe's state changes (newly broke / recovered / git drift changed), never every interval — so silence means steady state.
Step 6: On-demand triage ("what broke?")
When the operator later asks you to investigate a failing probe:
node <skill-directory>/scripts/cli.mjs status # find the failing probe name(s)
node <skill-directory>/scripts/cli.mjs probe <name> # re-run it, get stdout/stderr tail
Then diagnose with built-in tools: read the error from result.stderrTail, Grep/Read the offending
files, and explain the root cause and a proposed fix. Do not auto-apply fixes or commit — Vigil
is read-only; propose the change and let the operator decide. After they fix it, the next tick
(or a manual probe) confirms recovery and the next change-triggered notification announces it.
Step 7: Stop / uninstall
node <skill-directory>/scripts/cli.mjs stop
node <skill-directory>/scripts/cli.mjs status # confirm "watching": false
This unloads the LaunchAgent and removes its plist. vigil.config.json and .vigil/ remain so the
operator can re-install later; to discard collected history, they can rm -rf .vigil (don't run
destructive deletes yourself unless they explicitly confirm). To pause without uninstalling, use
halt / resume.
Mode B — Task completion (AI judge, one-shot)
Use this when the operator wants "tell me when this task is done" rather than "tell me when something breaks". Instead of health probes, Vigil watches a single plain-language task and, each tick, asks a read-only AI judge whether it's complete. When it is, Vigil notifies and uninstalls itself (one-shot).
One command
node <skill-directory>/scripts/cli.mjs task "create a file named DONE.md in the repo root" --interval 15
This writes a one-shot vigil.config.json (a single task probe + schedule.oneShot: true) and
installs the launchd agent — equivalent to running Mode A's init + install. Flags: --interval <min> (default 15), --model <alias> (default haiku), --speak, --force (replace an existing
config). Phrase the task as a concrete, verifiable outcome ("all components in src/forms/ have
TypeScript types", "the /health endpoint returns 200", "tests in auth.test.ts pass").
How the judge works
Each tick spawns claude -p headless with a strict read-only prompt and a JSON schema, and parses
the verdict { done, confidence, reasoning }:
echo "<judge prompt>" | claude -p --output-format json --json-schema '{...}' \
--permission-mode dontAsk --allowedTools "Read,Grep,Glob" --model haiku --add-dir <project>
done && confidence ≥ threshold ⇒ probe ok (task complete); otherwise fail (not yet). The judge
gets only Read/Grep/Glob — it inspects, never edits. Vigil only judges completion; it does not
perform the task (you, or a separate coding session, do the work).
Lifecycle & check-in
- Setup: run the
taskcommand, then verify withstatus+notify --test(Steps 3–4 of Mode A). - While not done: silent (the not-done baseline doesn't notify).
- When judged done: a "✅ Task done" notification fires (message = the judge's reasoning), a
completedevent is logged, and the launchd agent self-uninstalls (status→watching:false). - Operator can check progress any time with
status/history, or stop early withstop.
Honest caveats (relay these)
- There IS an LLM in this loop (unlike Mode A). Each tick is a full headless Claude run that
costs tokens (~$0.05–0.15 with
haiku). Pick the interval consciously. - Needs
claudeon PATH and working auth under launchd (the plist setsHOME+ adds~/.local/bin; uses the existing subscription, no API key on a normal Mac). Fallbacks inreferences/macos-gotchas.mdif auth fails. - The judge can be wrong — the notification carries its reasoning so the operator can verify; the
.vigil/state is kept after the one-shot stop for review.
Important Notes
Read-only by default — code-enforced, not prompt-enforced
- Probes are health checks, never mutations. Shell probes spawn with
{ shell: false }and an argv array — no shell, no metacharacter evaluation. - Only allowlisted shell commands run. A
shellprobe whose command is not an exact match insafety.commandAllowlistis skipped (status: "skipped"). You add a command to the allowlist only with the operator's explicit confirmation in Step 2. - No commits, deploys, or destructive ops. With
blockDestructive(default on), even an allowlisted command is rejected if it matches the denylist (rm,git push,git commit,sudo,npm publish,deploy,kill, output redirection>, mutating HTTP verbs, …). httpprobes are GET-only and localhost-only unlesssafety.allowRemoteHttpis explicitly set.- Vigil only ever writes inside
.vigil/and the single launchd plist.
No LLM in the loop
- launchd invokes
tickdirectly; it is deterministic — run probes, compare, record, notify. No model is in the overnight loop, so watching is free and offline. Claude is only involved at setup (Step 2) and on-demand triage (Step 6), both operator-initiated.
Sleep / wake honesty (sudo)
- By default Vigil does not wake a sleeping Mac (
wakeForRun:false); probes due during sleep run on the next interval after wake (launchd coalesces missed runs into one). This needs no permissions. - Guaranteed overnight wakes require
sudo pmset repeat wake ...(and AC power for a closed lid). Vigil never runs sudo — surface the exact command for the operator to run.
Anti-spam notifications
- A notification fires only on a state transition above
notifications.minSeverity. A probe that stays broken does not re-notify each interval.
Full uninstall
stopunloads + removes the LaunchAgent plist..vigil/andvigil.config.jsonstay; delete.vigil/to discard history. Re-runinstallany time to resume.