Skip to content

Commit 694c3a6

Browse files
committed
refactor: remove stdin support, use {prompt} placeholder only
1 parent a826bb5 commit 694c3a6

4 files changed

Lines changed: 16 additions & 28 deletions

File tree

README.md

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ issuebot --repo owner/repo --ntfy-topic my-alerts --command "copilot -p {prompt}
5353
# Log to a file for background operation
5454
issuebot --org myorg --log-file ~/.issuebot/issuebot.log --command "claude -p {prompt} --dangerously-skip-permissions"
5555

56-
# Use any command that reads from stdin
57-
issuebot --local --command "./my-issue-handler.sh"
56+
# Use a custom script
57+
issuebot --local --command "./my-issue-handler.sh {prompt}"
5858
```
5959

6060
### Flags
@@ -69,7 +69,7 @@ issuebot --local --command "./my-issue-handler.sh"
6969
| `--workers` | `5` | Max concurrent repo workers |
7070
| `--workspace` | `~/.issuebot/repos` | Directory for cloned repos |
7171
| `--local` | `false` | Use current directory instead of cloning |
72-
| `--command` | **(required)** | Command to run (prompt via stdin or `{prompt}` placeholder) |
72+
| `--command` | **(required)** | Command to run (`{prompt}` is replaced with the rendered prompt) |
7373
| `--prompt-file` | `~/.issuebot/prompt.tmpl` | Path to prompt template file |
7474
| `--dry-run` | `false` | Run command but skip push/PR (print diff instead) |
7575
| `--max-retries` | `3` | Max retry attempts per issue |
@@ -94,7 +94,7 @@ Each repo gets at most one concurrent worker to prevent conflicts. Failed issues
9494

9595
The prompt template controls what your command receives as input. On first run, issuebot writes a default template to `~/.issuebot/prompt.tmpl`. Use `--prompt-file` to specify a different path.
9696

97-
The template uses Go's [text/template](https://pkg.go.dev/text/template) syntax. The rendered output is passed to your `--command` via stdin (or substituted into `{prompt}`).
97+
The template uses Go's [text/template](https://pkg.go.dev/text/template) syntax. The rendered output replaces `{prompt}` in your `--command`.
9898

9999
**Default template:**
100100

@@ -133,7 +133,7 @@ The `ISSUE_FAILED` marker is important — if the command output contains it, is
133133

134134
## Command Interface
135135

136-
The `--command` flag is required. By default, the prompt is passed via stdin. If your tool needs it as an argument, use `{prompt}` as a placeholder (stdin is not used when `{prompt}` is present):
136+
The `--command` flag is required. Use `{prompt}` as a placeholder — issuebot substitutes it with the rendered prompt template:
137137

138138
```bash
139139
# Claude Code
@@ -144,9 +144,6 @@ issuebot --command "copilot -p {prompt} --yolo"
144144

145145
# Google Gemini CLI
146146
issuebot --command "gemini -p {prompt} --yolo"
147-
148-
# Any tool that reads stdin
149-
issuebot --command "./my-issue-handler.sh"
150147
```
151148

152149
## Per-Issue Configuration

cmd/watch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func init() {
4444
f.StringVar(&cfg.Workspace, "workspace", cfg.Workspace, "Workspace directory for repo clones")
4545
f.BoolVar(&cfg.Local, "local", cfg.Local, "Use current directory instead of cloning")
4646
f.BoolVar(&cfg.DryRun, "dry-run", cfg.DryRun, "Run command but skip push/PR (print diff instead)")
47-
f.StringVar(&cfg.Command, "command", cfg.Command, "Command to run (prompt via stdin or {prompt} placeholder)")
47+
f.StringVar(&cfg.Command, "command", cfg.Command, "Command to run ({prompt} is replaced with the rendered prompt)")
4848

4949
_ = rootCmd.MarkFlagRequired("command")
5050

internal/worker/worker.go

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -82,22 +82,12 @@ func (w *Worker) pullRepo(ctx context.Context, repoDir string) (string, error) {
8282
// If the command contains {prompt}, the prompt is substituted as an argument.
8383
// Otherwise, the prompt is passed via stdin.
8484
func (w *Worker) RunCommand(ctx context.Context, workDir, prompt string) (string, error) {
85-
cmdStr := w.CLIConfig.Command
86-
87-
// If {prompt} placeholder is present, substitute it and don't pipe stdin.
88-
useStdin := !strings.Contains(cmdStr, "{prompt}")
89-
if !useStdin {
90-
cmdStr = strings.ReplaceAll(cmdStr, "{prompt}", shellescape(prompt))
91-
}
85+
cmdStr := strings.ReplaceAll(w.CLIConfig.Command, "{prompt}", shellescape(prompt))
9286

9387
cmd := exec.CommandContext(ctx, "sh", "-c", cmdStr)
9488
cmd.Dir = workDir
9589
cmd.Stderr = os.Stderr
9690

97-
if useStdin {
98-
cmd.Stdin = strings.NewReader(prompt)
99-
}
100-
10191
out, err := cmd.Output()
10292
if err != nil {
10393
return "", fmt.Errorf("command exited with error: %w", err)

internal/worker/worker_test.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
const (
2323
testIssueTitle = "Test issue"
24+
testEchoCmd = "echo {prompt}"
2425
)
2526

2627
// mockCommand creates a shell script that echoes output and exits with the given code.
@@ -90,7 +91,7 @@ func testWorker(t *testing.T) *Worker {
9091
return &Worker{
9192
State: st,
9293
Notifier: notify.New(""),
93-
CLIConfig: config.CLIConfig{Strategy: "pr", MaxRetries: 3, Workspace: filepath.Join(dir, "repos"), PromptFile: promptFile, Command: "cat"},
94+
CLIConfig: config.CLIConfig{Strategy: "pr", MaxRetries: 3, Workspace: filepath.Join(dir, "repos"), PromptFile: promptFile, Command: testEchoCmd},
9495
}
9596
}
9697

@@ -155,24 +156,24 @@ func TestWorker_RunCommand_Failure(t *testing.T) {
155156
}
156157
}
157158

158-
func TestWorker_RunCommand_Stdin(t *testing.T) {
159+
func TestWorker_RunCommand_PromptSubstitution(t *testing.T) {
159160
w := testWorker(t)
160-
w.CLIConfig.Command = "cat"
161+
w.CLIConfig.Command = testEchoCmd
161162
dir := t.TempDir()
162163

163-
result, err := w.RunCommand(context.Background(), dir, "hello from stdin")
164+
result, err := w.RunCommand(context.Background(), dir, "hello world")
164165
if err != nil {
165166
t.Fatalf("unexpected error: %v", err)
166167
}
167168

168-
if result != "hello from stdin" {
169-
t.Errorf("result = %q, want %q", result, "hello from stdin")
169+
if result != "hello world" {
170+
t.Errorf("result = %q, want %q", result, "hello world")
170171
}
171172
}
172173

173174
func TestWorker_RunCommand_PromptPlaceholder(t *testing.T) {
174175
w := testWorker(t)
175-
w.CLIConfig.Command = "echo {prompt}"
176+
w.CLIConfig.Command = testEchoCmd
176177
dir := t.TempDir()
177178

178179
result, err := w.RunCommand(context.Background(), dir, "fix the bug")
@@ -187,7 +188,7 @@ func TestWorker_RunCommand_PromptPlaceholder(t *testing.T) {
187188

188189
func TestWorker_RunCommand_PromptPlaceholder_QuotesHandled(t *testing.T) {
189190
w := testWorker(t)
190-
w.CLIConfig.Command = "echo {prompt}"
191+
w.CLIConfig.Command = testEchoCmd
191192
dir := t.TempDir()
192193

193194
result, err := w.RunCommand(context.Background(), dir, "it's a test")

0 commit comments

Comments
 (0)