Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 8 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ app. Closing this needs the server-side blocking-message capture described above
`agent.send`, `events.subscribe`, …). It translates to/from an internal domain model
(`AgentStatus`, `AgentView`, `SnapshotResponse` — `bridge/types.ts`), so a Herdr API rename is a
one-file fix, not a shatter.
- **Two transports behind one interface, chosen by platform.** `herdr-client.ts` exports a
`HerdrClient` interface with two implementations selected by `createHerdrClient()`:
`SocketHerdrClient` (mac/Linux) opens Herdr's Unix socket directly, the verified default;
`CliHerdrClient` (Windows) spawns the `herdr` CLI once per RPC, because Herdr maps the socket path
onto a Windows named pipe that `Bun.connect({unix})` can't reach. Both emit identical JSON
envelopes, so nothing above the adapter changes. On Windows there's no `events.subscribe` CLI
equivalent, so the poke stream reports down and the poll below carries the whole load — which it's
designed to do anyway.
- **Output model: poll, not stream — event-poked.** Herdr exposes `pane.read` (snapshot) and
`pane.output_matched` (regex event) but **no raw output-stream event**, so there is nothing to
stream even if we wanted to; the live pane view is poll-on-status-change + caching. The bridge's
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ All notable changes to Collie are recorded here. The format follows
`version` in `herdr-plugin.toml`, `package.json`, and `web/package.json` (enforced by
`scripts/check-version.sh`). See [`CLAUDE.md`](./CLAUDE.md) → *Versioning* for the bump policy.

## [0.15.0] - 2026-07-23

### Added
- Windows support: the bridge talks to Herdr by spawning the `herdr` CLI per RPC, since Herdr's Windows control socket is a named pipe `Bun.connect({unix})` can't reach. Selected by platform — mac/Linux keep the direct Unix-socket transport unchanged
- `HERDR_BIN_PATH` / `COLLIE_HERDR_BIN` config for the `herdr` binary path (Windows only; defaults to `herdr` on PATH)

### Changed
- `herdr-client.ts` split into a `HerdrClient` interface with `SocketHerdrClient` (mac/Linux) and `CliHerdrClient` (Windows) implementations, chosen by `createHerdrClient()`
- On Windows there is no `events.subscribe` stream (no CLI equivalent), so change detection is poll-only — events were always just a poke, never a source of truth, so correctness is unaffected

## [0.14.2] - 2026-07-23

### Added
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,21 @@ deps by hand — the build runs `bun install` for you; the backend imports only
[`web-push`](https://www.npmjs.com/package/web-push) is optional and lazy (see [Web
Push](#web-push-optional)).

### Windows

Collie runs on Windows with two caveats:

- **`herdr` must be on `PATH`** (or point `HERDR_BIN_PATH` / `COLLIE_HERDR_BIN` at it). On Windows,
Herdr's control socket is a named pipe the bridge can't open directly, so it talks to Herdr by
spawning the `herdr` CLI per request instead — this needs the same-version binary reachable.
- **Change detection is poll-only.** There's no live `events.subscribe` stream over the CLI, so the
UI refreshes on the poll cadence (`COLLIE_POLL_MS`, default 1.5s) rather than being poked instantly
on a herd change. Events were only ever a poke — polling is the source of truth — so nothing but
refresh latency changes.

The control script (`collie-ctl.sh`) shells out via `bash`, so run it under Git Bash; without
`systemd --user` it supervises the bridge as a `nohup` process with a pidfile.

## Install

On the host, not your phone. Two ways in.
Expand Down
8 changes: 8 additions & 0 deletions bridge/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ function envBool(name: string, fallback: boolean): boolean {
export interface Config {
/** Path to Herdr's control socket. A non-Herdr-launched daemon must discover this itself. */
socketPath: string;
/**
* Path to the `herdr` binary. Only used on Windows, where the bridge talks to Herdr by spawning
* this CLI (Herdr's socket is a Windows named pipe Bun can't open directly — see herdr-client.ts).
* Herdr injects `HERDR_BIN_PATH` into plugin commands; we fall back to that, then `COLLIE_HERDR_BIN`,
* then a bare `herdr` resolved on PATH. Inert on mac/Linux (the socket transport ignores it).
*/
herdrBin: string;
/** TCP port the bridge listens on (loopback only). `tailscale serve` proxies to it. */
port: number;
/**
Expand Down Expand Up @@ -149,6 +156,7 @@ export function loadConfig(): Config {

return {
socketPath: process.env.HERDR_SOCKET_PATH ?? join(homedir(), ".config", "herdr", "herdr.sock"),
herdrBin: process.env.HERDR_BIN_PATH ?? process.env.COLLIE_HERDR_BIN ?? "herdr",
port: envInt("COLLIE_PORT", 8787, { min: 1, max: 65535 }),
host: process.env.COLLIE_HOST ?? "127.0.0.1",
pollMs: envInt("COLLIE_POLL_MS", 1500, { min: 250 }),
Expand Down
Loading