Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "flow-scenes",
"displayName": "Flow Scenes",
"version": "0.8.0",
"version": "0.9.0",
"description": "Ambient scenes above the prompt that move with your agent's work: a fire, bubbles, surf, ski, rockets, a hot-air balloon and more, by day or night. /flow to control it.",
"author": {
"name": "Rob Macrae"
Expand Down
26 changes: 22 additions & 4 deletions AGENTS.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ CI runs the same on every pull request. For a change you can see, also run `npm

Flow runs inside people's coding sessions, so it stays small and local. A pull request that crosses one of these lines needs a good reason, said up front:

- **No network.** Nothing in `hooks/` or `pi/` contacts a host, and nothing a hook reads (the conversation, files, settings) leaves the machine.
- **No network.** Nothing in `hooks/`, `opencode/` or `pi/` contacts a host, and nothing a hook reads (the conversation, files, settings) leaves the machine.
- **No new runtime dependencies.** The mod runs on its own files alone. Dev tools go in `devDependencies`, pinned to an exact version (no lockfile is committed: see AGENTS.md).
- **Hooks only watch.** Every hook passes its event on unchanged. Flow never blocks, allows or rewrites a tool call, a permission or a prompt, and it answers no command but `/flow`.
- **What it stores stays as described** in the README's *What Flow does on your machine*. If a change reads or keeps something new, update that section in the same pull request.
Expand Down
41 changes: 34 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<img width="1200" height="384" alt="Flow's fire above the prompt: pilot lights at idle, climbing with each of Claude's tool calls, an inferno with three subagents, then back down when Claude is done" src="https://github.com/user-attachments/assets/0f0120cc-af23-44ec-9398-309cccd42764" />

Ambient scenes in your terminal that move with the work your coding agent is doing: a fire, fizzing bubbles, the surf, a ski run, two rockets, a hot-air balloon, a steam engine, a train through the countryside, a colony ship behind its shield, the Earth rising over the moon, and a space marine fighting through a 1993 techbase. They run above the prompt of Claude Code (in the terminal or the Claude desktop app) or [pi](https://github.com/badlogic/pi-mono), or in a tall pane beside Claude Code's transcript.
Ambient scenes in your terminal that move with the work your coding agent is doing: a fire, fizzing bubbles, the surf, a ski run, two rockets, a hot-air balloon, a steam engine, a train through the countryside, a colony ship behind its shield, the Earth rising over the moon, and a space marine fighting through a 1993 techbase. They run above the prompt of Claude Code (in the terminal or the Claude desktop app), [opencode](https://opencode.ai) or [pi](https://github.com/badlogic/pi-mono), or in a tall pane beside Claude Code's transcript or in opencode's sidebar.

Each scene is drawn in 24-bit color from Unicode block, quadrant and braille characters, so several pixels share each terminal cell. In the Claude desktop app, which has no character grid to draw into, the same frames are drawn as images.

Expand All @@ -24,6 +24,12 @@ Installed it before as `flow@robdmac`? The plugin is now `flow-scenes` (another

**Claude desktop**, in the Code tab: click **+** next to the prompt box, then **Plugins** → **Add plugin**, add the marketplace `robdmac/flow` and install **Flow** (`flow-scenes`). Manage it later from **+** → **Plugins** → **Manage plugins**. The desktop app and Claude Code share their plugins, so if you've installed Flow in the terminal it's already there. It runs in local sessions, not cloud ones.

**opencode** (2.0 or later), from your shell:

```
opencode plugin add github:robdmac/flow
```

**pi**, from your shell:

```
Expand Down Expand Up @@ -165,7 +171,7 @@ The background is long takes that blend one into the next at random moments, so

When Claude starts waiting on you (a permission, its question, a plan to approve), a soft chime: two struck tubes rising a fourth, about as loud as a scene at 6. Once a wait, and not for one right behind another (within 20 s): you're there already. It's part of the soundscape, so it follows the session's sound setting: `/flow sound` turns it on and off with the rest. For an alert without the ambience, there's Claude Code's own notification setting in `/config`.

It plays only while the scene is on screen, and stops with the session. Claude Code plays the clips with `afplay`, so it's macOS only; elsewhere the setting does nothing. It's set per session like everything else, so you can have it on in one session and not another, or quieter in one; several sessions with it on each play their own.
It plays only while the scene is on screen, and stops with the session. Claude Code plays the clips with `afplay`, and in opencode Flow runs `afplay` itself, so it's macOS only; elsewhere the setting does nothing. It's set per session like everything else, so you can have it on in one session and not another, or quieter in one; several sessions with it on each play their own.

## Turning it off

Expand Down Expand Up @@ -193,19 +199,36 @@ pi tells an extension nothing of a wait on you, so there the scenes never show o

As in Claude Code, each session keeps its own settings, kept in the session itself (an entry the model never sees), so resuming or forking it brings them back. `~/.pi/agent/flow.json` holds the defaults new sessions start with; `/flow save` writes it. To try it without installing: `pi --extension ./pi/index.ts`.

## opencode

The same scenes run as an opencode terminal UI plugin (`opencode/tui.ts`, opencode 2.0 or later): above the prompt of the session on screen, or with `/flow spine` in its sidebar. They share the scenes, the activity model, the soundscapes and the `/flow` command with the Claude Code version; `/flow pick` is opencode's own list, `/flow help` a dialog. Click a subagent's companion to see what it's doing: its task, whether it's working, quiet, waiting on you or done, and how long it's run. The one-time tips are the same as in Claude Code: the other scenes, then the sound, each once, as a session first shows or under a `/flow` reply. opencode's events drive it:

| opencode event | The scene |
|---|---|
| `session.execution.started` / ended | a turn lifts it (a level more for every 30 s it runs), then it settles to idle; a turn that fails shows as smoke |
| `session.step.started`, text and reasoning deltas | each model step, and streamed text and thinking, keep it going; the model's variant sets the floor (`low` 2 … `max` 6) |
| `session.tool.called` | `write`/`edit`/`apply_patch` push it by lines written, `shell` sparks, reads and other tools flicker |
| `session.tool.success`, `.failed` | a command that exits non-zero shows as smoke |
| `session.created` with a parent | a subagent: a companion of its own, there till its run ends (done well, or not) |
| `permission.asked`, `form.created` | a permission or a question put to you: the scene settles and breathes till it's answered |
| `session.compaction.ended` | smoke, as for a compaction in Claude Code |
| `session.usage.updated` | a context nearly full (the last step's tokens over the model's limit) shows as blue |

Each session keeps its own settings, kept in Flow's own opencode storage under the session's id (the 100 used most recently), so reopening it brings them back; `/flow save` makes them the defaults new sessions start with, for every opencode on the machine. Options set for the plugin in `cli.json` (`{ "package": "github:robdmac/flow", "options": { "style": "surf" } }`) are defaults too, under what `/flow save` keeps. opencode has no player for a plugin's own sounds, so for the soundscape Flow runs `afplay` (macOS) in the background itself, one per clip, stopped with the scene or opencode.

## What Flow does on your machine

**Privacy:** Flow collects nothing. It has no servers, sends no data anywhere, and keeps what little it stores (below) on your machine: in Claude Code's own storage for the plugin, or in pi under `~/.pi/agent/flow.json` and `~/.cache/flow`.
**Privacy:** Flow collects nothing. It has no servers, sends no data anywhere, and keeps what little it stores (below) on your machine: in Claude Code's or opencode's own storage for the plugin, or in pi under `~/.pi/agent/flow.json` and `~/.cache/flow`.

**Network:** none. Flow sends nothing anywhere and contacts no hosts. Everything it draws and plays is computed locally from the plugin's own files. The one call that takes anything out of the plugin is `$.audio.play`, which hands one of the plugin's own clips from `sounds/` (and a volume) to the local audio player, `afplay`: nothing Flow reads (settings, the conversation, your files) goes into it, and it reaches no address.
**Network:** none. Flow sends nothing anywhere and contacts no hosts. Everything it draws and plays is computed locally from the plugin's own files. The one call that takes anything out of the plugin is `$.audio.play`, which hands one of the plugin's own clips from `sounds/` (and a volume) to the local audio player, `afplay`: nothing Flow reads (settings, the conversation, your files) goes into it, and it reaches no address. In opencode, which has no such call, Flow starts `/usr/bin/afplay` itself for each clip (macOS only, and only with the sound on): its arguments are a volume and the path of one of the plugin's own clips, or of a short sound it made (written to a file of its own in the system's temporary folder, and deleted once played). It runs nothing else.

**Your settings:** Flow writes its own `/config` rows (the `flow-scenes` plugin's options: scene, mode, sound and the rest) only when you run `/flow save`, which writes the settings your session shows as the defaults for new sessions. It reads them (`$.config.list()`) to start each session on your defaults.

It writes no other settings, no files and no environment variables. (`CLAUDE_CODE_ENABLE_FUNCTION_HOOKS`, under *Not loading?*, is a variable you set yourself, if you need it.)

**What it keeps:** in Claude Code's per-plugin store, each session's own `/flow` settings (the 100 most recently used sessions, none unused for 60 days) and which one-time tips you've seen. In the session's own plugin state, the balloon's altitude and whether someone has been at the session. In pi, a session's settings go in the session as an entry the model never sees, and `/flow save` writes `~/.pi/agent/flow.json`.
**What it keeps:** in Claude Code's per-plugin store, each session's own `/flow` settings (the 100 most recently used sessions, none unused for 60 days) and which one-time tips you've seen. In the session's own plugin state, the balloon's altitude and whether someone has been at the session. In opencode, each session's own `/flow` settings (the 100 most recently used), the defaults `/flow save` keeps and which one-time tips you've seen, in opencode's storage for the plugin. In pi, a session's settings go in the session as an entry the model never sees, and `/flow save` writes `~/.pi/agent/flow.json`.

**Sound:** off unless you turn it on. Claude Code plays the plugin's own clips (`sounds/`) through `afplay`, only after someone has typed or run a turn in the session.
**Sound:** off unless you turn it on. Claude Code plays the plugin's own clips (`sounds/`) through `afplay`, only after someone has typed or run a turn in the session; in opencode, Flow plays them through `afplay` itself, once a turn has run or `/flow` has been used in the session on screen.

**What its hooks see, and what they decide:** nothing. Every hook passes the event on unchanged (the one Flow answers is its own `/flow` command); Flow never blocks, allows, rewrites or answers a tool call, a permission, a prompt or a setting. It only watches, to set how busy the scene looks:

Expand All @@ -222,13 +245,15 @@ It writes no other settings, no files and no environment variables. (`CLAUDE_COD

It also lists the session's running subagents (`$.agent.list()`: their state, task and type) to give each one a companion and a hover card.

In opencode, Flow is a terminal UI plugin only: nothing of it runs in opencode's server, and it changes nothing it hears. It reads opencode's events for the session on screen and its subagents, as in the table under *opencode*: tool names, the lines an edit writes (counted, not kept), a command's exit code, how much text streams (its length), that a permission or question is up and when it's answered, token counts. It draws in two places in opencode's screen (above the prompt, and the sidebar) and adds the `/flow` command.

## Cost

Flow repaints about 14 times a second while busy and 8 times when calm. It skips unchanged frames and doesn't repaint at all while off screen. In the desktop app it redraws at most 10 times a second. The heaviest scene takes under 2 ms per frame.

## Compatibility

Built against Claude Code 2.1.289 and pi 1.0.0. Mods (function-hook plugins) are early access: the API may change between Claude Code releases, so a newer release can break flow until it's updated.
Built against Claude Code 2.1.289, opencode 2.0.29 and pi 1.0.0, the oldest of each it's known to work on. On an older one Flow still runs, but says so once (a notice when a session starts, once for each version), since a scene may misbehave there. Mods (function-hook plugins) are early access: the API may change between Claude Code releases, so a newer release can break flow until it's updated. opencode's plugin API for its terminal UI is new with opencode 2, and may change between its releases too.

## Development

Expand All @@ -253,6 +278,8 @@ npm run check # colour pairs and timing, before you open a pull r

`new-scene` writes `hooks/aurora.ts`: a scene that already moves with the level, shows the tints, breathes while Claude waits on you and (with `--night`) has a night. Edit its `paint()`: it gets a grid of pixels (2 × 2 a terminal cell) and the dials (the level, eased; the frame count; night; the tint; how far into a wait on you; whether it's the tall spine). [AGENTS.md](AGENTS.md) has the rules a scene keeps to.

To try the opencode version from a checkout, put a one-line `tui.ts` in a project's `.opencode/plugins/flow/`: `export { default } from '/path/to/flow/opencode/tui.ts'`, then run `opencode` in that project.

Every hook file carries a `// REVISION:` marker. On startup the mod writes the loaded revision, the local time and the UTC offset to the debug log (`claude --debug`).

## License
Expand Down
Loading
Loading