mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-09 22:31:08 +00:00
Add stdin stream mode for the cli (#11476)
* Add stdin stream mode for the cli * fix: clear jsonEmitter state between tasks in stdin-prompt-stream mode * fix: use consistent user role for prompt echo partials in stream-json mode --------- Co-authored-by: Roo Code <roomote@roocode.com>
This commit is contained in:
parent
04ffb64bb7
commit
27a78833c8
5 changed files with 102 additions and 7 deletions
|
|
@ -100,6 +100,24 @@ In approval-required mode:
|
|||
- Tool, command, browser, and MCP actions prompt for yes/no approval
|
||||
- Followup questions wait for manual input (no auto-timeout)
|
||||
|
||||
### Print Mode (`--print`)
|
||||
|
||||
Use `--print` for non-interactive execution and machine-readable output:
|
||||
|
||||
```bash
|
||||
# Prompt is required
|
||||
roo --print "Summarize this repository"
|
||||
```
|
||||
|
||||
### Stdin Stream Mode (`--stdin-prompt-stream`)
|
||||
|
||||
For programmatic control (one process, multiple prompts), use `--stdin-prompt-stream` with `--print`.
|
||||
Send one prompt per line via stdin:
|
||||
|
||||
```bash
|
||||
printf '1+1=?\n10!=?\n' | roo --print --stdin-prompt-stream --output-format stream-json
|
||||
```
|
||||
|
||||
### Roo Code Cloud Authentication
|
||||
|
||||
To use Roo Code Cloud features (like the provider proxy), you need to authenticate:
|
||||
|
|
@ -152,6 +170,7 @@ Tokens are valid for 90 days. The CLI will prompt you to re-authenticate when yo
|
|||
| `--prompt-file <path>` | Read prompt from a file instead of command line argument | None |
|
||||
| `-w, --workspace <path>` | Workspace path to operate in | Current directory |
|
||||
| `-p, --print` | Print response and exit (non-interactive mode) | `false` |
|
||||
| `--stdin-prompt-stream` | Read prompts from stdin (one prompt per line, requires `--print`) | `false` |
|
||||
| `-e, --extension <path>` | Path to the extension bundle directory | Auto-detected |
|
||||
| `-d, --debug` | Enable debug output (includes detailed debug information, prompts, paths, etc) | `false` |
|
||||
| `-a, --require-approval` | Require manual approval before actions execute | `false` |
|
||||
|
|
|
|||
|
|
@ -93,6 +93,8 @@ export class JsonEventEmitter {
|
|||
private previousContent = new Map<number, string>()
|
||||
// Track the completion result content
|
||||
private completionResultContent: string | undefined
|
||||
// The first non-partial "say:text" per task is the echoed user prompt.
|
||||
private expectPromptEchoAsUser = true
|
||||
|
||||
constructor(options: JsonEventEmitterOptions) {
|
||||
this.mode = options.mode
|
||||
|
|
@ -227,7 +229,14 @@ export class JsonEventEmitter {
|
|||
private handleSayMessage(msg: ClineMessage, contentToSend: string | null, isDone: boolean): void {
|
||||
switch (msg.say) {
|
||||
case "text":
|
||||
this.emitEvent(this.buildTextEvent("assistant", msg.ts, contentToSend, isDone))
|
||||
if (this.expectPromptEchoAsUser) {
|
||||
this.emitEvent(this.buildTextEvent("user", msg.ts, contentToSend, isDone))
|
||||
if (isDone) {
|
||||
this.expectPromptEchoAsUser = false
|
||||
}
|
||||
} else {
|
||||
this.emitEvent(this.buildTextEvent("assistant", msg.ts, contentToSend, isDone))
|
||||
}
|
||||
break
|
||||
|
||||
case "reasoning":
|
||||
|
|
@ -378,6 +387,9 @@ export class JsonEventEmitter {
|
|||
if (this.mode === "json") {
|
||||
this.outputFinalResult(event.success, resultContent)
|
||||
}
|
||||
|
||||
// Next task in the same process starts with a new echoed prompt.
|
||||
this.expectPromptEchoAsUser = true
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -442,5 +454,6 @@ export class JsonEventEmitter {
|
|||
this.seenMessageIds.clear()
|
||||
this.previousContent.clear()
|
||||
this.completionResultContent = undefined
|
||||
this.expectPromptEchoAsUser = true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import fs from "fs"
|
||||
import path from "path"
|
||||
import { createInterface } from "readline"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
import { createElement } from "react"
|
||||
|
|
@ -30,6 +31,24 @@ import { ExtensionHost, ExtensionHostOptions } from "@/agent/index.js"
|
|||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
async function* readPromptsFromStdinLines(): AsyncGenerator<string> {
|
||||
const lineReader = createInterface({
|
||||
input: process.stdin,
|
||||
crlfDelay: Infinity,
|
||||
terminal: false,
|
||||
})
|
||||
|
||||
try {
|
||||
for await (const line of lineReader) {
|
||||
if (line.trim()) {
|
||||
yield line
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lineReader.close()
|
||||
}
|
||||
}
|
||||
|
||||
export async function run(promptArg: string | undefined, flagOptions: FlagOptions) {
|
||||
setLogger({
|
||||
info: () => {},
|
||||
|
|
@ -185,15 +204,42 @@ export async function run(promptArg: string | undefined, flagOptions: FlagOption
|
|||
// Output format only works with --print mode
|
||||
if (outputFormat !== "text" && !flagOptions.print && isTuiSupported) {
|
||||
console.error("[CLI] Error: --output-format requires --print mode")
|
||||
console.error("[CLI] Usage: roo <prompt> --print --output-format json")
|
||||
console.error("[CLI] Usage: roo --print --output-format json")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (flagOptions.stdinPromptStream && !flagOptions.print) {
|
||||
console.error("[CLI] Error: --stdin-prompt-stream requires --print mode")
|
||||
console.error("[CLI] Usage: roo --print --stdin-prompt-stream [options]")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (flagOptions.stdinPromptStream && process.stdin.isTTY) {
|
||||
console.error("[CLI] Error: --stdin-prompt-stream requires piped stdin")
|
||||
console.error("[CLI] Example: printf '1+1=?\\n10!=?\\n' | roo --print --stdin-prompt-stream [options]")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (flagOptions.stdinPromptStream && prompt) {
|
||||
console.error("[CLI] Error: cannot use positional prompt or --prompt-file with --stdin-prompt-stream")
|
||||
console.error("[CLI] Usage: roo --print --stdin-prompt-stream [options]")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const useStdinPromptStream = flagOptions.stdinPromptStream
|
||||
|
||||
if (!isTuiEnabled) {
|
||||
if (!prompt) {
|
||||
console.error("[CLI] Error: prompt is required in print mode")
|
||||
console.error("[CLI] Usage: roo <prompt> --print [options]")
|
||||
console.error("[CLI] Run without -p for interactive mode")
|
||||
if (!prompt && !useStdinPromptStream) {
|
||||
if (flagOptions.print) {
|
||||
console.error("[CLI] Error: no prompt provided")
|
||||
console.error("[CLI] Usage: roo --print [options] <prompt>")
|
||||
console.error("[CLI] For stdin control mode: roo --print --stdin-prompt-stream [options]")
|
||||
} else {
|
||||
console.error("[CLI] Error: prompt is required in non-interactive mode")
|
||||
console.error("[CLI] Usage: roo <prompt> [options]")
|
||||
console.error("[CLI] Run without -p for interactive mode")
|
||||
}
|
||||
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
|
|
@ -258,7 +304,22 @@ export async function run(promptArg: string | undefined, flagOptions: FlagOption
|
|||
jsonEmitter.attachToClient(host.client)
|
||||
}
|
||||
|
||||
await host.runTask(prompt!)
|
||||
if (useStdinPromptStream) {
|
||||
let hasReceivedStdinPrompt = false
|
||||
|
||||
for await (const stdinPrompt of readPromptsFromStdinLines()) {
|
||||
hasReceivedStdinPrompt = true
|
||||
await host.runTask(stdinPrompt)
|
||||
jsonEmitter?.clear()
|
||||
}
|
||||
|
||||
if (!hasReceivedStdinPrompt) {
|
||||
throw new Error("no prompt provided via stdin")
|
||||
}
|
||||
} else {
|
||||
await host.runTask(prompt!)
|
||||
}
|
||||
|
||||
jsonEmitter?.detach()
|
||||
await host.dispose()
|
||||
process.exit(0)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ program
|
|||
.option("--prompt-file <path>", "Read prompt from a file instead of command line argument")
|
||||
.option("-w, --workspace <path>", "Workspace directory path (defaults to current working directory)")
|
||||
.option("-p, --print", "Print response and exit (non-interactive mode)", false)
|
||||
.option("--stdin-prompt-stream", "Read prompts from stdin (one prompt per line, requires --print)", false)
|
||||
.option("-e, --extension <path>", "Path to the extension bundle directory")
|
||||
.option("-d, --debug", "Enable debug output (includes detailed debug information)", false)
|
||||
.option("-a, --require-approval", "Require manual approval for actions", false)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export type FlagOptions = {
|
|||
promptFile?: string
|
||||
workspace?: string
|
||||
print: boolean
|
||||
stdinPromptStream: boolean
|
||||
extension?: string
|
||||
debug: boolean
|
||||
requireApproval: boolean
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue