cdragon 0.1.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 +110 -0
- package/bin/cdragon.js +170 -0
- package/package.json +31 -0
- package/skills/agent-browser/SKILL.md +50 -0
- package/skills/grill-me/SKILL.md +7 -0
- package/skills/herdr-agent/SKILL.md +142 -0
- package/skills/herdr-cli/SKILL.md +388 -0
- package/skills/herdr-cli/scripts/herdr-agent-run-and-wait +203 -0
- package/skills/herdr-cli/scripts/herdr-agent-wait-complete +168 -0
- package/skills/notion-presentation/SKILL.md +170 -0
- package/skills/notion-presentation/references/example-redis-deck.md +97 -0
- package/skills/setup-matt-pocock-skills/SKILL.md +127 -0
- package/skills/setup-matt-pocock-skills/domain.md +51 -0
- package/skills/setup-matt-pocock-skills/issue-tracker-github.md +34 -0
- package/skills/setup-matt-pocock-skills/issue-tracker-gitlab.md +35 -0
- package/skills/setup-matt-pocock-skills/issue-tracker-local.md +19 -0
- package/skills/setup-matt-pocock-skills/triage-labels.md +15 -0
- package/skills/tdd/SKILL.md +108 -0
- package/skills/tdd/mocking.md +59 -0
- package/skills/tdd/refactoring.md +10 -0
- package/skills/tdd/tests.md +61 -0
- package/skills/to-html/SKILL.md +83 -0
- package/skills/to-html/designs/INDEX.md +74 -0
- package/skills/to-html/designs/airbnb.DESIGN.md +581 -0
- package/skills/to-html/designs/airtable.DESIGN.md +275 -0
- package/skills/to-html/designs/alipay.DESIGN.md +456 -0
- package/skills/to-html/designs/apple.DESIGN.md +566 -0
- package/skills/to-html/designs/banksalad.DESIGN.md +621 -0
- package/skills/to-html/designs/channeltalk.DESIGN.md +374 -0
- package/skills/to-html/designs/clay.DESIGN.md +398 -0
- package/skills/to-html/designs/clickhouse.DESIGN.md +374 -0
- package/skills/to-html/designs/cohere.DESIGN.md +361 -0
- package/skills/to-html/designs/coinone.DESIGN.md +218 -0
- package/skills/to-html/designs/coupang.DESIGN.md +502 -0
- package/skills/to-html/designs/cursor.DESIGN.md +416 -0
- package/skills/to-html/designs/elevenlabs.DESIGN.md +376 -0
- package/skills/to-html/designs/expo.DESIGN.md +373 -0
- package/skills/to-html/designs/figma.DESIGN.md +490 -0
- package/skills/to-html/designs/framer.DESIGN.md +393 -0
- package/skills/to-html/designs/freee.DESIGN.md +572 -0
- package/skills/to-html/designs/gangnamunni.DESIGN.md +621 -0
- package/skills/to-html/designs/gmarket.DESIGN.md +483 -0
- package/skills/to-html/designs/gogolook.DESIGN.md +131 -0
- package/skills/to-html/designs/hahow.DESIGN.md +158 -0
- package/skills/to-html/designs/hashicorp.DESIGN.md +369 -0
- package/skills/to-html/designs/hyundaicard.DESIGN.md +177 -0
- package/skills/to-html/designs/ibm.DESIGN.md +420 -0
- package/skills/to-html/designs/kakaobank.DESIGN.md +548 -0
- package/skills/to-html/designs/kakaopay.DESIGN.md +544 -0
- package/skills/to-html/designs/karrot.DESIGN.md +445 -0
- package/skills/to-html/designs/kdan.DESIGN.md +160 -0
- package/skills/to-html/designs/krds.DESIGN.md +997 -0
- package/skills/to-html/designs/line.DESIGN.md +431 -0
- package/skills/to-html/designs/linear.app.DESIGN.md +548 -0
- package/skills/to-html/designs/miro.DESIGN.md +272 -0
- package/skills/to-html/designs/mistral.ai.DESIGN.md +353 -0
- package/skills/to-html/designs/money-forward.DESIGN.md +401 -0
- package/skills/to-html/designs/mongodb.DESIGN.md +357 -0
- package/skills/to-html/designs/naver.DESIGN.md +533 -0
- package/skills/to-html/designs/nhncloud.DESIGN.md +174 -0
- package/skills/to-html/designs/opencode.ai.DESIGN.md +388 -0
- package/skills/to-html/designs/pinterest.DESIGN.md +322 -0
- package/skills/to-html/designs/posthog.DESIGN.md +430 -0
- package/skills/to-html/designs/raycast.DESIGN.md +422 -0
- package/skills/to-html/designs/remember.DESIGN.md +460 -0
- package/skills/to-html/designs/resend.DESIGN.md +396 -0
- package/skills/to-html/designs/sanity.DESIGN.md +449 -0
- package/skills/to-html/designs/sendbird.DESIGN.md +285 -0
- package/skills/to-html/designs/smarthr.DESIGN.md +404 -0
- package/skills/to-html/designs/socar.DESIGN.md +403 -0
- package/skills/to-html/designs/spotify.DESIGN.md +265 -0
- package/skills/to-html/designs/supabase.DESIGN.md +348 -0
- package/skills/to-html/designs/superhuman.DESIGN.md +414 -0
- package/skills/to-html/designs/together.ai.DESIGN.md +356 -0
- package/skills/to-html/designs/toss.DESIGN.md +655 -0
- package/skills/to-html/designs/uber.DESIGN.md +387 -0
- package/skills/to-html/designs/upstage.DESIGN.md +232 -0
- package/skills/to-html/designs/velog.DESIGN.md +168 -0
- package/skills/to-html/designs/vercel.DESIGN.md +479 -0
- package/skills/to-html/designs/wanted.DESIGN.md +529 -0
- package/skills/to-html/designs/wise.DESIGN.md +276 -0
- package/skills/to-html/designs/yanolja.DESIGN.md +463 -0
- package/skills/to-html/designs/yeogiotte.DESIGN.md +459 -0
- package/skills/to-html/designs/zapier.DESIGN.md +433 -0
- package/skills/to-html/designs/zigzag.DESIGN.md +633 -0
- package/skills/to-issues/SKILL.md +84 -0
- package/skills/to-prd/SKILL.md +75 -0
- package/src/colors.js +15 -0
- package/src/link.js +47 -0
- package/src/prompt.js +137 -0
- package/src/skills.js +75 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: herdr-cli
|
|
3
|
+
description: "Control herdr from inside it. Manage workspaces and tabs, split panes, spawn agents, read output, and wait for state changes — all via CLI commands that talk to the running herdr instance over a local unix socket. Use when running inside herdr (HERDR_ENV=1)."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# herdr — agent skill
|
|
7
|
+
|
|
8
|
+
before using this skill, check that `HERDR_ENV=1`. if it is not set to `1`, say you are not running inside a herdr-managed pane and stop. do not inspect or control the focused herdr pane from outside herdr.
|
|
9
|
+
|
|
10
|
+
you are running inside herdr, a terminal-native agent multiplexer. herdr gives you workspaces, tabs, and panes — each pane is a real terminal with its own shell, agent, server, or log stream — and you can control all of it from the cli.
|
|
11
|
+
|
|
12
|
+
this means you can:
|
|
13
|
+
|
|
14
|
+
- see what other panes and agents are doing
|
|
15
|
+
- create tabs for separate subcontexts inside one workspace
|
|
16
|
+
- split panes and run commands in them
|
|
17
|
+
- start servers, watch logs, and run tests in sibling panes
|
|
18
|
+
- wait for specific output before continuing
|
|
19
|
+
- wait for another agent to finish
|
|
20
|
+
- spawn more agent instances
|
|
21
|
+
|
|
22
|
+
the `herdr` binary is available in your PATH. its workspace, tab, pane, and wait commands talk to the running herdr instance over a local unix socket.
|
|
23
|
+
|
|
24
|
+
if you need the raw protocol or full api reference, read the [socket api docs](https://herdr.dev/docs/socket-api/).
|
|
25
|
+
|
|
26
|
+
## concepts
|
|
27
|
+
|
|
28
|
+
**workspaces** are project contexts. each workspace has one or more tabs. unless manually renamed, a workspace's label follows the first tab's root pane — usually the repo name, otherwise the root pane's current folder name.
|
|
29
|
+
|
|
30
|
+
**tabs** are subcontexts inside a workspace. each tab has one or more panes.
|
|
31
|
+
|
|
32
|
+
**panes** are terminal splits inside a tab. each pane runs its own process — a shell, an agent, a server, anything.
|
|
33
|
+
|
|
34
|
+
**agent status** is detected automatically by herdr. the api exposes one public field for it:
|
|
35
|
+
|
|
36
|
+
- `agent_status` — `idle`, `working`, `blocked`, `done`, `unknown`
|
|
37
|
+
|
|
38
|
+
`done` means the agent finished, but you have not looked at that finished pane yet.
|
|
39
|
+
|
|
40
|
+
plain shells still exist as panes, but herdr's sidebar agent section intentionally focuses on detected agents rather than listing every shell.
|
|
41
|
+
|
|
42
|
+
**ids** — workspace ids look like `1`, `2`. tab ids look like `1:1`, `1:2`, `2:1`. pane ids look like `1-1`, `1-2`, `2-1`. these are compact public ids for the current live session.
|
|
43
|
+
|
|
44
|
+
important: ids can compact when tabs, panes, or workspaces are closed. do not treat them as durable ids. re-read ids from `workspace list`, `tab list`, `pane list`, or create/split responses when you need a current id. do not guess that an older `1-3` is still the same pane later.
|
|
45
|
+
|
|
46
|
+
## discover yourself
|
|
47
|
+
|
|
48
|
+
**you are the pane named by `$HERDR_PANE_ID`.** herdr injects this env var into every pane's shell, and your cli commands inherit it. resolve it to the current public ids — pane, tab, and workspace — with one call:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
herdr pane get "$HERDR_PANE_ID"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
the response holds your `pane_id`, `tab_id`, and `workspace_id`. "current workspace" and "current tab" mean _these_ — the ones your agent pane lives in. when the task says to test in the current workspace/tab, split or create from these ids, not from whatever else is on screen.
|
|
55
|
+
|
|
56
|
+
do **not** use `focused` to find yourself. `focused:true` is whichever pane the user's herdr ui is looking at right now — often a different agent's pane entirely. when several agents run at once, multiple panes show `agent_status: working` and your own pane is usually `focused:false`. the only reliable self-signal is `$HERDR_PANE_ID`.
|
|
57
|
+
|
|
58
|
+
if `$HERDR_PANE_ID` is somehow unset, fall back to matching: compare each candidate pane's `cwd` to your working directory, and if still ambiguous, `pane read` each one and look for _this_ conversation's output on screen.
|
|
59
|
+
|
|
60
|
+
see every pane and its neighbors:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
herdr pane list
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
list workspaces:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
herdr workspace list
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## tab management
|
|
73
|
+
|
|
74
|
+
list tabs in the current workspace:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
herdr tab list --workspace 1
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
create a new tab:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
herdr tab create --workspace 1
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
without `--label`, the new tab keeps the default numbered tab name.
|
|
87
|
+
|
|
88
|
+
create and name it in one step:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
herdr tab create --workspace 1 --label "logs"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
rename it:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
herdr tab rename 1:2 "logs"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
focus it:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
herdr tab focus 1:2
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
close it:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
herdr tab close 1:2
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## read another pane
|
|
113
|
+
|
|
114
|
+
see what is on another pane's screen:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
herdr pane read 1-1 --source recent --lines 50
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- `--source visible` = current viewport
|
|
121
|
+
- `--source recent` = recent scrollback as rendered in the pane
|
|
122
|
+
- `--source recent-unwrapped` = recent terminal text with soft wraps joined back together
|
|
123
|
+
|
|
124
|
+
## split a pane and run a command
|
|
125
|
+
|
|
126
|
+
split your pane to the right and keep focus on your current pane:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
herdr pane split 1-2 --direction right --no-focus
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
that prints json with the new pane nested at `result.pane.pane_id`. parse that value, then run a command in that pane:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
NEW_PANE=$(herdr pane split 1-2 --direction right --no-focus | python3 -c 'import sys,json; print(json.load(sys.stdin)["result"]["pane"]["pane_id"])')
|
|
136
|
+
herdr pane run "$NEW_PANE" "npm run dev"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
split downward instead:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
herdr pane split 1-2 --direction down --no-focus
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## wait for output
|
|
146
|
+
|
|
147
|
+
block until specific text appears in a pane. useful for waiting on servers, builds, and tests.
|
|
148
|
+
|
|
149
|
+
for `--source recent`, matching uses unwrapped recent terminal text, so pane width and soft wrapping do not break matches. `pane read --source recent` still shows the pane as rendered. if you want to inspect the same transcript that the waiter matches, use `pane read --source recent-unwrapped`.
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
herdr wait output 1-3 --match "ready on port 3000" --timeout 30000
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
with regex:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
herdr wait output 1-3 --match "server.*ready" --regex --timeout 30000
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
if it times out, exit code is `1`.
|
|
162
|
+
|
|
163
|
+
## wait for an agent status
|
|
164
|
+
|
|
165
|
+
block until another agent reaches a specific status:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
herdr wait agent-status 1-1 --status done --timeout 60000
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
use this when you want the same `done` / `idle` distinction the UI shows.
|
|
172
|
+
|
|
173
|
+
## wait for an agent task to complete
|
|
174
|
+
|
|
175
|
+
`wait agent-status` is level-triggered: if the pane is already in the requested status, it returns immediately. so a stale `idle` or `done` from a previous task looks identical to a fresh completion. there is no native OR between `idle` and `done`, and no `--wait done` shortcut.
|
|
176
|
+
|
|
177
|
+
two helper scripts wrap this safely. resolve them relative to this `SKILL.md`:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
HERDR_CLI_SKILL_DIR=<directory containing this SKILL.md>
|
|
181
|
+
RUN_WAIT="$HERDR_CLI_SKILL_DIR/scripts/herdr-agent-run-and-wait"
|
|
182
|
+
WAIT_COMPLETE="$HERDR_CLI_SKILL_DIR/scripts/herdr-agent-wait-complete"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
both treat `idle` and `done` as completion, and `blocked` as needs-attention.
|
|
186
|
+
|
|
187
|
+
### send a new task and wait — `herdr-agent-run-and-wait`
|
|
188
|
+
|
|
189
|
+
use this whenever you are about to send a prompt. it records the pane's baseline status, sends the prompt, then waits for `working`, `idle`, `done`, or `blocked`:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
"$RUN_WAIT" 1-3 "review the test coverage in src/api/" --timeout 120000
|
|
193
|
+
herdr pane read 1-3 --source recent-unwrapped --lines 120
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
recording the baseline before sending is what makes it safe: it only counts a _new_ terminal status as completion, not a leftover one. if the task is so fast it returns to its previous status without the helper ever seeing `working`, the helper times out instead of guessing — read the pane and verify manually in that case.
|
|
197
|
+
|
|
198
|
+
### wait on an already-running task — `herdr-agent-wait-complete`
|
|
199
|
+
|
|
200
|
+
use this only when the task is already in flight and you did not start it through `run-and-wait`. it first waits briefly for `working` (so a pre-task `idle`/`done` is not mistaken for completion), then races `idle` / `done` / `blocked`:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
"$WAIT_COMPLETE" 1-3 --timeout 120000
|
|
204
|
+
herdr pane read 1-3 --source recent-unwrapped --lines 120
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
if the task already finished before this helper starts, it can fail because it never sees `working`. it can run for up to `--start-timeout + --timeout` wall-clock time.
|
|
208
|
+
|
|
209
|
+
if you are sure the task is running and only want to race the terminal statuses, skip the working check:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
"$WAIT_COMPLETE" 1-3 --no-wait-working --timeout 120000
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
`--no-wait-working` is unsafe for fresh tasks: it can treat an existing `idle` or `done` as completion. prefer `run-and-wait` for new prompts.
|
|
216
|
+
|
|
217
|
+
### exit codes (both helpers)
|
|
218
|
+
|
|
219
|
+
- `0` — completed as `idle` or `done`.
|
|
220
|
+
- `1` — failed or timed out.
|
|
221
|
+
- `2` — reached `blocked`; read the pane and respond instead of waiting longer.
|
|
222
|
+
|
|
223
|
+
on timeout, inspect in this order:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
herdr pane get 1-3
|
|
227
|
+
herdr pane read 1-3 --source recent-unwrapped --lines 120
|
|
228
|
+
herdr pane list
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
run one task at a time per agent pane; queued tasks make status attribution ambiguous. for deterministic shell commands, prefer `wait output` on the command's own output over these agent-status helpers.
|
|
232
|
+
|
|
233
|
+
## send text or keys to a pane
|
|
234
|
+
|
|
235
|
+
send text without pressing Enter:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
herdr pane send-text 1-1 "hello from claude"
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
press Enter or other keys:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
herdr pane send-keys 1-1 Enter
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
`send-keys` accepts only these named keys:
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
Enter Tab Esc Backspace Up Down Left Right C-c ctrl+c
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Lowercase spellings also work for the basic named keys. Single-character keys also
|
|
254
|
+
work. For keys not on the named-key list — notably **Shift+Tab / BackTab** (e.g.
|
|
255
|
+
to cycle Claude's permission mode) — send the raw escape with `send-text`:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
herdr pane send-text 1-1 $'\e[Z' # Shift+Tab (BackTab)
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
`pane run` sends the text and then a real `Enter` key in one request:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
herdr pane run 1-1 "echo hello"
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## workspace management
|
|
268
|
+
|
|
269
|
+
create a new workspace:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
herdr workspace create --cwd /path/to/project
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
without `--label`, the new workspace keeps the default cwd-based name.
|
|
276
|
+
|
|
277
|
+
create and name one in one step:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
herdr workspace create --cwd /path/to/project --label "api server"
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
create one without focusing it:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
herdr workspace create --no-focus
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
focus a workspace:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
herdr workspace focus 2
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
rename:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
herdr workspace rename 1 "api server"
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
close:
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
herdr workspace close 2
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## close a pane
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
herdr pane close 1-3
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## recipes
|
|
314
|
+
|
|
315
|
+
### run a server and wait until it is ready
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
NEW_PANE=$(herdr pane split 1-2 --direction right --no-focus | python3 -c 'import sys,json; print(json.load(sys.stdin)["result"]["pane"]["pane_id"])')
|
|
319
|
+
herdr pane run "$NEW_PANE" "npm run dev"
|
|
320
|
+
herdr wait output "$NEW_PANE" --match "ready" --timeout 30000
|
|
321
|
+
herdr pane read "$NEW_PANE" --source recent --lines 20
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### run tests in a separate pane and inspect the result
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
herdr pane split 1-2 --direction down --no-focus
|
|
328
|
+
herdr pane run 1-3 "cargo test"
|
|
329
|
+
herdr wait output 1-3 --match "test result" --timeout 60000
|
|
330
|
+
herdr pane read 1-3 --source recent --lines 30
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### check what another agent is working on
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
herdr pane list
|
|
337
|
+
herdr pane read 1-1 --source recent --lines 80
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### watch another pane robustly
|
|
341
|
+
|
|
342
|
+
use this pattern when you need to coordinate with a sibling pane:
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
# inspect what is already there
|
|
346
|
+
herdr pane read 1-3 --source recent --lines 40
|
|
347
|
+
|
|
348
|
+
# wait only for the next output you expect
|
|
349
|
+
herdr wait output 1-3 --match "ready" --timeout 30000
|
|
350
|
+
|
|
351
|
+
# if you need to inspect the same transcript the waiter matched,
|
|
352
|
+
# read the unwrapped recent text directly
|
|
353
|
+
herdr pane read 1-3 --source recent-unwrapped --lines 40
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### spawn a new agent and give it a task
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
herdr pane split 1-2 --direction right --no-focus
|
|
360
|
+
herdr pane run 1-3 "claude"
|
|
361
|
+
herdr wait output 1-3 --match ">" --timeout 15000
|
|
362
|
+
"$RUN_WAIT" 1-3 "review the test coverage in src/api/" --timeout 120000
|
|
363
|
+
herdr pane read 1-3 --source recent-unwrapped --lines 120
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
see [wait for an agent task to complete](./scripts/herdr-agent-wait-complete) for how `$RUN_WAIT` is resolved and why it is safer than a bare `wait agent-status`.
|
|
367
|
+
|
|
368
|
+
### coordinate with another agent
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
herdr wait agent-status 1-1 --status done --timeout 120000
|
|
372
|
+
herdr pane read 1-1 --source recent --lines 100
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## notes
|
|
376
|
+
|
|
377
|
+
- `workspace list`, `workspace create`, `tab list`, `tab create`, `tab get`, `tab focus`, `tab rename`, `tab close`, `pane list`, `pane get`, `pane split`, `wait output`, and `wait agent-status` print json on success.
|
|
378
|
+
- `pane read` prints text, not json.
|
|
379
|
+
- `pane read --format ansi` or `pane read --ansi` returns a rendered ANSI snapshot for TUI feedback loops.
|
|
380
|
+
- `pane read --source recent-unwrapped` is useful when you want to inspect the same unwrapped transcript that `wait output --source recent` matches against.
|
|
381
|
+
- `pane send-text`, `pane send-keys`, and `pane run` print nothing on success.
|
|
382
|
+
- the `scripts/herdr-agent-run-and-wait` and `scripts/herdr-agent-wait-complete` helpers (resolved relative to this `SKILL.md`) print json and wrap `wait agent-status` so a stale `idle` / `done` is not mistaken for a fresh completion. use `run-and-wait` when sending a new task, `wait-complete` only for a task already in flight.
|
|
383
|
+
- parse ids from `workspace create`, `tab create`, and `pane split` responses when you need new ids. `workspace create` returns `result.workspace`, `result.tab`, and `result.root_pane`. `tab create` returns `result.tab` and `result.root_pane`. for `pane split`, the new pane id is at `result.pane.pane_id`.
|
|
384
|
+
- use `pane read` for current output that already exists. use `wait output` for future output you expect next.
|
|
385
|
+
- `--no-focus` on split, tab create, and workspace create keeps your current terminal context focused.
|
|
386
|
+
- without `--label`, workspace create keeps cwd-based naming and tab create keeps numbered naming.
|
|
387
|
+
- `--label` on tab create and workspace create applies the custom name immediately.
|
|
388
|
+
- if you are running inside herdr, the `HERDR_ENV` environment variable is set to `1`.
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import argparse
|
|
3
|
+
import json
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
COMPLETION_STATUSES = ("idle", "done")
|
|
10
|
+
ATTENTION_STATUSES = ("blocked",)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def run_json(args):
|
|
14
|
+
result = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
15
|
+
if result.returncode != 0:
|
|
16
|
+
return None, result
|
|
17
|
+
try:
|
|
18
|
+
return json.loads(result.stdout), result
|
|
19
|
+
except json.JSONDecodeError:
|
|
20
|
+
return None, result
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def pane_status(pane_id):
|
|
24
|
+
data, result = run_json(["herdr", "pane", "get", pane_id])
|
|
25
|
+
if data is None:
|
|
26
|
+
return None, result
|
|
27
|
+
return data["result"]["pane"].get("agent_status"), result
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def popen(args):
|
|
31
|
+
return subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def wait_status(pane_id, status, timeout_ms):
|
|
35
|
+
return popen(["herdr", "wait", "agent-status", pane_id, "--status", status, "--timeout", str(timeout_ms)])
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def finish_process(proc):
|
|
39
|
+
stdout, stderr = proc.communicate()
|
|
40
|
+
return stdout.strip(), stderr.strip()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def stop_processes(procs):
|
|
44
|
+
for proc in procs:
|
|
45
|
+
if proc.poll() is None:
|
|
46
|
+
proc.terminate()
|
|
47
|
+
try:
|
|
48
|
+
proc.wait(timeout=2)
|
|
49
|
+
except subprocess.TimeoutExpired:
|
|
50
|
+
proc.kill()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def parse_event(stdout):
|
|
54
|
+
if not stdout:
|
|
55
|
+
return None
|
|
56
|
+
try:
|
|
57
|
+
return json.loads(stdout)
|
|
58
|
+
except json.JSONDecodeError:
|
|
59
|
+
return stdout
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def emit(payload, stream=sys.stdout):
|
|
63
|
+
print(json.dumps(payload), file=stream)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def remaining_timeout_ms(deadline):
|
|
67
|
+
return max(1, int((deadline - time.time()) * 1000))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def completion_statuses_to_watch(baseline_status, saw_working):
|
|
71
|
+
if saw_working:
|
|
72
|
+
return COMPLETION_STATUSES
|
|
73
|
+
return tuple(status for status in COMPLETION_STATUSES if status != baseline_status)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def build_waiters(pane_id, timeout_ms, baseline_status, saw_working):
|
|
77
|
+
statuses = []
|
|
78
|
+
if not saw_working:
|
|
79
|
+
statuses.append("working")
|
|
80
|
+
statuses.extend(completion_statuses_to_watch(baseline_status, saw_working))
|
|
81
|
+
statuses.extend(ATTENTION_STATUSES)
|
|
82
|
+
return {status: wait_status(pane_id, status, timeout_ms) for status in statuses}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def add_completion_waiters(waiters, pane_id, deadline):
|
|
86
|
+
timeout_ms = remaining_timeout_ms(deadline)
|
|
87
|
+
for status in COMPLETION_STATUSES:
|
|
88
|
+
if status not in waiters:
|
|
89
|
+
waiters[status] = wait_status(pane_id, status, timeout_ms)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def main():
|
|
93
|
+
parser = argparse.ArgumentParser(
|
|
94
|
+
description="Run a prompt in a herdr agent pane and wait for completion."
|
|
95
|
+
)
|
|
96
|
+
parser.add_argument("pane_id")
|
|
97
|
+
parser.add_argument("prompt")
|
|
98
|
+
parser.add_argument("--timeout", type=int, default=120000, help="total wait timeout in ms")
|
|
99
|
+
args = parser.parse_args()
|
|
100
|
+
|
|
101
|
+
baseline_status, baseline_result = pane_status(args.pane_id)
|
|
102
|
+
if baseline_status is None:
|
|
103
|
+
emit(
|
|
104
|
+
{
|
|
105
|
+
"error": "could not read baseline pane status",
|
|
106
|
+
"pane_id": args.pane_id,
|
|
107
|
+
"pane_get_stderr": baseline_result.stderr.strip() if baseline_result else None,
|
|
108
|
+
},
|
|
109
|
+
stream=sys.stderr,
|
|
110
|
+
)
|
|
111
|
+
return 1
|
|
112
|
+
|
|
113
|
+
deadline = time.time() + (args.timeout / 1000)
|
|
114
|
+
saw_working = baseline_status == "working"
|
|
115
|
+
waiters = build_waiters(args.pane_id, args.timeout, baseline_status, saw_working)
|
|
116
|
+
|
|
117
|
+
run_result = subprocess.run(
|
|
118
|
+
["herdr", "pane", "run", args.pane_id, args.prompt],
|
|
119
|
+
stdout=subprocess.PIPE,
|
|
120
|
+
stderr=subprocess.PIPE,
|
|
121
|
+
text=True,
|
|
122
|
+
)
|
|
123
|
+
if run_result.returncode != 0:
|
|
124
|
+
stop_processes(list(waiters.values()))
|
|
125
|
+
emit(
|
|
126
|
+
{
|
|
127
|
+
"error": "pane run failed",
|
|
128
|
+
"pane_id": args.pane_id,
|
|
129
|
+
"baseline_status": baseline_status,
|
|
130
|
+
"pane_run_stderr": run_result.stderr.strip(),
|
|
131
|
+
"pane_run_stdout": run_result.stdout.strip(),
|
|
132
|
+
},
|
|
133
|
+
stream=sys.stderr,
|
|
134
|
+
)
|
|
135
|
+
return 1
|
|
136
|
+
|
|
137
|
+
while time.time() < deadline:
|
|
138
|
+
for status, proc in list(waiters.items()):
|
|
139
|
+
if proc.poll() is None:
|
|
140
|
+
continue
|
|
141
|
+
stdout, stderr = finish_process(proc)
|
|
142
|
+
if proc.returncode != 0:
|
|
143
|
+
waiters.pop(status, None)
|
|
144
|
+
continue
|
|
145
|
+
event = parse_event(stdout)
|
|
146
|
+
|
|
147
|
+
if status == "working":
|
|
148
|
+
saw_working = True
|
|
149
|
+
waiters.pop(status)
|
|
150
|
+
add_completion_waiters(waiters, args.pane_id, deadline)
|
|
151
|
+
break
|
|
152
|
+
|
|
153
|
+
if status in ATTENTION_STATUSES:
|
|
154
|
+
stop_processes(list(waiters.values()))
|
|
155
|
+
emit(
|
|
156
|
+
{
|
|
157
|
+
"completed_status": None,
|
|
158
|
+
"status": status,
|
|
159
|
+
"phase": "attention",
|
|
160
|
+
"pane_id": args.pane_id,
|
|
161
|
+
"baseline_status": baseline_status,
|
|
162
|
+
"saw_working": saw_working,
|
|
163
|
+
"event": event,
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
return 2
|
|
167
|
+
|
|
168
|
+
if saw_working or status != baseline_status:
|
|
169
|
+
stop_processes(list(waiters.values()))
|
|
170
|
+
emit(
|
|
171
|
+
{
|
|
172
|
+
"completed_status": status,
|
|
173
|
+
"status": status,
|
|
174
|
+
"phase": "completion",
|
|
175
|
+
"pane_id": args.pane_id,
|
|
176
|
+
"baseline_status": baseline_status,
|
|
177
|
+
"saw_working": saw_working,
|
|
178
|
+
"evidence": "working_then_terminal" if saw_working else "terminal_status_changed",
|
|
179
|
+
"event": event,
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
return 0
|
|
183
|
+
|
|
184
|
+
time.sleep(0.05)
|
|
185
|
+
|
|
186
|
+
stop_processes(list(waiters.values()))
|
|
187
|
+
status, result = pane_status(args.pane_id)
|
|
188
|
+
emit(
|
|
189
|
+
{
|
|
190
|
+
"error": "timed out waiting for a new completion signal",
|
|
191
|
+
"pane_id": args.pane_id,
|
|
192
|
+
"baseline_status": baseline_status,
|
|
193
|
+
"last_status": status,
|
|
194
|
+
"saw_working": saw_working,
|
|
195
|
+
"pane_get_stderr": result.stderr.strip() if result else None,
|
|
196
|
+
},
|
|
197
|
+
stream=sys.stderr,
|
|
198
|
+
)
|
|
199
|
+
return 1
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
if __name__ == "__main__":
|
|
203
|
+
raise SystemExit(main())
|