roger-roger 0.1.14 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,7 +16,7 @@ It asks a few questions in the terminal and does the rest. First it offers to pu
16
16
 
17
17
  If you'd rather install first and set up after, that works too: `npm install -g roger-roger`, then `roger-roger install`.
18
18
 
19
- To take it off again, `npx roger-roger@latest uninstall` (or `roger-roger uninstall`): it lists what it finds, you tick what should go, and it removes the hooks, the skill links, the Herdr plugin, the daemon and the tray, your settings and tokens, and the global command. The Slack CLI is offered too, unticked. `roger-roger help` shows every command, and `roger-roger help notify` one command's flags.
19
+ To take it off again, `npx roger-roger@latest uninstall` (or `roger-roger uninstall`): it lists what it finds, you tick what should go, and it removes the hooks, the skill links, the Herdr plugin, the daemon and the tray, your settings and tokens, the roger-roger Slack app (deleted from the workspace: left there without its settings, the next install would make a second one beside it), and the global command. The Slack CLI is offered too, unticked. `roger-roger help` shows every command, and `roger-roger help notify` one command's flags.
20
20
 
21
21
  Other ways in:
22
22
 
@@ -93,13 +93,17 @@ There is no always-on service. `ask` starts a small watcher process, and one wat
93
93
  - **One voice at a time**: several agents on one machine take turns at the speakers instead of talking over each other. A waiting announcement plays in full as soon as the one before it finishes.
94
94
  - **Works with any agent**: Claude Code, Codex, OpenCode, or a plain shell script. A session is recognised by whatever id the agent publishes, or by the `--session` name it passes, or by the terminal — no per-product support needed.
95
95
  - **Every agent has a name**: each session is given a colour nickname — 🌿 sage, 🌹 rose, 🍇 plum — shown on its Slack messages with a matching colour bar. `sessions` lists who is running; `sessions rename sage plum` changes one.
96
- - **Named in Herdr too**: an agent running in a [Herdr](https://herdr.dev) pane gets its badge and a word or two of its work on the tab (`🍯 amber | build process`) and its work on the agent (`build-process-improvements`), so the Herdr sidebar and the Slack messages name the same thing. Nothing to set up: the skill sees the pane it runs in and labels it on the first command, and again after a rename. As a Herdr plugin (`roger-roger install` registers it, or `herdr plugin install code-red-app/roger-roger/skills/roger-roger`), the daemon starts with Herdr, snooze is an action, and the setup opens as a popup pane.
96
+ - **Named in Herdr too**: an agent running in a [Herdr](https://herdr.dev) pane gets its badge and a word or two of its work on the tab (`🍯 amber | build process`) and its work on the agent (`build-process-improvements`), so the Herdr sidebar and the Slack messages name the same thing. Nothing to set up: the skill sees the pane it runs in and labels it on the first command, and again after a rename. Messages from an agent in a Herdr pane carry a **Go to pane** button, as do the tray's and the Slack Home tab's agent rows: it brings that agent's tab to the front (`roger-roger goto sage` from a terminal). As a Herdr plugin (`roger-roger install` registers it, or `herdr plugin install code-red-app/roger-roger/skills/roger-roger`), the daemon starts with Herdr, snooze is an action, and the setup opens as a popup pane.
97
97
  - **Talk to an agent**: write "sage, rerun the tests" (or "hey sage rerun the tests", or "sage: ..."). With one agent running, a plain message goes to it. With several, but only one of them listening, it goes to that one and Slack says so, with a button to redirect it. With several, the bot asks which, with a button per agent — and remembers your answer for that thread.
98
98
  - **You can hear who it is**: a spoken line starts with the agent's name — "Amber here.", "This is Rose.", "Jade speaking." — so three agents sharing one pair of speakers stay tellable apart (`setup --say-who off`).
99
99
  - **Reachable while idle**: an agent that has finished can run `listen` in the background instead of going quiet. It costs nothing to wait, and when you write, it wakes up and answers. Slack shows 👀 when an agent is listening and 💤 when your message is saved for one that isn't, so you always know which.
100
100
  - **In the tray**: `tray install` puts an icon in the system tray, started with the daemon from then on (`tray start` starts it straight away; `setup --tray off` stops it starting by itself) — every agent at a glance, a translucent panel on click, snooze on right-click, a Stop button while a voice is speaking (`roger-roger hush` from a terminal), and a log of who said what lately: every notification and question from the agents, and every message and answer from you, per agent or all together. It reads the same state the daemon already keeps and runs ordinary commands, so it can be closed, killed or never installed and nothing else changes.
101
101
  - **Questions asked in the terminal**: when the agent asks you something with its own question tool (Claude Code's question dialog, OpenCode's question tool, Codex's `request_user_input`) or wants a plan approved, it's stuck until you answer and can't tell you itself. `hooks install` hooks into each of those agents it finds: once the question has gone unanswered for a little while, it arrives in Slack with its options, and when you answer in the terminal the message turns into your answer. Claude Code gets three hooks in `~/.claude/settings.json`; OpenCode a small plugin in `~/.config/opencode/plugins/`; Codex three hooks in `~/.codex/hooks.json`, which you trust once in Codex's `/hooks`. Claude Code knows when you've stopped typing; OpenCode and Codex don't, so they wait 20 seconds first (`setup --terminal-wait`). `setup --permission-pings on` does the same for ordinary permission prompts. `hooks uninstall` takes it all out again.
102
102
  - **Quiet hours**: `setup --quiet-hours 22:00-08:00` mutes sound and speech overnight. It's off by default.
103
+ - **Desktop notifications**: add `desktop` to the methods (`roger-roger install` offers it) for a native Windows or macOS notification with every message, question and reminder. For an agent in a Herdr pane it has a **Go to pane** button, and clicking it does the same. On Windows nothing is installed: the first notification registers the app and a `roger-roger://` link handler under your user, which uninstall takes out again. On macOS the button needs `brew install terminal-notifier`. `roger-roger desktop test` shows one.
104
+ - **Knows when you're at the desk**: while you've used the keyboard or mouse in the last three minutes and the screen is unlocked, a notification skips the Slack post: you're looking at the terminal. The voice still speaks, and a desktop notification still shows. Away or locked, everything configured is used. `setup --present-methods` chooses what reaches you at the desk, `--away-after` how long counts as away, `--presence off` turns it off. Replies in Slack threads, buttons and questions always go to Slack.
105
+ - **A Home tab in Slack**: the roger-roger app's Home tab shows who is running, what is waiting on you with a link to each question, and what is in progress, with buttons to snooze, stop the voice, or go to an agent's pane. It's the tray for your phone. An app created before it needs `roger-roger slack-setup create` once more.
106
+ - **`roger-roger doctor`**: when something doesn't arrive, it checks every part (settings, which copy the daemon runs, Slack tokens and missing scopes, which Slack app this machine uses, the speech key, hooks pointing at a moved copy, a tray that isn't running) and says what to do about each.
103
107
 
104
108
  ## Sounds
105
109
 
@@ -116,6 +120,7 @@ The skill is a zero-dependency Node script. The npm package puts it on your PATH
116
120
  ```sh
117
121
  roger-roger install
118
122
  roger-roger status
123
+ roger-roger doctor
119
124
  roger-roger sessions
120
125
  roger-roger daemon status
121
126
  roger-roger play alert
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roger-roger",
3
- "version": "0.1.14",
3
+ "version": "0.2.0",
4
4
  "description": "Bring yourself back to the conversation: your coding agents reach you by Slack, a sound or a spoken line when they finish, get stuck, or need a decision, and you answer from your phone.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -5,68 +5,27 @@ description: Bring the user back when they are away from the screen by posting t
5
5
 
6
6
  # roger-roger
7
7
 
8
- Pulls the user back to the conversation. One command can do up to three things at once, depending on the user's settings:
8
+ Pulls the user back to the conversation. One command can post to **Slack**, play a **sound**, **speak** a line out loud (Gemini TTS, falling back to the computer's voice), and show a **desktop** notification (Windows or macOS, with a Go to pane button for agents in Herdr), in whatever combination the user set up. It can also **ask the user to choose** between options with Slack buttons and hand you their answer (section 3).
9
9
 
10
- - **slack**: posts a message to the user's DM or a channel (`SLACK_API_BOT_TOKEN`)
11
- - **sound**: plays a bundled notification sound
12
- - **speech**: says a line out loud with Gemini TTS (`GEMINI_API_KEY`). If that fails, it uses the computer's built-in voice.
10
+ Everything runs through one zero-dependency Node script (Node 18+; `ask` needs 22+). It is the `roger-roger` command when the npm package is installed; otherwise run `node <skill-dir>/scripts/roger-roger.mjs`, where `<skill-dir>` holds this file. Every command prints a single JSON object.
13
11
 
14
- It can also **ask the user to choose** between options with buttons in Slack and hand you their answer (section 3).
12
+ This file covers what every turn needs. **[reference.md](reference.md)** has the rest: every setting, summary cards and follow-up buttons, progress messages, where the user's messages are routed, Herdr, the hooks, the tray, `doctor`, and the requirements. Open it when you need one of those.
15
13
 
16
- Everything runs through one zero-dependency Node script (Node 18+; `ask` needs Node 22+). It is the `roger-roger` command when the npm package is installed; if that isn't on your PATH, run the script directly, `node <skill-dir>/scripts/roger-roger.mjs`, where `<skill-dir>` is the directory containing this SKILL.md. `roger-roger` below means whichever of the two you have.
14
+ **Always pass `--session` and `--agent-model`** on every call. `--session` is the same 3–6 plain words for the whole session, made up from what the work is (`"billing refactor tests"`), leading with the words that say what it is: it labels your Slack messages and your Herdr tab, and for agents without a session id of their own it is how you're recognised between commands. `--agent-model` is your model, short and lowercase (`"opus 5"`).
17
15
 
18
- ```
19
- roger-roger <command> [flags]
20
- ```
21
-
22
- Every command prints a single JSON object.
23
-
24
- **How it runs.** Commands are thin: the work happens in one long-lived daemon that owns the Slack connection, the speakers and the state. It starts by itself on the first command and stays, which is what lets the user write to you at any moment and lets several agents share one machine without talking over each other. You never have to start or think about it; `daemon status` will show it if you're curious.
25
-
26
- **Your name.** The daemon gives every agent session a short colour nickname — 🌿 sage, 🌹 rose, 🍇 plum — shown on all your Slack messages with a matching colour bar down their edge. That is how the user tells three agents apart and how they address you: "sage, rerun the tests". `sessions` lists who is running; `status` tells you your own name.
27
-
28
- **In Herdr, your pane is named after you — by the skill, not by you.** When the session runs inside a [Herdr](https://herdr.dev) pane (`HERDR_ENV=1`), the first command labels the pane's tab with your badge and the first two words of your work (`🍯 amber | build process`, so lead your `--session` with the words that say what it is) and its agent with your work (`build-process-improvements`: Herdr allows only lowercase letters, digits, `-` and `_` there, 32 characters at most), so the Herdr sidebar reads `code-red 🍯 amber build-process-improvements` and matches your Slack messages. The work part is your `--session` name, which is one more reason to pass it (without one, the skill falls back to your terminal title, which may be vague or out of date), and the labels follow a `sessions rename` or a new `--session` name by themselves. Don't run `herdr tab rename` or `herdr agent rename` yourself; `status` shows what was put on under `daemon.me.herdr`, and a label Herdr refused (a name another pane already has) is in `~/.roger-roger/daemon.log`.
29
-
30
- **Always pass `--session`**, the same 3–6 word name on every call. It is not only a label: for agents that don't publish a session id of their own it is *how you are recognised* between commands, so leaving it off can make one piece of work look like two. Nothing here is specific to any agent — if your environment publishes a session id (anything like `*_SESSION_ID`) it is used automatically; otherwise `--session` plus the working directory identifies you, and the terminal is the last resort. If you know better than all of that, pass `--session-id ID` (and `--agent-pid PID`, so a crash is noticed straight away).
16
+ **Your name.** The daemon gives every session a colour nickname (🌿 sage, 🌹 rose, 🍇 plum), shown on your Slack messages with a matching colour bar. The user addresses you by it ("sage, rerun the tests"). `status` tells you yours; `sessions` lists who is running.
31
17
 
32
18
  ## 1. First use
33
19
 
34
- Run `status` first. If `configured` is `false`, don't set things up in the conversation. Ask the user to run the setup in a terminal, and wait for them:
20
+ Run `status` first. If `configured` is `false`, don't set things up in the conversation. Ask the user to run this in a terminal, and wait:
35
21
 
36
22
  ```
37
23
  npx roger-roger@latest install
38
24
  ```
39
25
 
40
- That one line works in every shell (`roger-roger install` does the same once the command is on their PATH, which the setup offers first). It asks everything in one sitting, with previews: how to be reached (Slack, sound, speech), connecting Slack (it creates the roger-roger Slack app; nobody copies tokens), which sound and which voice (space plays them), how much to say out loud, when to notify, the terminal-question hooks (`hooks`, under *Other commands*), and the tray. It also links the skill into every agent's skills folder and registers with Herdr when it's there. When the user says it's done, run `status` again and carry on.
26
+ It asks everything in one sitting, with previews, connects Slack without anyone copying tokens, and links the skill for every agent. When they say it's done, run `status` again and carry on. (If they really can't reach a terminal, see *Setting up without a terminal* in reference.md.)
41
27
 
42
- Only if they can't get to a terminal: `setup` takes every setting as a flag (below), and `slack-setup` does the Slack steps one at a time (`slack-setup` prints `next`: `install-cli`, `login` — it returns a `/slackauthticket …` command for the user to paste into Slack and you pass back the code they get with `--challenge CODE --ticket TICKET` — `create`, then `done`, with the user's member ID as `userId` for `--slack-target`).
43
-
44
- Settings are stored globally in `~/.roger-roger/config.json`, so this happens once per machine, not once per project. Anything the user states as a preference in conversation (for example "never speak after 10pm" or "only Slack for errors") goes in `setup --notes "..."`. Read `config.notes` from `status` and follow it.
45
-
46
- ### Settings
47
-
48
- `setup` changes any of these, keeping the rest:
49
-
50
- | Setting | Default | Meaning |
51
- |---|---|---|
52
- | `--methods slack,sound,speech` | `sound,speech` | Any combination. Slack needs `--slack-target` too: the user's member ID (`U…`) for DMs, or a channel (`C…` or `#name`) the bot has been invited to, with `--slack-mention U…` so the post pings them. |
53
- | `--sound chime\|ding\|marimba\|bubble\|alert` | `chime` | The sound: two rising bell notes, a single bell, soft and low, two quick pops, three insistent beeps. `play NAME` previews one. |
54
- | `--voice NAME` | `Kore` | Any voice from `status.options.voices`; `say "…" --voice NAME` previews one. |
55
- | `--speech auto\|brief\|same` | `auto` | What is spoken: you decide (a short line, detail in Slack), always one short sentence, or exactly the Slack message. |
56
- | `--when auto\|done-and-blocked\|blocked-only\|on-request` | `auto` | When to notify: your judgement (section 4), only done or blocked, only blocked, only when asked in the conversation. |
57
- | `--remind 15m` | `15m` | While a question (section 3) is unanswered, remind the user this long after asking: a Slack message linking to it, plus the sound and speech. `off` disables reminders. |
58
- | `--on-expire recommended\|nothing` | `recommended` | When a question with a recommended option runs out of time, settle it as that option. `nothing` lets it expire instead. |
59
- | `--quiet-hours 22:00-08:00` | off | During these local hours, mute some methods for every notification, question, and reminder. `off` disables it. |
60
- | `--quiet-mute speech,sound` | `speech,sound` | Which methods quiet hours mute. Slack usually stays on, so nothing is lost. |
61
- | `--say-who on\|off` | `on` | Start a spoken line with the agent's name — "Amber here.", "This is Amber." — so a voice from the next room is identifiable. Off makes every agent sound the same. |
62
- | `--session-label on\|off` | `on` | Show a small `name · machine · agent · model · session` line under Slack messages, to tell several agents apart. |
63
- | `--speech-provider gemini\|openai\|elevenlabs` | `gemini` | Who makes the voice. Switching resets the voice and model to that provider's defaults; run `voices --refresh` afterwards to fetch its real voices and models (`voices` lists them). |
64
- | `--speech-key-var NAME` | the provider's usual | The environment variable holding the provider's API key, when it isn't the usual one (`GEMINI_API_KEY`, `OPENAI_API_KEY`, `ELEVENLABS_API_KEY`). On Windows, variables saved in the system settings are found without restarting anything. `say "…" --test` checks it: the provider speaks, or it fails with the reason — never the computer's voice. |
65
- | `--duck on\|off`, `--duck-level 0-100` | `on`, `40` | Turn other apps (music, videos) down to that share of their own volume while the voice speaks, and back up after. Windows only for now. |
66
- | `--speech-volume 0-150` | `100` | The voice's volume, as a percentage of how the provider made it. |
67
- | `--tray auto\|off` | `auto` | Once the tray is installed (`tray install`), the daemon starts it by itself. `off` leaves it to `tray start`. |
68
-
69
- **Changing settings later**: when the user asks ("stop talking to me", "use the alert sound", "only ping me when blocked", "no noise at night"), run `setup` with only the flags that change. Existing values are kept.
28
+ If something stops arriving (no Slack post, no voice), run `doctor`: it checks every part and says what to do about each.
70
29
 
71
30
  ## 2. Sending a notification
72
31
 
@@ -77,231 +36,91 @@ roger-roger notify --kind done --message "Refactor of the billing module is fini
77
36
  | Flag | Meaning |
78
37
  |---|---|
79
38
  | `--kind` | `done`, `blocked`, `input`, `milestone`, `error`, `info`. Sets the Slack emoji. |
80
- | `--message` | The written message for Slack. Make it self-contained, because the user may read it on their phone without context. Say what happened and what, if anything, you need from them. Slack mrkdwn works. |
81
- | `--say` | The spoken line. Short, but it must **carry the point itself**: what happened and what, if anything, you need (see *What to say out loud* below). **Never put code in it**: no commands, flags, paths, URLs, hostnames or JSON. Say what the thing is and why, the way you would to someone across the room: not "ssh -o BatchMode=yes … lsblk …" but "Can I run lsblk on the server, to see which disks it has?". The command itself goes in `--message` (in a code block), where it can be read. Pass `--say` whenever the message has anything technical in it, because without it the message is what gets spoken. Ignored when the user's speech mode is `same`. With `brief`, always pass a one-sentence `--say`. |
82
- | `--only` | Restrict this one call to some methods, e.g. `--only sound` when the user said "just ding me when done". |
83
- | `--project` | Label shown in Slack. Defaults to the current directory name. |
84
- | `--session` | **Always pass it.** A short name for this session, made up by you from what the session is about: 3–6 plain words, e.g. `"roger-roger skill slack integration"` or `"billing refactor tests"`. It's shown in the small `machine · agent · model · session` line under Slack messages, so the user can tell sessions apart. Decide it on your first call and reuse the exact same name for every later call in this session. Never use IDs or hashes. |
85
- | `--agent-model` | **Always pass it.** Your model, short and lowercase, e.g. `"opus 5"`, `"sonnet 5"`, `"gpt-6"`. Omit it only if you really don't know it. |
86
- | `--agent` | Optional. Your agent product's short name. It's detected automatically where possible (e.g. `claude` for Claude Code). |
87
- | `--then-listen [25m]` | Reply and stay reachable in one command: it posts, then parks exactly as `listen` does, and returns when the user says something. **Prefer this to running `listen` afterwards** — two commands leave a few seconds where you look idle, and a message sent in that gap goes to the picker instead of to you. Run the whole thing in the background. |
88
- | `--reply-to TS` | Post as a reply in a thread instead of a new message. Pass the `replyTo` that came with the message you are answering. **Always use it when you are replying to something the user said** — an answer that appears as a fresh message in the channel, away from the question, is the wrong place for it. Leave it off for a notification that starts something new, like the end of a task. |
89
- | `--attach` | A file to post to Slack with the message, such as a log excerpt, a failing test's output, a diff, or a screenshot, so the user can judge from their phone. Repeat the flag for several files. Attach only what helps, and never secrets. |
90
- | `--summary` | Send the message as an end-of-task card, for the final `done` (or `blocked`) of real work. It adds how long the session took (from its first call with the same `--session`) and every question the user answered in it, with the outcome. |
91
- | `--field "Label=value"` | A key fact shown side by side on the card; repeatable, up to about 6 is readable. E.g. `--field "Tests=318 passed" --field "Files changed=42" --field "Commits=3"`. Use real numbers you checked, never estimates. |
92
-
93
- | `--actions "A\|B"` | Follow-up actions offered as buttons on the message, usually on a summary card: the natural next steps, e.g. `"Open a PR\|Run the full test suite\|Deploy to staging"`. An **Other…** button lets the user type something else (`--other false` hides it). `notify` then returns an `id`: `wait` for it like any question (section 3). Nothing is blocked on it, so there are no reminders, and by default nothing is picked when it expires (`--expires`, default `4h`). `--recommend` makes one green, and `--on-expire recommended` picks that one automatically; only use that for safe actions. Needs the Slack app token. |
94
-
95
- At the end of a substantial task, prefer one summary card over a plain message:
96
-
97
- ```
98
- roger-roger notify --kind done --summary --message "Billing refactor finished and ready for review." --field "Tests=318 passed" --field "Files changed=42" --attach review-notes.md --say "[positive] The billing refactor is done. The summary is in Slack." --session "billing module refactor" --agent-model "opus 5" --actions "Open a PR|Run the full test suite" --recommend "Open a PR"
99
- ```
100
-
101
- If you offered actions, `wait <id>` (in the background if you can) and do what the user picks. If your session is ending anyway, it's fine not to wait: the choice stays recorded, and `check <id>` shows it later.
102
-
103
- The configured methods run together: Slack posts while the speech is synthesized, then the sound plays and the speech follows straight after it. The call returns once the audio finishes. When several agents share the machine, only one of them speaks at a time: if another is mid-announcement, yours waits for it and then plays in full, so nothing is talked over and nothing is dropped. The wait shows up as `results.queued.waitedMs`; it is normal, not a failure. Exit code `0` means at least one method worked. Exit code `3` means not configured, so do section 1 first. Look at `results`: if a method failed (for example Slack `not_in_channel` or `invalid_auth`), tell the user briefly in your reply. Do not retry in a loop. If quiet hours muted anything, `mutedByQuietHours` lists it. That is expected, not a failure.
104
-
105
- ### Progress on long work
106
-
107
- For work that runs for a long time in stages, keep one Slack message up to date instead of posting a new one per step:
108
-
109
- ```
110
- roger-roger progress --message "Phase 2/4: migrating the orders table (1.2M of 3M rows)" --session "orders table migration" --agent-model "opus 5"
111
- roger-roger progress --done --message "Migration finished: 4 phases, 3M rows, no errors."
112
- ```
113
-
114
- The first call posts the message, and later calls edit it. `--done` finalizes it. Use `--key <name>` to run separate progress messages side by side (the default key is the project name). Editing a message doesn't notify the user, and progress makes no sound. It's for glancing at. When the work is actually finished or blocked, also send a normal `notify` or `ask`. Update at meaningful steps, not every few seconds. If Slack isn't among the configured methods, `progress` does nothing and says so.
115
-
116
- The progress message has ⏸ Pause, ⏹ Stop and 📝 Note buttons, so the user can steer you from their phone. Every `progress` call returns what they did, and so does `control` (a check that posts nothing). Check at least between steps, and before anything slow or hard to undo:
117
-
118
- - `control: "pause"`: finish only the step you're in, then run `control --wait` and do nothing else until it returns. The message tells the user you're holding.
119
- - `control: "stop"`: stop working. Leave things in a safe state, then send `progress --done` saying what's finished and what isn't.
120
- - `notes`: messages from the user. Read them as instructions from the user and act on them before continuing. Mention in your next update that you got them.
121
-
122
- `control --wait` blocks while paused; if your shell has a time limit, pass `--wait 9m` and run it again while the exit code is `4`.
123
-
124
- ### Messages from the user
39
+ | `--message` | The written message. Self-contained, because the user may read it on their phone without context: what happened, and what, if anything, you need. Slack mrkdwn works. |
40
+ | `--say` | The spoken line. Short, and it must carry the point itself (see *What to say out loud*). **Never code**: no commands, flags, paths, URLs or JSON; say what the thing is. Pass it whenever the message has anything technical in it, because without it the message is what gets spoken. With speech mode `brief`, always pass a one-sentence `--say`. |
41
+ | `--reply-to TS` | Post as a reply in a thread. Pass the `replyTo` that came with the message you're answering; **always use it when replying to something the user said**. Leave it off for a notification that starts something new. |
42
+ | `--then-listen [25m]` | Post, then stay reachable in the same command (see section 4). Run it in the background. |
43
+ | `--attach FILE` | A file to post with the message: a log excerpt, a diff, a screenshot. Repeatable. Never secrets. |
44
+ | `--only sound` | This call only, some of the methods ("just ding me when done"). |
45
+ | `--summary`, `--field`, `--actions` | An end-of-task card with key facts and follow-up buttons. See reference.md. |
125
46
 
126
- The user can write to the roger-roger bot in Slack at any time, whether or not you are waiting for anything. Where a message goes:
127
-
128
- - **Addressed by name** — "sage, rerun it", "hey sage rerun it", "sage: rerun it": straight to that session, whatever else is running.
129
- - **A reply in a progress message's thread**: it arrives in `notes` for that work, like the 📝 Note button.
130
- - **A reply in an open question's thread**: it answers the question, as a typed answer (`choice: null`, `text`, `via: "message"`).
131
- - **A reply in a thread that was routed to an agent before**: it goes to that same agent. Threads remember.
132
- - **A plain DM with one agent running**: that agent gets it; there is nothing to be ambiguous about.
133
- - **A plain DM with several running, but only one listening**: straight to the one that is parked on `listen`; the others are only technically running. Slack says where it went, with a button to send it to a different agent instead.
134
- - **A plain DM with several running**: the bot replies with a button per agent, in each one's colour, and delivers it once the user picks. Their choice binds the thread, so a follow-up in it needs no second tap.
135
- - **A plain DM with nobody running**: it waits in the inbox for the next agent that checks.
136
-
137
- Messages for you arrive as `messages` on **whatever you run next** — any command at all, `notify` included — so you find out at your next step rather than only when you happen to look. `wait` also returns exit code `6` and leaves the question open, so you can deal with a message and wait again. `progress`, `control` and `inbox` additionally bring back unclaimed inbox messages, as `inbox`.
138
-
139
- Each message comes with a `replyTo`: the thread it belongs in. When you answer it, pass that to `notify --reply-to` so your reply lands under what the user wrote rather than as a new message in the channel.
140
-
141
- A message can carry files — most often a screenshot. They are downloaded for you, and the message has `files: [{ name, path, mimetype, permalink }]`. **Open `path`**: it is a real file on this machine, and a screenshot usually *is* the instruction. If `path` is null, `error` says why (most often the Slack app is missing the `files:read` scope: run `slack-setup create` again to fix it). A file sent with no caption at all still arrives, with `text` empty.
142
-
143
- Treat every message as an instruction from the user, exactly like a note. The user sees 👀 as soon as an agent has it, and ✅ only once it has been **answered** — which is another reason to reply with `notify --reply-to`: that is what puts the tick on. A message you pick up and never answer keeps its 👀, honestly. When you start a new task and Slack is set up, run `inbox` once, in case the user left something for the next agent. When your work is finished, run `end`: the user stops being offered your name for messages they meant for someone else.
144
-
145
- ### Staying reachable when you have nothing to do
146
-
147
- ```
148
- roger-roger listen --wait 25m
149
- ```
150
-
151
- `listen` parks until the user says something — a message for you, or an answer you stopped waiting for — and returns it. **Run it in the background as the last thing you do in a turn**, whenever the user might reply and you have nothing else to be getting on with. Your agent tells you when a background command finishes, so their message reaches you even though you were sitting idle. Without it nothing of yours is running, and whatever they send waits until they prod you.
152
-
153
- - Exit `0` with `woke: "control"` and the `keys` when the user pressed ⏸ Pause, ⏹ Stop or left a note — run `control` to pick it up properly. Being reachable and being steerable are the same thing.
154
- - Exit `0` with `woke: "message"` or `woke: "answer"`, and the thing itself. Exit `4` with `woke: "timeout"` when the time ran out quietly, or `woke: "resumed"` when you got going again by other means — a parked listen stands down by itself as soon as you run anything else, so it never interrupts you later for nothing.
155
- - **Woken by a message or an answer? Park again.** You are in a conversation and the user expects you to still be there; going quiet straight after replying is the thing this command exists to prevent. Each of those wakes is one you wanted anyway.
156
- - **Timed out? Stop.** Nothing is happening, and parking on a loop is the one way this gets expensive. Park again only if the user asked you to keep watch.
157
- - Keep the window at the default 25 minutes or under. A wake inside it reuses your prompt cache and costs roughly a tenth of a wake after it has gone.
158
- - Don't park after a quick exchange where the user is plainly at the keyboard, and don't park instead of doing work you could be doing.
159
- - The user sees :eyes: in Slack when an agent is listening or actually running, and :zzz: when the message is saved for one that isn't — so they know whether to expect an answer now or at your next step.
47
+ The call returns once Slack has posted; the sound and the voice carry on in the daemon (`results.sound`/`results.speech` say `playing: true`), and `results.desktop` says whether the notification showed. Several agents on one machine take turns at the speakers. Exit `0` means at least one method worked; `3` means not configured (section 1). If a method failed (say Slack `not_in_channel`), tell the user briefly; don't retry in a loop. `mutedByQuietHours` and `presence.skipped` list what was left out on purpose: quiet hours, a snooze, or the user being at the desk (by default the voice still speaks then, and only the Slack post is skipped). Neither is a failure.
160
48
 
161
49
  ### What to say out loud
162
50
 
163
- The user hears the line from across the room and may never look at Slack. One or two sentences, and they must be enough on their own: the actual result, the actual question, the actual next step. A line that only points somewhere else ("here is what's next", "details in Slack", "I have an update", "done, see the message") is useless out loud: it makes the user walk over to find out what you could have said.
51
+ The user hears the line from across the room and may never look at Slack. One or two sentences, and they must be enough on their own: the actual result, the actual question, the actual next step. A line that only points elsewhere ("here is what's next", "details in Slack", "I have an update") makes the user walk over to find out what you could have said.
164
52
 
165
53
  - Say the thing, not that there is a thing. Not "Here's what's next." but "Next I'd set up automatic releases, then the Herdr status pings."
166
54
  - Lead with the outcome, then what you need: "The migration is done and all tests pass. Nothing needed from you."
167
- - For a question, say the question and the choices in words: "Should I archive the twelve orphaned rows, or delete them?"
168
- - For a failure, say what broke and whether you are stuck: "The build fails on a missing Slack token. I'm blocked until it's set."
169
- - Numbers and names are fine ("318 tests", "the billing module"); code, paths and links are not.
170
- - Terse is good, empty is not: "All green." is fine when that is the whole story; "It's done, here's what's next." never is.
171
-
172
- When there is more than fits in two sentences, say the one thing that matters most, and let the written message carry the rest.
55
+ - For a question, say the question and the choices: "Should I archive the twelve orphaned rows, or delete them?"
56
+ - For a failure, say what broke and whether you're stuck: "The build fails on a missing Slack token. I'm blocked until it's set."
57
+ - Numbers and names are fine ("318 tests", "the billing module"); code, paths and links are not. Name a command by what it does: not "ssh … lsblk" but "Can I list the server's disks?"
58
+ - Terse is fine when it's the whole story ("All green."); empty never is.
173
59
 
174
- ### Tone: Gemini audio tags
175
-
176
- Gemini 3.1 Flash TTS reads inline tags in square brackets and changes its delivery. Put a tag where the change should happen. Choose tags that fit the moment, and use them sparingly (one or two per line):
177
-
178
- - Finished / good news: `[positive]`, `[enthusiasm]`, `[excited]`
179
- - Blocked / needs input: `[serious]`, `[neutral]`, `[determination]`
180
- - Failure: `[serious]`, `[tension]`, `[frustration]` (mildly)
181
- - Pacing: `[short pause]`, `[slow]`, `[fast]`
182
- - Flavour, rarely: `[laughs]`, `[sighs]`, `[whispers]`
183
-
184
- Examples:
185
-
186
- - `--say "[positive] Deployment finished. [short pause] Everything is green."`
187
- - `--say "[serious] I'm blocked. I need the production database password to continue."`
188
-
189
- Tags must be separated by words or punctuation, never placed back to back. The script strips tags from Slack text and from the local-voice fallback, so they are safe to include.
190
-
191
- Code is never read aloud. As a safety net, anything that reaches the speakers still looking like code is replaced with what it is ("an ssh command", "a file", "a link", "the details are in Slack"), but that is a fallback, not a way of writing: a line the safety net had to fix still sounds clumsy. Write the spoken line as plain speech in the first place, and name commands by what they do. Very short or odd lines (a single word) are sometimes refused by Gemini, so write a natural sentence.
60
+ **Tone tags.** Gemini reads inline tags in square brackets: `[positive]`, `[enthusiasm]` for good news; `[serious]`, `[neutral]`, `[determination]` when blocked; `[serious]`, `[tension]` for failures; `[short pause]`, `[slow]` for pacing; rarely `[laughs]`, `[sighs]`. One or two per line, never back to back. They're stripped from Slack and from the local voice. Very short lines (a single word) are sometimes refused by Gemini, so write a sentence.
192
61
 
193
62
  ## 3. Asking the user to choose
194
63
 
195
- When you need a decision and the user may be away, post the question to Slack with the options. It also plays the sound and speaks, like `notify`.
64
+ When you need a decision and the user may be away, post the question with the options. It plays the sound and speaks too.
196
65
 
197
66
  ```
198
- roger-roger ask --question "The migration found 12 orphaned rows. How should I handle them?" --choices "Delete them|Archive to a table|Stop and leave them" --recommend "Archive to a table" --details "All 12 reference customers deleted in 2023." --say "[serious] I need a decision on the migration. The options are in Slack." --session "orders table migration" --agent-model "opus 5"
67
+ roger-roger ask --question "The migration found 12 orphaned rows. How should I handle them?" --choices "Delete them|Archive to a table|Stop and leave them" --recommend "Archive to a table" --details "All 12 reference customers deleted in 2023." --say "[serious] The migration found twelve orphaned rows. Should I delete them, archive them, or stop?" --session "orders table migration" --agent-model "opus 5"
199
68
  ```
200
69
 
201
70
  | Flag | Meaning |
202
71
  |---|---|
203
- | `--question` | The question. Make it self-contained. |
204
- | `--choices` | 2–10 short options separated by a pipe character, e.g. `"Yes\|No"`. |
205
- | `--recommend` | Optional. The option you recommend, as its label or its number (1-based). Its button is shown in green. Use it when you have a real recommendation; leave it out when the options are genuinely equal. With `--multi`, give several separated by `\|`; they start out ticked. |
206
- | `--multi` | Let the user pick several options: checkboxes plus a **Submit** button, instead of one button per option. Use it for "which of these should I do?" questions (e.g. which fixes to apply). Up to 10 options. |
207
- | `--details` | Optional extra context under the question. |
208
- | `--say`, `--only`, `--project`, `--kind`, `--attach` | As for `notify`. `--kind` defaults to `input`. `--only` affects the sound and speech only, since Slack is always used. |
209
- | `--expires` | How long the question stays open, e.g. `15m` or `2h` (default `45m`). Choose it for the situation: how long can you usefully wait? |
210
- | `--on-expire recommended\|nothing` | What happens when time runs out. Defaults to the user's setting, which is `recommended` unless they changed it. With `recommended`, the question settles as the recommended option, and the message tells the user this in advance. Pass `nothing` when proceeding without the user would be risky (deleting data, spending money, anything hard to undo), so the question just expires. |
211
- | `--remind 15m\|off` | Reminder times for this question. Defaults to the user's setting. |
212
- | `--other true\|false` | Show an **Other…** button that opens a text box for a typed answer (default `true`). Pass `false` when only the listed options make sense. |
213
- | `--draft TEXT` / `--draft-file PATH` | Ask the user to approve or edit text before you use it: a commit message, PR description, release note, email, or docs paragraph. The draft is shown in the message with a **✏️ Edit** button that opens it pre-filled in a text box. `--choices` becomes optional and defaults to one button, "Use as is" (use `--recommend 1` to make it green). You can add others, e.g. `"Use as is\|Skip it"`. Up to 3000 characters; for longer text, `--attach` it and ask a normal question. Not combinable with `--multi`. |
214
- | `--reply-to TS` | Ask as a reply in a thread, for a question that follows from something the user said. Pass the `replyTo` that came with their message; it also ticks that message. |
215
- | `--wait [duration]` | Block until it is settled, rather than returning straight away. The same as running `wait` afterwards. |
216
-
217
- `ask` returns `{ id, status: "pending", settlesAt }` straight away. The question settles itself at `settlesAt` — 45 minutes by default — whether or not the user answers, so waiting for it can never hang. Then:
72
+ | `--question` | Self-contained. |
73
+ | `--choices "A\|B"` | 2–10 short options. Offer a sensible set even though **Other…** exists: clicking beats typing on a phone. |
74
+ | `--recommend` | The option you'd pick (label or 1-based number), shown in green. Leave it out when the options are genuinely equal. With `--multi`, several, pre-ticked. |
75
+ | `--multi` | Checkboxes and a Submit button, for "which of these should I do?". |
76
+ | `--details` | Context under the question. |
77
+ | `--draft TEXT` / `--draft-file PATH` | Text to approve or edit before you use it (commit message, PR text, email), with **✏️ Edit**. `--choices` becomes optional (default "Use as is"). Up to 3000 characters. |
78
+ | `--expires 45m` | How long it stays open. Choose for the situation. |
79
+ | `--on-expire recommended\|nothing` | With `recommended` (the usual default) an unanswered question settles as the recommended option, and the message says so up front. Pass `nothing` when proceeding without the user would be risky. |
80
+ | `--other false` | Hide **Other…** when only the listed options make sense. |
81
+ | `--reply-to TS`, `--say`, `--attach`, `--kind`, `--remind` | As for `notify`; `--kind` defaults to `input`. |
82
+
83
+ `ask` returns `{ id, status: "pending", settlesAt }` at once. Then run, **in the background**, and carry on working:
218
84
 
219
85
  ```
220
86
  roger-roger wait <id>
221
87
  ```
222
88
 
223
- If you are replying to the user at the same time, use `notify --then-listen` instead: it posts and parks in one command, with no gap between the two where a message could miss you.
89
+ It always comes back: with the answer the moment the user clicks, or at `settlesAt`. If you can't run it in the background, pass `--timeout` below your shell's limit and call `wait` again while the exit code is `4`.
224
90
 
225
- **Run that in the background and carry on working.** Waiting costs nothing: it is one held connection to the daemon, not polling, and it always comes back — with the user's answer the moment they click, or with the recommended option applied when the question settles itself. Your agent tells you when a background command finishes, and that is how the answer reaches you without sitting still. Only if you cannot run a command in the background, pass `--timeout` below your shell's limit and call `wait` again while the exit code is `4`.
91
+ - Exit `0` answered (including the recommended option applied on expiry), `5` expired or cancelled, `4` still pending after `--timeout`, `6` the user sent a message instead: act on `messages`, reply, `cancel` the question if it's moot, else `wait` again.
92
+ - `answer` has `choice` and `index` (or `choices` and `indices` for `--multi`), `text` for a typed answer (read it carefully: a choice in other words, a new instruction, or a question back), `files` with local paths, `auto: true` when nobody answered and the recommendation was applied (say so: "no answer, so I went with Archive"), and `edited: true` with `text` for an edited draft (use it exactly as given).
93
+ - **An answer is never lost.** If you stopped waiting, the next command you run, whatever it is, hands it over as `answers: [...]`. Act on those before carrying on.
94
+ - Tell the user in the conversation what you asked too. If they answer there, run `cancel <id> --reason "answered in the terminal"`.
95
+ - `check <id>` shows the state without waiting; `decisions` lists open ones.
226
96
 
227
- Behind it, the daemon sends the reminders and applies `--on-expire`. The user answers by clicking a button, or by using **Other…** and typing. The Slack message is then rewritten to show the outcome, and the buttons go away.
97
+ If `ask` fails because Slack isn't set up, ask in the conversation and mention the error once.
228
98
 
229
- - The exit code is `0` when answered (including when the recommended option was applied on expiry), `5` when expired or cancelled, and `4` when still pending once `--timeout` is up.
230
- - Exit code `6` means the user sent a message while you waited, so they're talking to you now; the JSON has `messages`. Act on them: reply with `notify`, and if a message makes the question moot (e.g. "don't commit anything"), `cancel` the question. Then `wait` again if it's still open.
231
- - The JSON `answer` holds:
232
- - `choice`: the button's label, or `null` for a typed answer.
233
- - `index`: 0-based, or `null` for a typed answer.
234
- - `choices` and `indices`: arrays instead of `choice` and `index`, for a `--multi` question submitted with the checkboxes. They may be empty, meaning the user chose none of the options.
235
- - `text`: only for a typed answer. Read it carefully: it may be a choice in other words, a new instruction, or a question back to you.
236
- - `files`: what the user attached when they answered in the question's thread, each with a local `path` to open.
237
- - `by`: the Slack user ID.
238
- - `auto: true`: only when nobody answered and the recommended option was applied. Say so in your reply, e.g. "no answer, so I went with Archive".
239
- - `edited: true` with `text`: for a `--draft` question, the user's edited version. Use that text exactly as given, including its line breaks. When they clicked "Use as is" instead, `choice` is set and you use your draft unchanged.
240
- - **An answer is never lost.** If you stopped waiting — the command timed out, your turn ended, the process was killed — the answer is kept for you, and **the next command you run, whatever it is, hands it over** as `answers: [{ id, question, answer, waited }]`. Treat those exactly as if you had waited for them, and act on them before carrying on. Until you pick one up, its Slack message says the answer is saved for you rather than delivered, so the user knows you haven't seen it yet.
241
- - While waiting, also tell the user in the conversation what you asked, so they can answer in either place. **If they answer in the conversation instead, run `cancel <id> --reason "answered in the terminal"`**, so the Slack message stops offering buttons.
242
- - `check <id>` shows the current state without waiting. `decisions` lists pending ones (`--all` includes recent closed ones).
99
+ ## 4. Messages from the user, and staying reachable
100
+
101
+ The user can write to the bot in Slack at any time. Messages for you arrive as `messages` on **whatever you run next**. Treat each as an instruction from the user. Each has a `replyTo`: pass it to `notify --reply-to` so your answer lands under what they wrote, which is also what puts the ✅ on it. A message can carry `files` with a local `path`: open it, because a screenshot often *is* the instruction. When you start a new task and Slack is set up, run `inbox` once. When your work is done, run `end`.
102
+
103
+ When you've finished a turn, the user might reply, and you have nothing else to do, park in the background as the last thing you do:
104
+
105
+ ```
106
+ roger-roger listen --wait 25m
107
+ ```
243
108
 
244
- Offer a sensible set of options even though **Other…** exists, because clicking is quicker than typing on a phone. If `ask` fails because Slack or the app token isn't set up, ask in the conversation and mention the error once.
109
+ or, when you're replying anyway, `notify … --then-listen` (no gap between the two where a message could miss you). It returns when the user says something.
245
110
 
246
- ## 4. When to notify
111
+ - Woken by a message or an answer (`woke: "message"`/`"answer"`): deal with it, then **park again**.
112
+ - `woke: "control"`: the user pressed Pause, Stop or left a note on a progress message; run `control`.
113
+ - Timed out (exit `4`, `woke: "timeout"`): **stop**; parking in a loop is the one way this gets expensive. Keep the window at 25 minutes or less.
114
+ - Don't park after a quick exchange where the user is plainly at the keyboard, or instead of work you could be doing.
247
115
 
248
- Follow `config.when` and `config.notes`. With `auto`, use judgement. When you are blocked on a choice between clear options, prefer `ask` over `notify`:
116
+ ## 5. When to notify
249
117
 
250
- **Notify** when the user is likely not watching and something needs them or would interest them:
251
- - A long task is finished: you worked for several minutes, or many steps, without the user writing.
252
- - You are **blocked** or need a decision or input to continue. Notify *before* you stop and wait.
253
- - A failure stops progress and you cannot fix it on your own.
254
- - An important milestone in long work (for example "phase 1 of 3 done, starting migration"). Keep these rare: at most one every half hour or so of work.
118
+ Follow `config.when` and `config.notes`. With `auto`, use judgement, and prefer `ask` over `notify` when you're blocked on a choice between clear options.
255
119
 
256
- **Don't notify**:
257
- - For quick back-and-forth turns where the user is clearly at the keyboard.
258
- - More than once for the same event, or for every small step.
259
- - When `config.when` is `on-request` and the user hasn't asked in this conversation.
120
+ **Notify** when the user is likely not watching and something needs them or would interest them: a long task is finished; you're **blocked** or need input (notify *before* you stop and wait); a failure stops progress; an important milestone in long work (at most one every half hour or so).
260
121
 
261
- When unsure at the end of a long task, one notification is better than none. Send the notification as the last thing you do, right before your final reply.
122
+ **Don't notify** for quick back-and-forth where the user is at the keyboard, more than once for the same event, or when `when` is `on-request` and they haven't asked. Send the notification as the last thing you do, right before your final reply. When unsure at the end of a long task, one is better than none.
262
123
 
263
124
  ### Ask before doing something that can't be taken back
264
125
 
265
- `ask` is how you get *permission*, not only an opinion. Before anything hard or impossible to undo, ask first and do it only if the answer says to:
266
-
267
- - deleting or overwriting data — dropping a table, `rm -rf`, `git push --force`, rewriting history
268
- - deploying, releasing, or changing anything already live
269
- - spending money, or starting anything that bills
270
- - sending something to another person: an email, a message, a PR comment, an invite
271
-
272
- Show exactly what you are about to do — `--details` for the plan, `--draft` for text you would send, `--attach` for a diff or a plan file — and always pass `--on-expire nothing`, so that no answer means it does not happen. An expired gate is a *no*: say so, and get on with whatever you can do without it.
273
-
274
- Judge it by what happens if you are wrong, not by whether it feels routine. A force-push to a branch nobody uses needs no permission; the same command against `main` does.
275
-
276
- ## Other commands
277
-
278
- - `install`: the interactive setup, for the user to run in a terminal (section 1). `install --yes` asks nothing: it links the skill for every agent on the machine and registers with Herdr, and prints what it did.
279
- - `uninstall`: takes it out again, for the user to run in a terminal (`npx roger-roger@latest uninstall`): the standing instruction `install` put in the agents' global files (a marked block in `CLAUDE.md`, `AGENTS.md`, `GEMINI.md`), hooks, skill links, the Herdr plugin, the daemon and the tray, settings and tokens, the global command. `--yes` removes all of that without asking (`--slack-cli` adds the Slack CLI). Never run it for the user unasked.
280
- - `help [command]`: the commands, and one command's flags.
281
- - `status`: current config, whether the tokens are present, the daemon, running sessions, and every valid option value.
282
- - `slack-setup [status|install-cli|login|create]`: the Slack steps `install` runs, one at a time, for repairing a connection by hand. `create` again is safe: it updates the same app, which is what to do after the manifest changed or when a scope is missing.
283
- - `sessions [--all]`: which agents are running, what each is called, and what each is doing. `sessions rename sage plum` gives one a different name (the user's request, not yours to change on a whim).
284
- - `end [name]`: this session is finished. Do this when your work is done, so messages meant for someone else don't come to you.
285
- - `tray status|start|stop|install`: an icon in the system tray showing every agent, with a translucent panel behind it and snooze on the right-click menu. The native part is a separate package installed on request into `~/.roger-roger` (about 4 MB, nothing compiled); everything here works without it. Once installed, the daemon starts it by itself whenever it starts (`setup --tray off` to stop that).
286
- - `snooze [1h|off]`: mute sound and speech for a while; Slack keeps working. For "not now" moments that quiet hours don't cover.
287
- - `hush`: stop the voice or sound playing right now (the user's Stop button in the tray does the same). Only that announcement; the next one plays, and the Slack message stays. If a notification comes back with `results.interrupted`, the user stopped it: don't send it again.
288
- - `hooks status|install|uninstall [claude|opencode|codex|all]`: the terminal-question hooks. When you use your own question tool (Claude Code's question dialog, OpenCode's question tool, Codex's `request_user_input`) or present a plan to approve, you are frozen until it's answered and can't send anything yourself; with the hooks, the agent itself runs roger-roger, and once the question has gone unanswered for a little while the user gets it in Slack with its options. `roger-roger install` offers them; without a name, `hooks install` covers the agents on this machine. What each gets: Claude Code, three entries in `~/.claude/settings.json`; OpenCode, a small plugin at `~/.config/opencode/plugins/roger-roger.js` (restart OpenCode to load it); Codex, three entries in `~/.codex/hooks.json`, to trust in `/hooks`. They follow the user's `when`, quiet hours and snooze like any notification, and log each run to `~/.roger-roger/hooks.log`. Claude Code pings once nobody has typed for a few seconds; OpenCode and Codex can't tell, so they wait `setup --terminal-wait` (default 20s) and ping only if it's still unanswered. Ordinary permission prompts (leave to run a command) don't ping unless the user asks: `setup --permission-pings on` (Claude Code and OpenCode; Codex gives no signal when one is answered). Run `hooks install` again after the skill moves, so they point at the new copy; `hooks status` says if they don't.
289
- - `daemon status|start|stop`: the always-on process behind everything. It starts on demand, so this is only for checking on it or restarting it after a change to the skill's code.
290
- - `play [sound]`: preview a sound (defaults to the configured one).
291
- - `say "text" [--voice Name] [--local]`: preview a voice. `--local` skips Gemini and uses the built-in voice.
292
- - `preview-sounds [sound...] [--voice Name] [--local]`: says each sound's name, then plays the sound. Covers all sounds by default. The JSON `order` lists what played, in order.
293
- - `preview-voices [voice...] [--all] [--text "..."]`: each voice introduces itself by name. Covers a shortlist of six by default. Needs Gemini, and the audio is generated before playback starts, so it takes a few seconds before the first voice speaks.
294
-
295
- ## Requirements
296
-
297
- - Node.js 18 or newer.
298
- - Any agent: nothing in the skill depends on a particular one. A session is recognised by an id the agent publishes if there is one (`CLAUDE_CODE_SESSION_ID`, `CODEX_THREAD_ID`, `OPENCODE_SESSION_ID`, or anything matching `*_SESSION_ID`), else by the `--session` name with the working directory, else by the terminal. `--session-id` and `ROGER_ROGER_SESSION_ID` override all of it. `status` reports which of these answered, as `daemon.me.source`.
299
- - The daemon: one process per machine, started on demand, reached over a local socket (a named pipe on Windows, `daemon.sock` in `~/.roger-roger` elsewhere). Nothing listens on the network. It holds the Slack connection open so messages arrive live, keeps the audio player warm, and is the only writer of the state files. If it can't be started, commands still run in the calling process and say so in `daemon`; everything works except Slack buttons, which need a live connection to arrive.
300
- - One announcement at a time: the daemon plays them in turn, and a lock file (`speaker.lock`) keeps any process that had to fall back from talking over it. A crashed holder is stepped over after about 15 seconds. `ROGER_ROGER_NO_SPEAKER_LOCK=1` turns the queue off; `ROGER_ROGER_NO_DAEMON=1` keeps everything in the calling process.
301
- - Audio players: Windows uses PowerShell (built in). macOS uses `afplay` and `say` (built in). Linux uses one of `paplay`, `pw-play`, `aplay`, or `ffplay` for sound, and `spd-say` or `espeak(-ng)` for the local voice.
302
- - Slack, the normal way: `roger-roger install` (or `slack-setup`, step by step) creates the roger-roger app with the Slack CLI and keeps its tokens in `~/.roger-roger/slack.json`. Tokens saved that way take priority over environment variables.
303
- - Slack, manually: create a Slack app yourself (the bundled `slack/manifest.json` can be pasted at api.slack.com/apps → *Create New App* → *From a manifest*), install it, and set:
304
- - `SLACK_API_BOT_TOKEN` (or `SLACK_BOT_TOKEN`): the bot token (`xoxb-…`), with `chat:write`, `im:write`, `im:history` (to take in DMs sent while nothing was connected), `reactions:write` (the 👀 and ✅ on the user's messages), `files:write` for `--attach`, and `files:read` to receive the screenshots the user sends. Invite the bot to any channel it should post in.
305
- - `ROGER_ROGER_SLACK_APP_TOKEN` (or `SLACK_APP_TOKEN` if that variable isn't used for something else): an app-level token (`xapp-…`) with `connections:write`, needed for `ask`. Button clicks reach the daemon over **Socket Mode**, so turn on Socket Mode and **Interactivity** for the app. No public URL is needed.
306
- - `status.env` shows whether usable tokens are present and where they come from (`slackTokenSource`).
307
- - Gemini: an API key from Google AI Studio, in `GEMINI_API_KEY` or pasted into `roger-roger install` (kept in `~/.roger-roger/keys.json`, readable by the user only). The default model is `gemini-3.1-flash-tts-preview`; change it with `setup --model`.
126
+ `ask` is how you get *permission*. Before anything hard or impossible to undo (deleting or overwriting data, force-pushing or rewriting history, deploying or changing anything live, spending money, sending something to another person), ask first and act only if the answer says to. Show exactly what you'll do (`--details`, `--draft`, `--attach`) and always pass `--on-expire nothing`, so no answer means it doesn't happen. An expired gate is a *no*. Judge by what happens if you're wrong: a force-push to a branch nobody uses needs no permission; the same command against `main` does.
@@ -6,7 +6,7 @@
6
6
 
7
7
  id = "roger-roger"
8
8
  name = "roger-roger"
9
- version = "0.1.14"
9
+ version = "0.2.0"
10
10
  min_herdr_version = "0.9.0"
11
11
  description = "Slack, a sound or a spoken line from the agents in your panes, and questions you answer from your phone"
12
12
  platforms = ["linux", "macos", "windows"]