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 +88 -264
- package/dist/core/doctor.js +1 -1
- package/dist/core/format.js +6 -1
- package/dist/core/github.js +9 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,322 +1,146 @@
|
|
|
1
1
|
# parawatch
|
|
2
2
|
|
|
3
|
-
|
|
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/
|
|
7
|
-
<img src="docs/
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84
|
-
parawatch
|
|
85
|
-
|
|
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
|
-
|
|
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 |
|
|
95
|
-
| Desktop app | used daily |
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
|
135
|
-
parawatch
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
+
## What you see
|
|
169
60
|
|
|
170
|
-
|
|
61
|
+
### Cards
|
|
171
62
|
|
|
172
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
75
|
+
### Agents
|
|
177
76
|
|
|
178
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
84
|
+
### Pull requests
|
|
183
85
|
|
|
184
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
127
|
+
## Docs
|
|
258
128
|
|
|
259
129
|
| | |
|
|
260
130
|
|--|--|
|
|
261
|
-
| [
|
|
262
|
-
| [
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
|
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
|
package/dist/core/doctor.js
CHANGED
|
@@ -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
|
|
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)
|
package/dist/core/format.js
CHANGED
|
@@ -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
|
-
|
|
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") : "";
|
package/dist/core/github.js
CHANGED
|
@@ -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.
|
|
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",
|