@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.
@@ -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.3";
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: ["dist", ".claude"],
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 Claude Code session ID for a directory */
622
- function findLatestSessionId(dir) {
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 jsonlFiles = readdirSync(projectDir).filter((f) => extname(f) === ".jsonl").map((f) => ({
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
- if (!jsonlFiles.length) return null;
631
- return basename(jsonlFiles[0].name, ".jsonl");
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 sourceDir = termBlocks[0].meta?.["cmd:cwd"] ?? process.cwd();
677
- const sessionId = findLatestSessionId(sourceDir);
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 ${sourceDir}`);
680
- consola.info(`Looked in ~/.claude/projects/${pathToProjectSlug(sourceDir)}/`);
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: sourceDir,
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",
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