@generativereality/agentherder 0.1.6 → 0.1.7

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agentherder",
3
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.6",
4
+ "version": "0.1.7",
5
5
  "author": {
6
6
  "name": "generativereality",
7
7
  "url": "https://agentherder.com"
package/dist/index.js CHANGED
@@ -4,13 +4,13 @@ import { cli, define } from "gunshi";
4
4
  import { createConnection } from "net";
5
5
  import { execFileSync, spawnSync } from "child_process";
6
6
  import { randomUUID } from "crypto";
7
- import { homedir } from "os";
7
+ import { homedir, tmpdir } from "os";
8
8
  import { basename, dirname, extname, join, resolve } from "path";
9
9
  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.6";
13
+ var version = "0.1.7";
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,
@@ -502,8 +502,21 @@ function ensureConfigExists() {
502
502
  }
503
503
  //#endregion
504
504
  //#region src/core/open-session.ts
505
+ /** Poll scrollback until Claude's ❯ prompt is visible, then return */
506
+ async function waitForClaudePrompt(adapter, blockId, timeoutMs = 3e4) {
507
+ const POLL_INTERVAL = 1e3;
508
+ const deadline = Date.now() + timeoutMs;
509
+ while (Date.now() < deadline) {
510
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL));
511
+ try {
512
+ const lines = adapter.scrollback(blockId, 10);
513
+ if (lines && lines.includes("❯")) return;
514
+ } catch {}
515
+ }
516
+ consola.warn("Timed out waiting for Claude prompt — sending task anyway");
517
+ }
505
518
  async function openSession(opts) {
506
- const { tabName, claudeCmd, workspaceQuery } = opts;
519
+ const { tabName, claudeCmd, workspaceQuery, initialPromptFile } = opts;
507
520
  const dir = resolve(opts.dir.replace(/^~/, homedir()));
508
521
  if (!existsSync(dir)) {
509
522
  consola.error(`Directory does not exist: ${dir}`);
@@ -539,6 +552,12 @@ async function openSession(opts) {
539
552
  const extraFlags = config.claude.flags.join(" ");
540
553
  const cmd = `cd ${JSON.stringify(dir)} && ${claudeCmd} --name ${JSON.stringify(tabName)}${extraFlags ? " " + extraFlags : ""}\n`;
541
554
  await adapter.sendInput(blockId, cmd);
555
+ if (initialPromptFile) {
556
+ await waitForClaudePrompt(adapter, blockId);
557
+ const prompt = readFileSync(initialPromptFile, "utf-8");
558
+ const text = prompt.endsWith("\n") ? prompt : prompt + "\n";
559
+ await adapter.sendInput(blockId, text);
560
+ }
542
561
  await new Promise((r) => setTimeout(r, 2e3));
543
562
  adapter.closeSocket();
544
563
  return tabId;
@@ -566,6 +585,16 @@ const newCommand = define({
566
585
  type: "boolean",
567
586
  short: "W",
568
587
  description: "Launch claude with --worktree <name> for isolated branch work"
588
+ },
589
+ file: {
590
+ type: "string",
591
+ short: "f",
592
+ description: "Send initial prompt from file once Claude is ready"
593
+ },
594
+ prompt: {
595
+ type: "string",
596
+ short: "p",
597
+ description: "Send initial prompt text once Claude is ready"
569
598
  }
570
599
  },
571
600
  async run(ctx) {
@@ -573,15 +602,23 @@ const newCommand = define({
573
602
  const dir = ctx.positionals[2] ?? process.cwd();
574
603
  const workspace = ctx.values.workspace;
575
604
  const useWorktree = ctx.values.worktree ?? false;
605
+ const promptFile = ctx.values.file;
606
+ const promptText = ctx.values.prompt;
576
607
  if (!name) {
577
608
  consola.error("Tab name is required");
578
609
  process.exit(1);
579
610
  }
611
+ let initialPromptFile;
612
+ if (promptText) {
613
+ initialPromptFile = join(tmpdir(), `herd-prompt-${Date.now()}.txt`);
614
+ writeFileSync(initialPromptFile, promptText);
615
+ } else if (promptFile) initialPromptFile = promptFile;
580
616
  const tabId = await openSession({
581
617
  tabName: name,
582
618
  dir,
583
619
  claudeCmd: useWorktree ? `claude --worktree ${JSON.stringify(name)}` : "claude",
584
- workspaceQuery: workspace
620
+ workspaceQuery: workspace,
621
+ initialPromptFile
585
622
  });
586
623
  const suffix = useWorktree ? ` (worktree: .claude/worktrees/${name})` : "";
587
624
  consola.success(`Tab "${name}" [${tabId.slice(0, 8)}] → claude at ${dir}${suffix}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@generativereality/agentherder",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Agent session manager for AI coding tools. Terminal tabs as the UI, no tmux.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,7 +30,7 @@ Each session runs in its own terminal tab. `herd` lets you — and other Claude
30
30
  ```bash
31
31
  herd sessions # list all tabs with session status
32
32
  herd list # list all workspaces, tabs, and blocks
33
- herd new <name> [dir] [-w workspace] # new tab + claude
33
+ herd new <name> [dir] [-w workspace] [-p "prompt"] [-f file] # new tab + claude
34
34
  herd resume <name> [dir] # new tab + claude --continue
35
35
  herd fork <tab-name> [-n new-name] # fork a session into a new tab
36
36
  herd close <name-or-id> # close a tab
@@ -103,28 +103,25 @@ use `herd resume <name> <dir>` afterwards to open the resulting session in a new
103
103
 
104
104
  As a Claude Code session, you can spawn a sibling session to work on a parallel task:
105
105
 
106
- ```bash
107
- herd new payments ~/Dev/myapp
108
- ```
109
-
110
- **CRITICAL: Wait for Claude to be ready before sending tasks.** After `herd new` returns, Claude is still starting up (loading MCP servers, showing the initial prompt). Sending immediately causes the task to arrive as raw shell commands, not as a Claude prompt.
111
-
112
- Poll `herd scrollback` until you see the Claude prompt (the `❯` line with no pending output):
106
+ **Preferred: pass the initial task directly to `herd new`** using `--prompt` or `--file`. This polls internally until Claude's `❯` prompt appears before sending — no race condition:
113
107
 
114
108
  ```bash
115
- # Poll until Claude prompt appears (look for the prompt line)
116
- herd scrollback payments 5
117
- # Repeat every few seconds until you see Claude's prompt — typically 10-15s
109
+ herd new payments ~/Dev/myapp --prompt "implement the billing endpoint"
110
+ herd new payments ~/Dev/myapp --file /tmp/task.txt
118
111
  ```
119
112
 
120
- Once Claude is ready, send the task:
113
+ If you need to send a task after the fact, poll first:
121
114
 
122
115
  ```bash
123
- herd send payments --file /tmp/task.txt # send a prompt from a file
124
- echo "implement the billing endpoint" | herd send payments # or via stdin
125
- herd send payments "yes\n" # or inline for quick replies
116
+ herd new payments ~/Dev/myapp
117
+ # Poll until appears (typically 10-15s with MCP servers)
118
+ herd scrollback payments 5 # repeat until you see
119
+ herd send payments --file /tmp/task.txt
120
+ herd send payments "yes\n" # quick replies
126
121
  ```
127
122
 
123
+ **Do NOT call `herd send` immediately after `herd new`** — Claude is still starting up and the text will land as raw shell commands.
124
+
128
125
  ## Workflow: Monitoring Another Session
129
126
 
130
127
  ```bash