# Add telemetry to a repo: anyrouter-cli

You are a coding agent. Follow these steps to add anonymous usage telemetry that reports to `anyrouter-cli`.

- Endpoint: `POST https://telemetry.anyr.workers.dev/anyrouter-cli`
- Project page (aggregate stats): https://telemetry.anyr.workers.dev/anyrouter-cli
- Stats JSON: https://telemetry.anyr.workers.dev/api/anyrouter-cli/stats
- Full API reference: https://telemetry.anyr.workers.dev/docs

## Payload

```json
{"event":"ping","id":"3f2c9a7e-1d4b-4c8e-9a52-7b1e0d6f4a21","version":"1.2.3","os":"linux","arch":"x64","ci":false,"props":{"command":"build"}}
```

| Field | Rules |
|---|---|
| `event` | Required. `^[A-Za-z0-9_.:/-]{1,64}$`. |
| `id` | Random install id the client creates once and keeps. Hashed on the server. |
| `version` | At most 32 chars. |
| `os`, `arch` | Free-form lowercase, at most 16 chars. Send what the runtime gives (`x64`, `x86_64`, `arm64`, `aarch64` are all fine). Do not remap. |
| `ci` | Boolean. `true` for a CI run. |
| `props` | At most 8 entries. Keys `[a-z0-9_.-]{1,32}`. String, number or boolean values. |
| `n` | Optional count, 1 to 1000. |

Success is HTTP 202. The server returns 400, 404, 413 or 429 otherwise. Ignore every response.

## How the server counts

- Any event with an `id` counts that id as an active user for that UTC day. `install` and `ping` are not special.
- So send `install` once on first run, then at most one `ping` per UTC day, plus a few meaningful events. More pings add no users.
- Days are UTC. The client's daily gate must use the UTC date.
- An event without `id` counts daily users only.

## Conventions used below

- **Set.** An env var counts as set for any value except empty, `0` or `false`. This applies to `DO_NOT_TRACK`, `CI` and every `ANYROUTER_CLI_TELEMETRY_*` flag.
- **Prefix and paths.** `ANYROUTER_CLI_*` and `~/.config/anyrouter-cli/` are defaults. Prefer the tool's own env prefix and config directory when it has them. For example, a CLI called `anyr` that already uses `ANYR_*` and `~/.anyrouter/` keeps those. Use the defaults only when the tool has no convention. Replace them everywhere below, in the code and in the README.

## Steps

### 1. Detect the language and entry point

Read the repo. Find the language, the runtime, the package manifest, the app version source and the entry point (main function, CLI parser, daemon loop, or app startup). Check the existing env prefix, config directory and config file, and the existing update-check or background code. Reuse them.

### 2. Pick the hook

- A once-a-day gate. Best places are an existing update check, a daemon tick, or startup. Send `ping` at most once per UTC day. Keep the last-sent UTC date (`YYYY-MM-DD`) in a small file next to the id. Write the stamp first, then send. If the stamp cannot be written, do not send.
- Send `install` once, when the id is first created.
- Key commands or features, as separate events (for example `run` with `props.command`). Keep names short and few. Never put user input in an event name.
- Commands that return before the startup hook, such as `--version` and `--help`, may stay untracked.
- For an install script, send one `install` event. Without an id it counts daily users only. That is fine.

### 3. Add a small telemetry module

Add one file with one function, `track(event, props)`. Start from the snippet for this language. Snippets for curl, shell, TypeScript, Rust, Go, Python and the browser are on https://telemetry.anyr.workers.dev/anyrouter-cli (Integrate tab) and in https://telemetry.anyr.workers.dev/docs. The ts, rust, go and python versions already include the id file, the opt-out check, the debug mode, fail-closed id handling and the timeout. They do not include the daily gate: add it.

Use https://telemetry.anyr.workers.dev/anyrouter-cli as the endpoint. Set the app version from the package metadata, not a literal.

### 4. Add opt-out, debug, CI skip, fail-closed and the first-run notice

- Disabled when `DO_NOT_TRACK` or `ANYROUTER_CLI_TELEMETRY_DISABLED` is set. Also add a config flag if the app has a config file. If the config file exists but cannot be read, do not send.
- Skip when `CI` is set, unless `ANYROUTER_CLI_TELEMETRY_CI=1`. When the opt-in is used, send `ci:true`.
- `ANYROUTER_CLI_TELEMETRY_DEBUG=1` prints the payload and sends nothing. It has no side effects: no id file, no stamp, no notice. If there is no id yet, print a placeholder. It may print on every due check.
- Keep the id at `~/.config/anyrouter-cli/telemetry-id` (use the platform config directory). Create it once with a random UUID.
- Fail closed. If the id or the daily stamp cannot be saved, skip sending. Otherwise a daemon would ping on every tick. Do not send without an id (an install script is the one exception).
- On first run, when the id file is created, show one line in the app's user-visible channel: stderr for a CLI, the log or notify channel for a daemon or plugin (stderr may be ignored there), the UI for a desktop app:
  `anyrouter-cli collects anonymous usage data. Opt out: ANYROUTER_CLI_TELEMETRY_DISABLED=1 or DO_NOT_TRACK=1. Details: https://telemetry.anyr.workers.dev/anyrouter-cli`

### 5. Add a test

Add a test that proves nothing is sent when `ANYROUTER_CLI_TELEMETRY_DISABLED=1`, when `DO_NOT_TRACK=1`, and when `CI` is set (and that the CI opt-in sends `ci:true`). Also prove:

- the debug mode prints and does not send, and writes no id file or stamp;
- nothing is sent when the id or stamp cannot be written;
- a second due check on the same UTC day sends no second `ping`.

Mock or stub the network call and assert it is not made. Use the repo's existing test framework.

### 6. Add a "Telemetry" section to the README

Use this template and adjust it to what the code really sends. Use the tool's real env prefix. Keep the config-file opt-out only if the tool has one. Say where the notice appears (stderr, the log, or the UI).

```markdown
## Telemetry

anyrouter-cli sends anonymous usage data so the maintainers know which versions and platforms are in use.

What is sent: event name (for example `install`, `ping`, `run`), a random install id, app version, OS, CPU architecture, whether it ran in CI, and the country (derived by Cloudflare from the request). An `install` event is sent once on first run, then at most one `ping` per day.

What is not sent: IP address, user agent, file paths, hostnames, usernames, emails, command arguments, or any file content.

On first run, anyrouter-cli prints a one-line notice to <stderr | the log | the UI> that says this.

Opt out: set `ANYROUTER_CLI_TELEMETRY_DISABLED=1` or `DO_NOT_TRACK=1`, or <the config-file setting, if the tool has a config file>. Set `ANYROUTER_CLI_TELEMETRY_DEBUG=1` to print what would be sent, without sending it.

Public aggregate stats: https://telemetry.anyr.workers.dev/anyrouter-cli
```

### 7. Verify

Required, always possible: a dry run.

1. Run the tests.
2. Run a normal command with `ANYROUTER_CLI_TELEMETRY_DEBUG=1`. Pick a command that reaches the startup hook, not `--version` or `--help`. Check the printed payload matches the table above and has no personal data. Check that no id file and no stamp were created.

Optional, only when network use is allowed: one real ping.

3. Remove `DEBUG`, unset `CI`, run the app once, or run:
   `curl -X POST https://telemetry.anyr.workers.dev/anyrouter-cli -H 'content-type: application/json' -d '{"event":"ping","version":"0.0.0"}'`
4. Check https://telemetry.anyr.workers.dev/anyrouter-cli or `curl https://telemetry.anyr.workers.dev/api/anyrouter-cli/stats`. The `summary.today.users` value updates within about 5 minutes.

## Do not

- Do not send PII: no paths, hostnames, emails, usernames, or argument values. Send flag names or subcommand names only, from a fixed list.
- Do not block the main path. Send in the background with a timeout of 1 second or less.
- Do not retry in a loop. One attempt, ignore the result.
- Do not crash on errors. Wrap the whole send in a catch-all.
- Do not send on every command. At most one `ping` per UTC day.
- Do not send when the id or the stamp cannot be saved. Fail closed.
- Do not write files, show the notice or send in debug mode.
- Do not derive the id from hardware, username or paths. It is random and stored in the config directory.
- Do not enable telemetry in CI by default.
