Axol

A tiny axolotl that lives in the corner of your screen, surfaces alerts from any tool that can POST JSON, and cheers you on.

View on GitHub

Alerts with a face.

Any tool that can curl can pop her speech bubble. Click to jump back to the terminal, URL, or file that needs you.

A little presence.

Drags anywhere on screen. Nudges you to breathe, hydrate, stretch. Fidgets and naps when things go quiet.

Nothing heavy.

One 700 KB Swift binary. No Electron, no helpers, no telemetry. Idles at 0 % CPU and ~40 MB RAM.

install

On macOS with the Xcode command line tools:

git clone https://github.com/Roach/axol.git
cd axol
./build.sh
./axol

send her alerts

Axol listens on 127.0.0.1:47329. Anything you can curl can talk to her — post an envelope from a script, a Makefile, a CI step:

curl -s -X POST http://127.0.0.1:47329/ \
  -H 'Content-Type: application/json' \
  -d '{
    "title":    "ci-bot",
    "body":     "build passed on main",
    "priority": "normal",
    "source":   "github-actions",
    "actions":  [{ "type": "open-url", "url": "https://github.com/.../runs/1" }]
  }'

Required field: title. Priority high / urgent floats worry bubbles above her head; urgent also pins the speech bubble open until you handle it. Actions are a closed set — focus-pid, open-url, reveal-file, noop — so there's no shell execution path.

lightweight plugins

Adapters are JSON files that translate a third-party payload into Axol's envelope. Drop one into ~/Library/Application Support/Axol/adapters/ and anything that POSTs JSON to localhost:47329 can talk to her — no code required.

incoming

Whatever JSON your tool already emits. GitHub sends this to a webhook on every workflow run.

{
  "workflow_run": {
    "conclusion":  "failure",
    "head_branch": "main",
    "html_url":    "https://github.com/.../runs/42"
  }
}
adapter

match picks the adapter. switch + cases choose a template. {{…}} placeholders are filled from the payload.

{
  "match":  { "field": "workflow_run", "exists": true },
  "switch": "workflow_run.conclusion",
  "cases": {
    "failure": {
      "title":    "{{workflow_run.head_branch}}",
      "body":     "CI failed",
      "priority": "urgent",
      "actions":  [{ "type": "open-url",
                     "url":  "{{workflow_run.html_url}}" }]
    }
  }
}
envelope

Axol's common shape. Any payload that reaches the bubble goes through this — adapters can't smuggle in unknown action types or shell commands.

{
  "title":    "main",
  "body":     "CI failed",
  "priority": "urgent",
  "actions":  [{ "type": "open-url",
                 "url":  "https://github.com/.../runs/42" }]
}
bubble

Urgent bubbles pin open until clicked. Clicking runs the first action — here, opening the workflow run in the browser.

main CI failed

One adapter ships with Axol (adapters/claude-code.json, for Claude Code hooks). Everything else you add lives in ~/Library/Application Support/Axol/adapters/.

Template language — deliberately tiny:

{{field}} top-level substitution {{nested.field}} dot-path into objects {{field | default 'x'}} fallback when missing {{basename path}} last path component {{trim field}} strip surrounding whitespace

Full spec in the README →