@generativereality/agentherder 0.1.3 → 0.1.5
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/.claude-plugin/plugin.json +12 -0
- package/dist/index.js +62 -14
- package/package.json +4 -2
- package/skills/herd/SKILL.md +181 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agentherder",
|
|
3
|
+
"description": "Run a fleet of Claude Code sessions. Terminal tabs as the UI, no tmux. Claude can orchestrate its own sibling sessions.",
|
|
4
|
+
"version": "0.1.5",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "generativereality",
|
|
7
|
+
"url": "https://agentherder.com"
|
|
8
|
+
},
|
|
9
|
+
"repository": "https://github.com/generativereality/agentherder",
|
|
10
|
+
"homepage": "https://agentherder.com",
|
|
11
|
+
"license": "MIT"
|
|
12
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { consola } from "consola";
|
|
|
10
10
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
|
|
11
11
|
//#region package.json
|
|
12
12
|
var name = "@generativereality/agentherder";
|
|
13
|
-
var version = "0.1.
|
|
13
|
+
var version = "0.1.5";
|
|
14
14
|
var description = "Agent session manager for AI coding tools. Terminal tabs as the UI, no tmux.";
|
|
15
15
|
var package_default = {
|
|
16
16
|
name,
|
|
@@ -18,7 +18,12 @@ var package_default = {
|
|
|
18
18
|
description,
|
|
19
19
|
type: "module",
|
|
20
20
|
bin: { "herd": "dist/index.js" },
|
|
21
|
-
files: [
|
|
21
|
+
files: [
|
|
22
|
+
"dist",
|
|
23
|
+
".claude",
|
|
24
|
+
".claude-plugin",
|
|
25
|
+
"skills"
|
|
26
|
+
],
|
|
22
27
|
scripts: {
|
|
23
28
|
"dev": "bun run ./src/index.ts",
|
|
24
29
|
"build": "tsdown",
|
|
@@ -618,20 +623,63 @@ const resumeCommand = define({
|
|
|
618
623
|
function pathToProjectSlug(dir) {
|
|
619
624
|
return resolve(dir).replace(/\//g, "-");
|
|
620
625
|
}
|
|
621
|
-
/** Find the most recent
|
|
622
|
-
function
|
|
623
|
-
const slug = pathToProjectSlug(dir);
|
|
624
|
-
const projectDir = join(homedir(), ".claude", "projects", slug);
|
|
626
|
+
/** Find the most recent .jsonl session file in a Claude project directory */
|
|
627
|
+
function latestJsonlIn(projectDir) {
|
|
625
628
|
if (!existsSync(projectDir)) return null;
|
|
626
|
-
const
|
|
629
|
+
const files = readdirSync(projectDir).filter((f) => extname(f) === ".jsonl").map((f) => ({
|
|
627
630
|
name: f,
|
|
628
631
|
mtime: statSync(join(projectDir, f)).mtimeMs
|
|
629
632
|
})).sort((a, b) => b.mtime - a.mtime);
|
|
630
|
-
|
|
631
|
-
|
|
633
|
+
return files.length ? basename(files[0].name, ".jsonl") : null;
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Find the most recent Claude Code session ID for a directory.
|
|
637
|
+
* Also checks worktree subdirectories (.claude/worktrees/*) since tabs
|
|
638
|
+
* opened with --worktree run from a worktree path, not the repo root.
|
|
639
|
+
*/
|
|
640
|
+
function findLatestSessionId(dir) {
|
|
641
|
+
const projectsRoot = join(homedir(), ".claude", "projects");
|
|
642
|
+
const direct = latestJsonlIn(join(projectsRoot, pathToProjectSlug(dir)));
|
|
643
|
+
if (direct) return direct;
|
|
644
|
+
const worktreesDir = join(dir, ".claude", "worktrees");
|
|
645
|
+
if (existsSync(worktreesDir)) {
|
|
646
|
+
const candidates = [];
|
|
647
|
+
for (const entry of readdirSync(worktreesDir)) {
|
|
648
|
+
const projectDir = join(projectsRoot, pathToProjectSlug(join(worktreesDir, entry)));
|
|
649
|
+
const id = latestJsonlIn(projectDir);
|
|
650
|
+
if (id) {
|
|
651
|
+
const mtime = statSync(join(projectDir, id + ".jsonl")).mtimeMs;
|
|
652
|
+
candidates.push({
|
|
653
|
+
id,
|
|
654
|
+
mtime
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
if (candidates.length) {
|
|
659
|
+
candidates.sort((a, b) => b.mtime - a.mtime);
|
|
660
|
+
return candidates[0].id;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return null;
|
|
632
664
|
}
|
|
633
665
|
//#endregion
|
|
634
666
|
//#region src/commands/fork.ts
|
|
667
|
+
/** If dir is inside .claude/worktrees/<name>, return the repo root instead */
|
|
668
|
+
function resolveSessionDir(dir) {
|
|
669
|
+
const worktreeMarker = `${join(".claude", "worktrees")}/`;
|
|
670
|
+
const idx = dir.indexOf(worktreeMarker);
|
|
671
|
+
if (idx !== -1) {
|
|
672
|
+
const repoRoot = dir.slice(0, idx - 1);
|
|
673
|
+
return {
|
|
674
|
+
sessionLookupDir: repoRoot,
|
|
675
|
+
openDir: repoRoot
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
return {
|
|
679
|
+
sessionLookupDir: dir,
|
|
680
|
+
openDir: dir
|
|
681
|
+
};
|
|
682
|
+
}
|
|
635
683
|
const forkCommand = define({
|
|
636
684
|
name: "fork",
|
|
637
685
|
description: "Fork a session into a new tab (claude --resume <id> --fork-session)",
|
|
@@ -673,16 +721,16 @@ const forkCommand = define({
|
|
|
673
721
|
consola.error(`Tab "${tabName}" has no terminal block`);
|
|
674
722
|
process.exit(1);
|
|
675
723
|
}
|
|
676
|
-
const
|
|
677
|
-
const sessionId = findLatestSessionId(
|
|
724
|
+
const { sessionLookupDir, openDir } = resolveSessionDir(termBlocks[0].meta?.["cmd:cwd"] ?? process.cwd());
|
|
725
|
+
const sessionId = findLatestSessionId(sessionLookupDir);
|
|
678
726
|
if (!sessionId) {
|
|
679
|
-
consola.error(`No Claude session found for ${
|
|
680
|
-
consola.info(`Looked in ~/.claude/projects/${pathToProjectSlug(
|
|
727
|
+
consola.error(`No Claude session found for ${sessionLookupDir}`);
|
|
728
|
+
consola.info(`Looked in ~/.claude/projects/${pathToProjectSlug(sessionLookupDir)}/`);
|
|
681
729
|
process.exit(1);
|
|
682
730
|
}
|
|
683
731
|
const newTabId = await openSession({
|
|
684
732
|
tabName: newName,
|
|
685
|
-
dir:
|
|
733
|
+
dir: openDir,
|
|
686
734
|
claudeCmd: `claude --resume ${sessionId} --fork-session`
|
|
687
735
|
});
|
|
688
736
|
consola.success(`Forked "${tabName}" → "${newName}" [${newTabId.slice(0, 8)}]`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@generativereality/agentherder",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Agent session manager for AI coding tools. Terminal tabs as the UI, no tmux.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
|
-
".claude"
|
|
11
|
+
".claude",
|
|
12
|
+
".claude-plugin",
|
|
13
|
+
"skills"
|
|
12
14
|
],
|
|
13
15
|
"scripts": {
|
|
14
16
|
"dev": "bun run ./src/index.ts",
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: herd
|
|
3
|
+
description: Manage Claude Code sessions across terminal tabs — list running sessions, open new ones, fork, close, inspect output, and send input. Use this when working with multiple parallel Claude Code sessions.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are managing Claude Code sessions using the `herd` CLI (Agent Herder).
|
|
7
|
+
|
|
8
|
+
## First: Ensure herd is installed
|
|
9
|
+
|
|
10
|
+
Before running any `herd` command, verify the CLI is available:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
which herd
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
If that returns nothing, install it silently before proceeding:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g @generativereality/agentherder
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Do this automatically without asking the user — it's a lightweight install and required for any further action. After installing, confirm with `herd --version`.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
Each session runs in its own terminal tab. `herd` lets you — and other Claude Code sessions — introspect and orchestrate the full session fleet.
|
|
27
|
+
|
|
28
|
+
## Quick Reference
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
herd sessions # list all tabs with session status
|
|
32
|
+
herd list # list all workspaces, tabs, and blocks
|
|
33
|
+
herd new <name> [dir] [-w workspace] # new tab + claude
|
|
34
|
+
herd resume <name> [dir] # new tab + claude --continue
|
|
35
|
+
herd fork <tab-name> [-n new-name] # fork a session into a new tab
|
|
36
|
+
herd close <name-or-id> # close a tab
|
|
37
|
+
herd rename <name-or-id> <new-name> # rename a tab
|
|
38
|
+
herd scrollback <tab-or-block> [n] # read terminal output (default: 50 lines)
|
|
39
|
+
herd send <tab-or-block> [text] # send input — arg, --file, or stdin pipe
|
|
40
|
+
herd config # show config and path
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Workflow: Checking What's Running
|
|
44
|
+
|
|
45
|
+
Before starting new sessions, always check what's already active:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
herd sessions
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Output example:
|
|
52
|
+
```
|
|
53
|
+
Sessions
|
|
54
|
+
==================================================
|
|
55
|
+
|
|
56
|
+
Workspace: work (current)
|
|
57
|
+
|
|
58
|
+
[a1b2c3d4] "auth" ◄ ~/Dev/myapp
|
|
59
|
+
● active
|
|
60
|
+
[e5f6a7b8] "api" ~/Dev/myapp
|
|
61
|
+
○ idle
|
|
62
|
+
[c9d0e1f2] "infra" ~/Dev/myapp
|
|
63
|
+
terminal
|
|
64
|
+
last: $ git status
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Workflow: Opening a Session Batch
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
herd new auth ~/Dev/myapp
|
|
71
|
+
herd new api ~/Dev/myapp
|
|
72
|
+
herd new infra ~/Dev/myapp
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Each tab is automatically named and the claude session name is synced to the tab title.
|
|
76
|
+
|
|
77
|
+
## Workflow: Resuming After Restart
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
herd sessions # identify which tabs need resuming
|
|
81
|
+
herd resume auth ~/Dev/myapp
|
|
82
|
+
herd resume api ~/Dev/myapp
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Workflow: Forking a Session
|
|
86
|
+
|
|
87
|
+
Use `fork` when you want to try an alternative approach without disrupting the original:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
herd fork auth # creates "auth-fork" tab
|
|
91
|
+
herd fork auth -n "auth-v2" # creates "auth-v2" tab
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The forked session runs `claude --resume <session-id> --fork-session` — it shares context from the original but creates an independent new session.
|
|
95
|
+
|
|
96
|
+
## Workflow: Spawning a Parallel Agent
|
|
97
|
+
|
|
98
|
+
As a Claude Code session, you can spawn a sibling session to work on a parallel task:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
herd new payments ~/Dev/myapp
|
|
102
|
+
herd send payments --file /tmp/task.txt # send a prompt from a file
|
|
103
|
+
echo "implement the billing endpoint" | herd send payments # or via stdin
|
|
104
|
+
herd send payments "yes\n" # or inline for quick replies
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Workflow: Monitoring Another Session
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
herd scrollback auth # last 50 lines
|
|
111
|
+
herd scrollback auth 200 # last 200 lines
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Workflow: Sending Input to a Session
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
herd send auth "yes\n" # approve a tool call
|
|
118
|
+
herd send auth "\n" # press enter (confirm a prompt)
|
|
119
|
+
herd send auth "/clear\n" # send a slash command
|
|
120
|
+
herd send auth --file ~/prompts/task.txt # send a full prompt from file
|
|
121
|
+
echo "do the thing" | herd send auth # pipe via stdin
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Workflow: Worktrees
|
|
125
|
+
|
|
126
|
+
**Always point tabs at the repo root — never at a manually-created worktree directory.** Claude Code manages worktrees itself via `claude --worktree <name>`, which creates `.claude/worktrees/<name>/` inside the repo and handles branch creation and cleanup automatically.
|
|
127
|
+
|
|
128
|
+
### New isolated session (new branch, Claude manages everything)
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
herd new feature-name ~/Dev/myapp --worktree
|
|
132
|
+
# Equivalent to: cd ~/Dev/myapp && claude --worktree "feature-name" --name "feature-name"
|
|
133
|
+
# Claude creates: ~/Dev/myapp/.claude/worktrees/feature-name/
|
|
134
|
+
# Claude creates branch: worktree-feature-name
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Existing branch — ask Claude to enter the worktree mid-session
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
herd new hiring ~/Dev/myapp # open tab at repo root
|
|
141
|
+
herd send hiring "Enter a worktree for branch z.old/new-hire-ad and ..."
|
|
142
|
+
# Claude will use EnterWorktree tool to set up isolation
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Do NOT manage git worktrees manually
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# ❌ WRONG — do not create worktree dirs yourself and pass them to herd new
|
|
149
|
+
git worktree add ~/Dev/myapp-feature branch
|
|
150
|
+
herd new feature ~/Dev/myapp-feature
|
|
151
|
+
|
|
152
|
+
# ✅ RIGHT — always use repo root; let Claude Code manage the worktree
|
|
153
|
+
herd new feature ~/Dev/myapp --worktree
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Why:** Manually created worktree dirs placed outside the repo confuse Claude Code's session tracking, project memory lookup (`.claude/` is in the main repo), and CLAUDE.md resolution. Claude Code's built-in worktree support keeps everything co-located under `.claude/worktrees/` and handles cleanup on session exit.
|
|
157
|
+
|
|
158
|
+
## Workflow: Cleanup
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
herd sessions # find idle/terminal tabs
|
|
162
|
+
herd close old-feature # close by name (prefix match)
|
|
163
|
+
herd close e5f6a7b8 # close by block ID prefix
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Tab Naming Conventions
|
|
167
|
+
|
|
168
|
+
Name tabs after the **project or task**:
|
|
169
|
+
- `auth` — authentication work
|
|
170
|
+
- `api` — API service
|
|
171
|
+
- `infra` — infrastructure
|
|
172
|
+
- `pr-1234` — specific PR work
|
|
173
|
+
- `auth-v2` — forked attempt
|
|
174
|
+
|
|
175
|
+
## Notes
|
|
176
|
+
|
|
177
|
+
- Tab names are matched by exact name or prefix (case-insensitive)
|
|
178
|
+
- Block IDs can be abbreviated to the first 8 characters
|
|
179
|
+
- `herd new` and `herd resume` automatically pass `--name <tab-name>` to claude, syncing the session display name with the tab title
|
|
180
|
+
- Configured `claude.flags` in `~/.config/herd/config.toml` are applied to every session
|
|
181
|
+
- `herd send` resolves tab names to their terminal block automatically
|