# Nod: setup and usage guide for agents

You are an agent. Your human (the owner) gave you this URL. Read it, check whether Nod is already set up, set it up if not, then use it.

Nod is a channel between you and your owner, on their iPhone and Apple Watch. It has three verbs:
- **send**: tell the owner something (a result, an alert, a summary). One way. Returns at once.
- **ask**: ask a short typed question: pick one of up to 4 options, or a short free text. It has a deadline and a default. It never blocks you.
- **wait_for_reply**: get the answer to an ask. It returns the moment the owner answers, or when the deadline applies the default.

It also holds the owner's **to-do list** (`todo`): things the owner wants to do or think about later. You can read it, add to it, edit it and check items off.

Server: `https://nod.pingbox.lol`. MCP endpoint: `https://nod.pingbox.lol/mcp` (Streamable HTTP, OAuth 2.1).

## 1. Check: is Nod already connected?

- You have the MCP tools `send`, `ask`, `wait_for_reply` **and** `sender` (server `nod`): you are connected as the owner. Go to step 3.
- You have `send`, `ask`, `wait_for_reply` and `todo` but **no** `sender`: you are connected with a sender token, as one fixed sender. Omit the `sender` field. Go to step 4.
- The env var `NOD_SENDER_TOKEN` is set: use REST (step 5) and read step 4.
- None of these: go to step 2.

## 2. Connect (once per machine)

The owner needs a Nod account first. Nod is an invite-only beta (see Access at the end). If they have none, stop and tell them.

The owner must log in once in a browser: they enter their Nod email (or account id), and then tap on their phone, in the Nod app, the number the page shows. You cannot do that part. Run the command, then tell the owner exactly what to do.

**Claude Code**
```sh
claude mcp add --transport http --scope user nod https://nod.pingbox.lol/mcp
```
`--scope user` makes Nod available in every project on this machine. Then tell the owner:
"Run `/mcp` in Claude Code, pick `nod`, choose Authenticate, and log in to Nod in the browser. If the Nod tools do not show up, restart Claude Code and say: continue Nod setup from https://nod.pingbox.lol/setup."
Then stop and wait. After the login (or the restart), start again at step 1.
If the browser login fails ("Not approved"): the owner tapped a wrong number, or did not answer within 5 minutes, or the email has no active Nod account. Try again. Five tries per email in 15 minutes, then wait.

**Codex**
```sh
codex mcp add nod --url https://nod.pingbox.lol/mcp
codex mcp login nod
```
`codex mcp login` opens a browser. Tell the owner to log in there.

**Other MCP clients** (Claude.ai and ChatGPT connectors, Cursor, and others): add a remote MCP server with the URL `https://nod.pingbox.lol/mcp`. The client finds OAuth by itself (`/.well-known/oauth-protected-resource/mcp`).

**No MCP** (scripts, CI, cron, cloud alarms): an agent that is connected creates a sender for the script (step 3). Put its token in the script's secret store as `NOD_SENDER_TOKEN`, and use REST (step 5).

## 3. First run: make your own sender

Every agent, script or alarm sends as its own named sender. The owner sees the name, and can mute or limit each one.

1. Pick your name: one sender per agent and project (repo), shared by all your sessions in that repo. Form: `<agent>-<repo>`, for example `claude-billing-api` or `codex-landing`. Lowercase letters, digits and dashes, at most 64 characters.
2. Call `sender` with `{"action":"list"}`. It returns the senders with their `name`. If your name is in the list, reuse it and skip to item 4.
3. If not, call `sender` with `{"action":"create","name":"<name>","description":"<what you do, where>","project":"<repo>","platform":"<what you run in, e.g. Claude Code>"}`.
   `project` is the sender's default project, usually the repo name (for example `billing-api`). Your messages are grouped under it. It is created on first use. An existing sender without a project: `{"action":"update","name":"<name>","project":"<repo>"}`.
   The result holds a send-only token, shown only this once. You do not need it yourself: with an owner login you send by passing `"sender":"<name>"`. Keep it only when a script, CI job or alarm will send as this sender. Then write it straight into that system's secret store (for example `gh secret set NOD_SENDER_TOKEN`), and never print it in chat, logs or commits. A lost token: `{"action":"rotate_token","name":"<name>"}`.
4. Send one test message so the owner sees it works:
   `send {"sender":"<name>","title":"Nod connected","body":"<you> can now reach you here.","level":"passive","key":"<name>-hello"}`
5. Optional, only if the owner agrees: add one line to the project's `CLAUDE.md` or `AGENTS.md` so later sessions know about it:
   `Reach the owner through Nod (MCP server "nod", sender "<name>"). Guide: https://nod.pingbox.lol/setup`

## 4. How to use it well

Nod is a mechanism, not a mailbox. Use it whenever you need the owner and they are not looking at your session. Mostly you tell; sometimes you ask.

**send** for:
- A long task is done, or it failed. Give the result in the title.
- Something needs attention: an alert, a broken build, a cost spike.
- A short scheduled summary. Use a template (the `template` tool) for a layout the owner keeps.

**ask** for:
- Approval before something irreversible or costly: a deploy, a migration, a delete, a purchase, an email to a customer. Use `default: "hold"`.
- A choice when you are blocked between options: `options: ["plan A", "plan B", "stop"]`.
- A short instruction or value you need: `options: []` and `allow_text: true`. The answer is up to 200 characters.
- A check-in during long work: "Keep going, or change direction?", with options such as `["continue", "pivot", "stop"]` and `allow_text: true`, so the owner can steer you in one line.

**todo** for things the owner wants to keep and come back to:
- The owner says "remind me", "note this", "add a to-do", "I want to think about this later". Add it.
- An open question that needs the owner's thought, not a quick answer: a design choice, pricing, a product direction. Add a to-do with the options in `note`, instead of an ask the owner must answer now.
- The owner asks what is on their list, or asks you to tidy, edit or check items off. `list`, then `update`, `done`, `reopen` or `delete` by `id`.

A to-do is not an approval and not a report. If you need a decision to keep working, use **ask**. If you only report, use **send**. Every change you make to a to-do (add, update, done, reopen, delete) pushes the owner at level `active`, and counts in your caps like a message. A call that changes nothing does not push. So change only what you mean to, and do not also `send` about the same to-do.

```
todo {"action":"add","title":"Decide pricing for the Pro plan","note":"Options: $9 or $15. Churn data is in the billing repo.","project":"billing-api","key":"pricing-todo"}
  -> {"todo":{"id":"todo_…","status":"open","added_by":"claude-billing-api",…},"status":"added"}
todo {"action":"list"}                          (open items; "include_done":true adds the last 7 days)
todo {"action":"done","id":"todo_…"}            (also: reopen, delete, update with title/note/due/project)
```
- Write the `title` as the thing to do or decide, short enough for a lock screen. Put the context the owner will need later in `note` (up to 2000 characters): links, options, why it matters.
- `due` is optional free text ("Fri", "2026-10-30"). `project` is optional; use the repo the item is about.
- With an owner login, pass `"sender":"<name>"` on every change (`add`, `update`, `done`, `reopen`, `delete`), so the owner sees who did it and gets the push. Without it the change counts as the owner's own, and nothing pushes. With a sender token, it is filled in.
- The list is the owner's. Change or delete only what the owner asks you to, or what you added and know is finished.

**The ask loop**
```
ask {"sender":"deploy-agent","question":"Run migration 042 on prod?","body":"Adds 2 columns. 30 s lock.","options":["approve","hold"],"default":"hold","timeout_seconds":900,"key":"mig-042"}
  -> {"ask_id":"ask_…","status":"pending","deadline_at":"…"}
wait_for_reply {"ask_id":"ask_…"}   (holds up to 45 s; call it again while status is "pending")
  -> {"status":"answered","answer":"approve","answered_by":"owner"}
  or {"status":"expired","answer":"hold","answered_by":"default"}
```
- You can do other work between `wait_for_reply` calls. The deadline is enforced by the server, even if you crash or stop.
- Act on `answer` in both cases. `answered_by: "default"` means the owner did not answer in time.
- Pick `timeout_seconds` to match how long you can wait: 120 for "right now", hours or days for "when you can". Range 30 s to 7 days.

**Rules for every call**
- With an owner login, every `send` and `ask` needs `"sender":"<name>"`. With a sender token, omit it.
- `project` is optional on `send` and `ask`. Without it, a message takes your sender's default project. Name another one only when the message is about a different repo.
- Limits: `title` and `question` up to 200 characters, `body` up to 2000, at most 4 `options` of up to 40 characters each (default `["approve","hold"]`), a text answer up to 200 characters.
- `default` is the answer applied at the deadline. Use one of the options (for a text-only ask, a safe phrase such as "no answer, skip"). It is required.
- Every `send` and `ask` needs a `key`, unique per logical event (for example `deploy-v1.4.2`). Retrying with the same key never makes a duplicate, so retry freely after an error or timeout.
- `level`: `inbox` (silent, no push), `passive` (a push with no sound; the default for send), `active` (banner and sound, not time-sensitive; Focus holds it), `ring` (sound, breaks through Focus; the default for ask). Use `active` when the owner should see it soon, and `ring` only when the owner must look now.
- Keep titles short and readable on a lock screen. Put detail in `body`. Send data, not documents.
- The owner controls the channel. Rules can lower your level, or cap you (5 pushes per sender per hour, 20 per owner per day). Then the message still lands silently in the inbox, and the result tells you why (`lowered_because`). Do not retry to get around a cap; send less.
- Errors explain themselves: `{"error":{"code","message","expected","example"}}`. Fix the call and send it again.

**Don't**
- Don't chat. One question per ask, not a conversation. If you need many answers, ask the most important one first.
- Don't send one push per step. Send the outcome.
- Don't put secrets in messages.

## 5. REST (for scripts, with a sender token)

```sh
NOD=https://nod.pingbox.lol
# send
curl -s -X POST $NOD/v1/messages -H "Authorization: Bearer $NOD_SENDER_TOKEN" -H 'content-type: application/json' \
  -d '{"title":"Nightly build passed","level":"passive","key":"build-2026-10-17"}'
# ask, then wait (repeat the GET while status is "pending")
curl -s -X POST $NOD/v1/asks -H "Authorization: Bearer $NOD_SENDER_TOKEN" -H 'content-type: application/json' \
  -d '{"question":"Deploy v1.4.2?","options":["approve","hold"],"default":"hold","timeout_seconds":600,"key":"deploy-v1.4.2"}'
curl -s "$NOD/v1/asks/<ask_id>?wait=40" -H "Authorization: Bearer $NOD_SENDER_TOKEN"
# to-dos: add, list, check off
curl -s -X POST $NOD/v1/todos -H "Authorization: Bearer $NOD_SENDER_TOKEN" -H 'content-type: application/json' \
  -d '{"title":"Look at the flaky checkout test","note":"Failed 3 of the last 20 runs.","key":"todo-flaky-checkout"}'
curl -s $NOD/v1/todos -H "Authorization: Bearer $NOD_SENDER_TOKEN"
curl -s -X PATCH $NOD/v1/todos/<id> -H "Authorization: Bearer $NOD_SENDER_TOKEN" -H 'content-type: application/json' -d '{"done":true}'
```
A sender token also works as a bearer on `https://nod.pingbox.lol/mcp`. It sees only `send`, `ask`, `wait_for_reply` and `todo`.

## Remove

- Stop a sender: `sender {"action":"delete","name":"<name>"}`. The owner confirms it in the app, because it makes them less reachable.
- Remove the client: `claude mcp remove nod --scope user` or `codex mcp remove nod`.

## Access

Nod is a closed beta. The owner makes an account with Sign in with Apple in the Nod iPhone app; an admin then lets them in. Older accounts were made with an invite code at `https://nod.pingbox.lol/join`.
