parawatch 0.3.7 → 0.3.8

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
@@ -1,322 +1,146 @@
1
1
  # parawatch
2
2
 
3
- A control tower for people who run many AI coding agents at once, across several projects and several tasks inside each one.
3
+ For people who run many Claude Code and Codex sessions, often on more than one machine.
4
+
5
+ With that many sessions it gets hard to tell which one was which, whether each is still going well, and which ones asked you something that has since scrolled out of sight. parawatch puts all of them on one board.
4
6
 
5
7
  <picture>
6
- <source media="(prefers-color-scheme: dark)" srcset="docs/app-dark.png">
7
- <img src="docs/app-light.png" alt="The parawatch desktop app: a chart of the last hour of agent activity per project, then the cards that need you, one with two screenshots and three options." width="560">
8
+ <source media="(prefers-color-scheme: dark)" srcset="docs/env-dark.svg">
9
+ <img src="docs/env-light.svg" alt="Left: a laptop and a desktop PC, each running several Claude Code and Codex sessions that look alike, three of them with a question left in the chat. Right: one parawatch board that lists what needs you and every session by project, with its state and its machine." width="100%">
8
10
  </picture>
9
11
 
10
- It answers three questions:
11
-
12
- - **What needs me?** Decisions to make, builds or videos to try, things only you can do.
13
- - **What is running?** Every agent session, its subagents and its `codex exec` jobs, grouped by project, with what each one is doing right now.
14
- - **What waits on someone else?** Pull requests waiting on a review, CI or a merge queue, each next to the session that owns it, with how long it has waited.
15
- - **Did my answer reach the agent?** When you answer a card, or a review lands on its pull request, the agent waiting on it wakes up. You do not go back to the chat to say "I decided" or "a review came in".
16
-
17
- parawatch is a CLI first. Agents use the CLI, and so can you. There is no server. Agent logs and cards never leave your machine; the only thing read from outside is your pull requests, through the GitHub CLI you have already signed in, and only when you have it. On top of the CLI, this repository has a [desktop app](apps/desktop/README.md) (the panel above, a menu bar count, a Dock badge and notifications).
18
-
19
- ```
20
- $ parawatch status
21
- NEEDS YOU 4
22
- c1 decide game/139 Which ending for the shake box? 12m
23
- c2 try game/139 Try build 139: does the glass read as glass? 4m
24
- c3 blocked app Install the iPad simulator runtime 1h
25
- c7 decide app/PR 21 Merge PR 21 and post the 1.4 release note? 9m
26
-
27
- ASKED IN CHAT, NO CARD 1 (read from the log)
28
- Pricing page 6m Publish it now, or wait for the new photos? (site)
29
-
30
- WAITING ON OTHERS 3 · 1 stuck
31
- marble-box#139 stuck in merge queue 9h Game Director Shake round 2 and the top-box ending
32
- ear-trainer#22 review (mina) 5h Settings screen
33
- ear-trainer#21 merge 20m Interval lesson screen Interval lessons and the count-in
34
-
35
- AGENTS 4 working
36
- app
37
- ✓ Interval lesson screen (1 PR) 1m done · PR 21 is green and reviewed; waiting on the merge
38
- ○ App Director 24m PR 18 passed. Waiting for the simulator runtime.
39
- game
40
- ● Game Director (1 PR) 0s Bash: Wait for answers
41
- ├ ● 139 shake round 2 0s Bash: Run tests
42
- ├ ● Generate the level icons 2m exec: python3 make_icons.py
43
- └ ! Overnight physics benchmark 24m stalled · no sign of life for 24m · scene 5 of 8
44
- site
45
- ○ Pricing page 6m asks: Publish it now, or wait for the new photos?
46
- ```
12
+ | | |
13
+ |--|--|
14
+ | **Which was which** | Sessions by project and task, with their subagents and `codex exec` jobs under them, and the machine each one runs on. |
15
+ | **Is it going well** | Working, idle, stalled or failed, what it is doing now, and the last hour as a timeline. |
16
+ | **What needs you** | Questions as cards, including ones left in a chat. Your answer goes back to the agent that asked. |
17
+ | **What waits on others** | Your pull requests: a review, CI or the merge queue, and for how long. |
47
18
 
48
- One line per item, always inside the terminal width. Finished subagents fold into a count after five minutes (`parawatch runs --all` shows them). `--wide` adds the runtime and ids. Colors follow `NO_COLOR` and `FORCE_COLOR`.
19
+ It is a CLI and a macOS app. It does not start agents: it reads the logs of the ones you already run. There is no server.
49
20
 
50
21
  ## Install
51
22
 
52
- There are three parts. Most people want all three.
53
-
54
- **The CLI**, which agents and you use:
55
-
56
- ```
57
- npm install -g parawatch
58
- ```
59
-
60
- **The agent skill**, so Claude Code and Codex agents learn the protocol on their own:
61
-
62
- ```
63
- parawatch skill install
64
- ```
65
-
66
- It links [`skills/parawatch`](skills/parawatch/SKILL.md) into `~/.claude/skills` and `~/.codex/skills`. The installed skill is a link to the copy in the package, so updating parawatch updates the skill.
67
-
68
- **The desktop app**, to see and answer cards without a terminal. On macOS, download the DMG from the [latest release](https://github.com/penspanic/parawatch/releases/latest): `arm64` for Apple silicon, `x64` for Intel. It is signed and notarized, so it opens without warnings. From 0.2.1 on it updates itself: it downloads a new version in the background and asks you to restart (Parawatch > Check for Updates, or Settings > Update automatically). Elsewhere, [build it from source](apps/desktop/README.md#run-from-source).
69
-
70
- **Checking the setup.** `parawatch doctor` shows the versions of the CLI and the app and whether they differ, the store, the skill links, the instruction files in this folder, the hook, and GitHub, with what to run for anything that needs work. Paste it into bug reports. `status` also says, at its end, when a newer CLI is out or when the app and the CLI are different versions.
71
-
72
- ### Updating and removing
73
-
74
- ```
75
- npm install -g parawatch@latest # the CLI; the linked skill follows it
76
- ```
77
-
78
- The CLI asks the npm registry for the latest version at most once a day, only when you run `status` or `doctor` at a terminal. `"updates": { "check": false }` in `config.json` or `PARAWATCH_UPDATE_CHECK=0` turns that off; it is off in CI. The app looks at GitHub Releases every few hours unless you turn Update automatically off.
79
-
80
- To remove parawatch:
81
-
82
23
  ```
83
- parawatch skill uninstall # the skill links
84
- parawatch init --remove -f AGENTS.md # the block in each instruction file you added it to
85
- npm uninstall -g parawatch
24
+ npm install -g parawatch # the CLI, Node 20 or newer
25
+ parawatch skill install # Claude Code and Codex agents learn the protocol
26
+ parawatch status # what needs you, then what is running
86
27
  ```
87
28
 
88
- Take the `parawatch hook` lines out of your Claude Code settings if you added them, drag Parawatch.app to the Trash, and delete `~/.local/state/parawatch` (your cards and history) and `~/Library/Logs/Parawatch` if you want those gone too.
89
-
90
- ### Platforms
29
+ The macOS app is on the [latest release](https://github.com/penspanic/parawatch/releases/latest): `arm64` for Apple silicon, `x64` for Intel, signed and notarized. It updates itself.
91
30
 
92
31
  | | macOS | Linux | Windows |
93
32
  |--|--|--|--|
94
- | CLI | used daily | tests pass in CI | not supported yet |
95
- | Desktop app | used daily | untested | untested |
96
- | Agent logs read | Claude Code, Codex | same | |
97
-
98
- Node 20 or newer.
99
-
100
- ## Concepts
33
+ | CLI | used daily | tests pass in CI | used daily |
34
+ | Desktop app | used daily | no build yet | no build yet |
35
+ | Agent logs read | Claude Code, Codex | same | Claude Code; Codex untested |
101
36
 
102
- | | |
103
- |--|--|
104
- | **Project** | A name, and optionally one or more paths. Git worktrees of a path belong to the project too. |
105
- | **Claim** | An agent session saying what it works on: a project, a task (a ticket key, a pull request, a short name), a title. It is the main way runs are grouped; guesses from the log are the fallback. A claim can also say the work is done. |
106
- | **Card** | Something that needs the human. Four kinds: `decide` (pick an option or write an answer), `try` (open a build, watch a video, look at a screen), `block` (only the human can do it and the agent is stuck until they do: install, sudo, sign in), `todo` (only the human can do it, but nothing waits on it now: check a bill, register a company). A card remembers the session that opened it, and `--since` keeps when the matter first came up when it is older than the card. |
107
- | **Run** | An agent at work. Most runs are *observed*: parawatch reads the agent's own log. Jobs that leave no log (a bake, a benchmark) can be *declared* with `parawatch run start`, with a heartbeat if they can hang silently. |
108
- | **Pull request** | Read through `gh`. Linked to the session that owns it, so its news wakes that session. |
109
- | **Lead** | A role, not a concept of its own. Claim a session with `--role lead` and it shows at the top of its project with its children under it. |
110
37
 
111
- ## For the human
112
-
113
- ```
114
- parawatch status # what needs you, then what is running
115
- parawatch cards # open and answered cards
116
- parawatch show c4 # one card with its body, options and attachments
117
- parawatch answer c4 -c 2 # pick option 2
118
- parawatch answer c4 -m "fill it to the rim" # or just say something
119
- parawatch answer c5 -m "glass is too dark" --attach ~/Desktop/shot.png
120
- parawatch runs --since 12 # agents seen in the last 12 hours
121
- parawatch runs -p game --all # include finished ones
122
- parawatch runs --wide # with runtime and ids
123
- parawatch runs --every # each run of a scheduled task, not one row per task
124
- parawatch run hide <id> # hide a run (6+ characters of its id) until it does something new; `runs --all` still shows it
125
- parawatch run unhide <id>
126
- parawatch pr list # open pull requests: what each waits on, since when, whose it is
127
- parawatch goto api#88 # open the session that owns a pull request
128
- parawatch digest # what each session did today (--date, --json)
129
- ```
38
+ ## More than one machine
130
39
 
131
- Agents say what they work on themselves (`parawatch claim`, below), so nothing has to be registered. Registering a project's paths groups the sessions that start inside it even when they do not claim:
40
+ parawatch does not move files between machines. Point it at a folder that something else keeps in step (Syncthing, a cloud drive), once on each machine:
132
41
 
133
42
  ```
134
- parawatch project add game ~/dev/game
135
- parawatch project add app ~/dev/app ~/notes/app
136
- parawatch claim 811a9612 -p game --role lead --title "Game Director" # claim a session for it
43
+ parawatch sync setup ~/agent-sync/parawatch --device mac --adapter syncthing
44
+ parawatch sync setup ~/agent-sync/parawatch --device pc --adapter syncthing
137
45
  ```
138
46
 
139
- Children of a claimed session follow it.
140
-
141
- ## For agents
142
-
143
- Add the agent protocol to a project's instruction file:
47
+ Cards, answers and the list of running sessions then show on every machine, each marked with where it came from. Agent logs stay on the machine that wrote them. The details are in [More than one machine](docs/sync.md).
144
48
 
145
- ```
146
- parawatch init -f AGENTS.md -p game # or CLAUDE.md
147
- ```
49
+ ## How an answer gets back
148
50
 
149
- It writes a short block between `<!-- parawatch:begin -->` and `<!-- parawatch:end -->` and replaces it in place on later runs. The protocol in short:
51
+ <picture>
52
+ <source media="(prefers-color-scheme: dark)" srcset="docs/flow-dark.svg">
53
+ <img src="docs/flow-light.svg" alt="An agent asks with parawatch ask and waits. The question reaches you as a card. Your answer is returned to the waiting agent, which goes back to work." width="100%">
54
+ </picture>
150
55
 
151
- ```
152
- parawatch claim -p api -t ABC-1234 --title "Fix the login redirect" # as soon as the work is clear
153
- parawatch pr link https://github.com/acme/api/pull/88 # when it opens a pull request
154
- parawatch ask -p game "Which ending?" -o "Side tank" -o "Top box" -r 2 -b "two or three lines"
155
- parawatch ask -p api "Merge PR 88?" --action "gh pr merge 88 --squash" --preview note.md # an approval
156
- parawatch try -p game "Try build 139" --open /path/Game.app --look "does the glass read?"
157
- parawatch block -p app "Install the iPad simulator runtime"
158
- parawatch todo -p game "Check the GitHub bill" --since 2026-10-01
159
- parawatch wait # run in the background; exits when the human answers
160
- parawatch comment c4 "baking the top box now" # a note on the card; does not wake anyone
161
- parawatch close c4 -n "used the top box"
162
- parawatch run start -p game "overnight bench" --heartbeat 600 --log bench.log # a job with no agent log
163
- parawatch claim --done "Merged #88; login works on staging" # when the work is finished
164
- ```
56
+ Claude Code wakes the moment you answer, because `parawatch wait` runs as a background command of the session. Other runtimes read the answer at their next turn.
165
57
 
166
- An agent claims the project the work is for, not the folder it runs in, and claims again when it moves to other work. A claim of `--kind scheduled` folds the runs of a schedule into one row. `--done` marks the session finished until someone writes to it again, and its line goes into the digest.
167
58
 
168
- An approval (`ask --action`) shows the exact command and the text it sends, with Do it and Don't, so the human can say yes without opening the session.
59
+ ## What you see
169
60
 
170
- The human can dismiss a card that no longer matters, with the x on the card in the app or `parawatch dismiss <card> -n "why"`. The card closes without an answer, and the agent that asked gets "dismissed by the human" at its next `wait` or `inbox`.
61
+ ### Cards
171
62
 
172
- `parawatch edit <card>` changes a card after the fact: its kind, wording, task, who asked or `--since`. `parawatch goto <card>` opens the session that asked in the Claude desktop app (`--copy` also puts a line about the card on the clipboard, to paste there); sessions outside the desktop app get the `claude --resume` command instead.
63
+ <picture>
64
+ <source media="(prefers-color-scheme: dark)" srcset="docs/card-dark.png">
65
+ <img src="docs/card-light.png" alt="A card in the desktop app: the question, two screenshots from the agent and three options, the second one picked." width="640">
66
+ </picture>
173
67
 
174
- `wait` prints the answers, and news on linked pull requests, and exits with code 0. Run from an agent session, it reads that session's own cards and pull requests, and the ones no session owns in the project it claimed, with a cursor of its own: four sessions in one project each wake only for their own news. `wait -p <project>` reads the whole project, for a lead. With `--timeout <seconds>` it exits with code 2 when nothing came. An answer given before `wait` starts is not lost: each consumer has its own cursor, and `wait` returns everything after it. While a `wait` runs, cards say an agent is waiting; when none does, an answered card says the answer is kept until the agent next checks.
68
+ | Card | When an agent opens it |
69
+ |--|--|
70
+ | `decide` | You pick an option or write an answer |
71
+ | `try` | You open a build, watch a video or look at a screen |
72
+ | `block` | Only you can do it, and the agent is stuck until you do |
73
+ | `todo` | Only you can do it, and nothing waits on it now |
175
74
 
176
- ### Agent skill
75
+ ### Agents
177
76
 
178
- [`skills/parawatch/SKILL.md`](skills/parawatch/SKILL.md) teaches an agent the whole protocol: claiming its session, writing a good card, keeping one background wait armed, closing cards, declaring long jobs, and who asks the human when a lead runs workers. Install it with `parawatch skill install` (see [Install](#install)); `parawatch skill path` prints where it lives, for runtimes that load skills from elsewhere.
77
+ Every session, its subagents and its `codex exec` jobs, grouped by project, with what each one is doing now. Read from the agents' own logs.
179
78
 
180
- `claim`, `run start` and every card command pick up the calling session from `$CLAUDE_CODE_SESSION_ID` (or `$CODEX_THREAD_ID`), so an agent can put itself on the board without knowing its id.
79
+ <picture>
80
+ <source media="(prefers-color-scheme: dark)" srcset="docs/agents-dark.png">
81
+ <img src="docs/agents-light.png" alt="The desktop app's chart of the last hour, grouped by project: lead sessions with their subagents and a codex exec job, one run stalled, one session that asked in its chat." width="640">
82
+ </picture>
181
83
 
182
- ### Waking a Claude Code session
84
+ ### Pull requests
183
85
 
184
- Claude Code re-invokes a session when one of its background commands exits. So an agent that runs `parawatch wait` as a background command is woken by your answer, with the answer as the command's output.
86
+ Read through the GitHub CLI you are already signed in to. Each open pull request shows what it waits on (a review, CI, the merge queue) and for how long. Its news wakes the session that owns it, like an answer.
185
87
 
186
- As a backstop, the hook adds the session's unhandled answers and pull request news to its next turn:
88
+ ### In a terminal
187
89
 
188
- ```json
189
- {
190
- "hooks": {
191
- "UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "parawatch hook claude-code" }] }],
192
- "SessionStart": [{ "hooks": [{ "type": "command", "command": "parawatch hook claude-code" }] }]
193
- }
194
- }
195
90
  ```
91
+ $ parawatch status
92
+ NEEDS YOU 4
93
+ c1 decide game/139 Which ending for the shake box? 12m
94
+ c2 try game/139 Try build 139: does the glass read as glass? 4m
95
+ c3 blocked app Install the iPad simulator runtime 1h
96
+ c7 decide app/PR 21 Merge PR 21 and post the 1.4 release note? 9m
196
97
 
197
- The hook only peeks. The agent's own `wait` or `inbox` moves its cursor.
198
-
199
- ### Other runtimes
200
-
201
- Anything that can run a command and notice when it exits can use `wait`. `parawatch inbox` returns at once (add `--peek` to leave the cursor where it is), for runtimes that poll.
202
-
203
- ## Pull requests
204
-
205
- When the [GitHub CLI](https://cli.github.com) is installed and signed in, parawatch reads your open pull requests and the ones asking for your review, on every host `gh` knows (GitHub Enterprise included). It never holds a token of its own. Without `gh`, all of this stays off.
206
-
207
- - **Whose it is.** A session links a pull request with `parawatch pr link <url>`. One it opened with `gh pr create` is linked from its log on its own.
208
- - **What it waits on.** Review (and from whom), CI, a failed check, requested changes, a conflict, the merge queue, auto-merge, or the merge itself, and since when. Past a limit per state it shows as stuck.
209
- - **News wakes the owner.** New reviews, approvals, requested changes, comments, CI results, conflicts, leaving the merge queue without merging, and the merge arrive at the owning project's `wait` like an answer. The desktop app notifies what keeps a pull request from landing, and the landing.
210
- - **Other sources.** A merge bot or a ticket system can record news the same way: `parawatch pr event <url> queue_removed -m "batch 412 failed"`.
211
-
212
- GitHub is read when its last reading is older than the interval: by `status`, by a running `wait`, and by the desktop app. `parawatch pr sync` reads it now. Pull requests nobody touched for 60 days are left out unless a session owns them.
98
+ ASKED IN CHAT, NO CARD 1 (read from the log)
99
+ Pricing page 6m Publish it now, or wait for the new photos? (site)
213
100
 
214
- `config.json` in the store folder changes the defaults (every key is optional):
101
+ WAITING ON OTHERS 3 · 1 stuck
102
+ marble-box#139 stuck in merge queue 9h Game Director Shake round 2 and the top-box ending
103
+ ear-trainer#22 review (mina) 5h Settings screen
104
+ ear-trainer#21 merge 20m Interval lesson screen Interval lessons and the count-in
215
105
 
216
- ```json
217
- {
218
- "github": {
219
- "enabled": true,
220
- "intervalMinutes": 5,
221
- "maxAgeDays": 60,
222
- "stallHours": { "review": 24, "CI": 2, "merge queue": 6, "conflict": 4 }
223
- }
224
- }
106
+ AGENTS 4 working
107
+ app
108
+ ✓ Interval lesson screen (1 PR) 1m done · PR 21 is green and reviewed; waiting on the merge
109
+ ○ App Director 24m PR 18 passed. Waiting for the simulator runtime.
110
+ game
111
+ ● Game Director (1 PR) 0s Bash: Wait for answers
112
+ ├ ● 139 shake round 2 0s Bash: Run tests
113
+ ├ ● Generate the level icons 2m exec: python3 make_icons.py
114
+ └ ! Overnight physics benchmark 24m stalled · no sign of life for 24m · scene 5 of 8
115
+ site
116
+ ○ Pricing page 6m asks: Publish it now, or wait for the new photos?
225
117
  ```
226
118
 
227
- `PARAWATCH_GITHUB=0` turns it off for one command.
228
-
229
- ## Observed agents
230
-
231
- parawatch reads agent logs. It never writes to them and never sends them anywhere.
232
-
233
- | Runtime | Where it reads | What it shows |
234
- |--|--|--|
235
- | Claude Code | `~/.claude/projects/*/<session>.jsonl` and `<session>/subagents/` (or `$CLAUDE_CONFIG_DIR`) | sessions with their title, subagents with their description and model, the tool in flight |
236
- | Codex | `~/.codex/sessions/` (or `$CODEX_HOME`) | `codex exec` jobs and interactive sessions with their prompt and last tool call; jobs started from a Claude Code session show under it |
237
-
238
- States: `●` working, `◐` waiting, `○` idle, `✓` done (a finished subagent, or a session that claimed `--done`), `!` stalled (quiet longer than its tool's timeout allows, counting a waited-on background job's output file as activity, or a declared job past its heartbeat), `✗` failed. A Claude Code run whose turn ended while a background shell job is still running (no completion notice or stop yet) counts as working, not idle or done.
239
-
240
- A run belongs to a project by, in order: a claim; its working directory, unless it only edits files of another project; its parent's project; the project whose files it edits most; a project path named in its first prompt. Runs that match nothing show under `other`. `--wide` marks the guesses (`~edits`, `~prompt`).
241
-
242
- When a session's turn ends with a question for the human and no card, parawatch lists it under ASKED IN CHAT. It reads the Claude desktop app's end-of-turn summary when there is one, else a question at the end of the last message. It is a guess, so agents are told to put questions on cards.
119
+ ## Connect a project
243
120
 
244
- ### Adding a runtime
245
-
246
- An adapter is one file in `src/adapters/` that implements:
247
-
248
- ```ts
249
- interface Adapter {
250
- name: string;
251
- scan(opts: { sinceMs: number; now: number }): Run[];
252
- }
121
+ ```
122
+ parawatch init -f AGENTS.md -p game # or CLAUDE.md
253
123
  ```
254
124
 
255
- It reads only, reads only files touched since `sinceMs`, and reads only the head and tail of large logs.
125
+ This writes a short block that tells the agents in that project to say what they work on, ask on cards and keep one `parawatch wait` armed. The details are in [Agents](docs/agents.md).
256
126
 
257
- ## Apps, plugins and dashboards
127
+ ## Docs
258
128
 
259
129
  | | |
260
130
  |--|--|
261
- | [`apps/desktop`](apps/desktop/README.md) | The desktop app. Reads and writes the store directly, so it works without the CLI on `PATH`. |
262
- | [`packages/panel`](packages/panel) | The panel both of them show, as plain DOM, with a harness page for working on it. |
263
-
264
- The desktop app shows each service's own site icon beside runs and pull requests (Claude Code, Codex, GitHub). It fetches the icon from that service's site, like a browser's favicon, and caches it in the app's data folder for a week. Turn it off in Settings and nothing is fetched. The names and icons belong to their owners, and parawatch is not affiliated with them.
265
-
266
- Anything else can build on the same two commands:
267
-
268
- ```
269
- parawatch status --json # one snapshot: projects, cards, runs, pull requests, running waits
270
- parawatch watch # every new event as one JSON line
271
- parawatch watch --from 120 # starting after event 120
272
- ```
273
-
274
- ## Storage
275
-
276
- Everything lives in one append-only file, `~/.local/state/parawatch/events.jsonl` (`$PARAWATCH_HOME`, or `$XDG_STATE_HOME/parawatch`). Writers take a short lock, so several agents can write at once. Next to it: cursors in `cursors/`, running waits in `listeners/`, the last GitHub reading in `github.json`, the last update check in `update.json`, the version each copy (CLI, app) last ran in `clients/`, and the optional `config.json`. Delete the folder to start over. A shared log lives in the shared folder instead; see [More than one machine](#more-than-one-machine).
277
-
278
- Every event carries a format number. A copy of parawatch reads a log that a newer copy wrote, skipping what it does not know, but refuses to write to it and says to update. The desktop app's own log is in `~/Library/Logs/Parawatch/main.log` (Help > Show Log File).
279
-
280
- Desktop notifications for new cards use `osascript` on macOS and `notify-send` on Linux. `PARAWATCH_NOTIFY=0` turns them off. While the desktop app runs it notifies instead, and the CLI stays quiet. To-dos never notify.
281
-
282
- ## More than one machine
283
-
284
- Agents on a second machine (a desktop PC, a build box) can ask and be answered like the ones on your laptop. Parawatch does not move files between machines itself: point it at a folder that something else keeps in step, and each machine writes only its own log there.
285
-
286
- ```sh
287
- # on each machine, once, with its own name
288
- parawatch sync setup ~/agent-sync/parawatch --device mac --adapter syncthing
289
- parawatch sync setup ~/agent-sync/parawatch --device pc --adapter syncthing
290
- parawatch sync # this machine, the others, how the sync tool is doing
291
- ```
292
-
293
- - **Nothing moves until you run `sync setup`.** A machine on its own keeps today's single file and format.
294
- - **`sync setup` moves this machine's log into the folder.** It becomes `events/<device>.jsonl` with every event, id and seq as it was, so cursors and open cards carry on. The old file is kept beside it as `events.jsonl.before-sync`. A copy of parawatch older than 0.3 on that machine stops writing until it is updated.
295
- - **Each machine reads every log and writes only its own.** The logs merge in time order. Two machines never write one file, so a sync tool has nothing to merge.
296
- - **Ids carry the machine once a log is shared:** `pc-c3`, `mac-r2`. Commands take the short form (`parawatch close c3`) while only one machine has that number. Cards from before the move keep their ids.
297
- - **When two machines answer one card before either has seen the other's answer, the earlier answer stands.** The other one stays on the card, marked as not used, and does not wake the agent. An answer given after seeing the first one counts as usual.
298
- - **Paths follow the machine.** Attachments, things to open and project folders written on another machine are read as the same place here, under its home folder or the shared folder. Name other folders that sit in different places on each machine (a cloud drive, say): `parawatch sync root gdrive "G:\My Drive"` on one machine and `parawatch sync root gdrive ~/Library/CloudStorage/…/My\ Drive` on the other. Pasted pictures are saved in the shared folder, so every machine can open them.
299
- - **A path that is absolute on either system is kept as written**, so an agent on the PC can ask you to open `/Users/Shared/Try/Game.app` on the Mac. Git Bash on Windows rewrites arguments that start with `/` before parawatch sees them; run such commands there with `MSYS_NO_PATHCONV=1`.
300
- - **Waits are seen everywhere.** A card on your laptop says whether an agent on the PC is waiting for it.
301
- - **Runs are seen everywhere.** Each machine writes a summary of the sessions its agent logs show (state, title, project, last activity, what each does) to `runs/<device>.json` about every half minute while `status`, a `wait` or the app runs. The other machines show those runs marked with the machine. The logs stay where they are. A machine whose summary is older than 15 minutes drops out of the list.
302
- - **Every machine reads GitHub for itself.** The same news is delivered once.
303
- - **Cards reach you as far as the sync tool does.** With Syncthing, leave global discovery and relays on (its defaults) to stay in step from another network; with them off, machines only meet on the same network or a VPN. `parawatch sync` shows each connection's address, a relay included.
304
-
305
- The shared folder holds `events/`, `devices/` (each machine's paths, version and last sign of life), `runs/`, `listeners/` and `attachments/`. Cursors, the lock, the GitHub reading and settings stay on each machine.
306
-
307
- **Sync adapters** are how parawatch asks the sync tool how it is doing, for `parawatch sync`, `doctor` and the app's settings. Pick one with `--adapter` (or `"sync": { "adapter": … }` in `config.json`):
308
-
309
- | Adapter | Reports |
310
- |---|---|
311
- | `syncthing` | Asks the local Syncthing through its REST API (address and API key from its `config.xml`; `PARAWATCH_SYNCTHING_KEY` or `"sync": { "syncthing": { "url", "home" } }` override): connected devices, when the folder was last in step, files on the way, errors. |
312
- | `folder` | Anything else that keeps a folder in step (a cloud drive, a network share): the other machines' last writes and conflict copies. |
313
-
314
- An adapter is one object with a `status()` method (`src/core/sync.ts`, `registerAdapter`). Another tool, such as a git checkout or a small server, fits the same shape.
131
+ | [Commands](docs/commands.md) | What you run: `status`, `cards`, `answer`, `runs`, `pr`, `digest` |
132
+ | [Agents](docs/agents.md) | The protocol, the skill, how a session is woken, other runtimes |
133
+ | [Pull requests](docs/pull-requests.md) | What is read from GitHub, and its settings |
134
+ | [Observed agents](docs/observed-agents.md) | Which logs are read, run states, adding a runtime |
135
+ | [More than one machine](docs/sync.md) | Cards and answers across machines through a synced folder |
136
+ | [Storage](docs/storage.md) | Where everything is kept, notifications |
137
+ | [Installing, updating, removing](docs/install.md) | The three parts in detail, updates, `parawatch doctor`, uninstalling |
138
+ | [Building on it](docs/integrations.md) | `status --json`, `watch`, the panel package |
315
139
 
316
140
  ## Not goals
317
141
 
318
142
  - Starting or steering agents. Use whatever you already use; parawatch only watches and carries answers back.
319
- - A shared team tracker. It is for one person and their agents, on one machine. It shows other people only as the reviewers your pull requests wait on.
143
+ - A shared team tracker. It is for one person and their agents. It shows other people only as the reviewers your pull requests wait on.
320
144
  - Replacing your issue tracker. Cards are short-lived questions, not the backlog.
321
145
 
322
146
  ## License
@@ -120,7 +120,7 @@ export async function doctor(input) {
120
120
  return false;
121
121
  }
122
122
  });
123
- add("hook", hooked.length ? "ok" : "info", hooked.length ? `Claude Code hook in ${hooked.join(", ")}` : "no Claude Code hook (optional; see the README)");
123
+ add("hook", hooked.length ? "ok" : "info", hooked.length ? `Claude Code hook in ${hooked.join(", ")}` : "no Claude Code hook (optional; see docs/agents.md)");
124
124
  // GitHub
125
125
  const g = githubConfig();
126
126
  if (!g.enabled)
@@ -247,6 +247,8 @@ export function waitText(p) {
247
247
  if (p.reviewRequested && !p.mine)
248
248
  return `${p.stalled ? "long " : ""}waiting for your review`;
249
249
  const who = p.who?.length && p.waitingOn === "review" ? ` (${p.who.join(", ")})` : "";
250
+ if (p.stalled && p.waitingOn === "merge held")
251
+ return "merge held, stuck";
250
252
  return `${p.stalled ? "stuck in " : ""}${p.waitingOn}${who}`;
251
253
  }
252
254
  function ownerText(p) {
@@ -350,7 +352,10 @@ export function runsTree(runs, v = PLAIN, now = Date.now(), indent = "") {
350
352
  const ended = ENDED.has(r.state);
351
353
  const titlePaint = r.role === "lead" ? ["bold"] : ended ? ["dim"] : [];
352
354
  const mark = paint(v, STATE_MARK[r.state], ...STATE_PAINT[r.state]);
353
- const label = r.title + (r.repeats ? ` ×${r.repeats + 1}` : "") + (r.prs?.length ? ` (${r.prs.length} PR)` : "");
355
+ // the repeat count and the PR count stay whole; a long title gives way before them
356
+ const tail = (r.repeats ? ` ×${r.repeats + 1}` : "") + (r.prs?.length ? ` (${r.prs.length} PR)` : "");
357
+ const keep = textWidth(tail);
358
+ const label = keep && room - keep >= 6 ? fit(r.title, room - keep) + tail : r.title + tail;
354
359
  const title = paint(v, pad(fit(label, room), room), ...titlePaint);
355
360
  const age = paint(v, ago(r.lastActivity, now).padStart(4), "dim");
356
361
  const dev = devW ? " " + paint(v, pad(fit(r.device ?? "", devW), devW), "cyan") : "";
@@ -18,6 +18,7 @@ export const DEFAULT_STALL_HOURS = {
18
18
  "merge queue": 6,
19
19
  "auto-merge": 6,
20
20
  merge: 24,
21
+ "merge held": 8,
21
22
  };
22
23
  /** `config.json` in the parawatch home, all of it optional. */
23
24
  export function githubConfig() {
@@ -123,7 +124,7 @@ const FIELDS = (withQueue) => `
123
124
  latestReviews(first: 30) { nodes { author { login } state submittedAt } }
124
125
  comments { totalCount }
125
126
  commits(last: 1) { nodes { commit { statusCheckRollup { state } } } }
126
- ${withQueue ? "mergeQueueEntry { state position }\n autoMergeRequest { enabledAt }" : ""}`;
127
+ ${withQueue ? "mergeQueueEntry { state position }\n autoMergeRequest { enabledAt }\n mergeStateStatus" : ""}`;
127
128
  function gqlString(s) {
128
129
  return JSON.stringify(s);
129
130
  }
@@ -177,6 +178,7 @@ function toStatus(host, node, viewer, asked) {
177
178
  state: state === "MERGED" ? "merged" : state === "CLOSED" ? "closed" : "open",
178
179
  draft: node.isDraft === true,
179
180
  mergeable: s(node.mergeable),
181
+ mergeState: s(node.mergeStateStatus),
180
182
  reviewDecision: s(node.reviewDecision) ?? undefined,
181
183
  pending,
182
184
  reviews,
@@ -198,8 +200,8 @@ export async function fetchHost(host, extra, gh = runGh, maxAgeDays = 60) {
198
200
  out = await ask(true);
199
201
  }
200
202
  catch (err) {
201
- // older GitHub Enterprise servers have no merge queue fields
202
- if (!/mergeQueueEntry|autoMergeRequest/.test(String(err.message)))
203
+ // older GitHub Enterprise servers have no merge queue fields, nor mergeStateStatus
204
+ if (!/mergeQueueEntry|autoMergeRequest|mergeStateStatus/.test(String(err.message)))
203
205
  throw err;
204
206
  out = await ask(false);
205
207
  }
@@ -246,6 +248,10 @@ export function waitingOn(p) {
246
248
  return { what: "merge queue" };
247
249
  if (p.ci === "PENDING" || p.ci === "EXPECTED")
248
250
  return { what: "CI" };
251
+ // approved (or no review needed) and CI green, yet GitHub says it cannot be merged: a rule other
252
+ // than reviews and CI, or a merge system outside GitHub, holds it. GitHub does not say who.
253
+ if (p.mergeState === "BLOCKED" && p.reviewDecision !== "REVIEW_REQUIRED" && !p.pending.length)
254
+ return { what: "merge held" };
249
255
  if (p.reviewDecision === "APPROVED")
250
256
  return p.autoMerge ? { what: "auto-merge" } : { what: "merge", who: author };
251
257
  if (p.pending.length)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "parawatch",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "description": "A control tower for people running many AI coding agents in parallel: see what is running, what needs you, and wake the right agent when you answer.",
5
5
  "keywords": [
6
6
  "ai-agents",