mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-05 08:10:14 +00:00
Claude-like cli flags, auth fixes
This commit is contained in:
parent
87a5afa629
commit
980681eccb
7 changed files with 77 additions and 85 deletions
|
|
@ -17,7 +17,7 @@
|
|||
"build:extension": "pnpm --filter roo-cline bundle",
|
||||
"build:all": "pnpm --filter roo-cline bundle && tsup",
|
||||
"dev": "tsup --watch",
|
||||
"start": "ROO_SDK_BASE_URL=http://localhost:3001 ROO_AUTH_BASE_URL=http://localhost:3000 node dist/index.js",
|
||||
"start": "ROO_AUTH_BASE_URL=http://localhost:3000 ROO_SDK_BASE_URL=http://localhost:3001 ROO_CODE_PROVIDER_URL=http://localhost:8080/proxy node dist/index.js",
|
||||
"start:production": "node dist/index.js",
|
||||
"release": "scripts/release.sh",
|
||||
"clean": "rimraf dist .turbo"
|
||||
|
|
|
|||
|
|
@ -11,12 +11,15 @@ export interface LoginOptions {
|
|||
verbose?: boolean
|
||||
}
|
||||
|
||||
export interface LoginResult {
|
||||
success: boolean
|
||||
error?: string
|
||||
userId?: string
|
||||
orgId?: string | null
|
||||
}
|
||||
export type LoginResult =
|
||||
| {
|
||||
success: true
|
||||
token: string
|
||||
}
|
||||
| {
|
||||
success: false
|
||||
error: string
|
||||
}
|
||||
|
||||
const LOCALHOST = "127.0.0.1"
|
||||
|
||||
|
|
@ -29,49 +32,57 @@ export async function login({ timeout = 5 * 60 * 1000, verbose = false }: LoginO
|
|||
console.log(`[Auth] Starting local callback server on port ${port}`)
|
||||
}
|
||||
|
||||
const corsHeaders = {
|
||||
"Access-Control-Allow-Origin": AUTH_BASE_URL,
|
||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
||||
"Access-Control-Allow-Headers": "Content-Type",
|
||||
}
|
||||
|
||||
// Create promise that will be resolved when we receive the callback.
|
||||
const tokenPromise = new Promise<{ token: string; state: string }>((resolve, reject) => {
|
||||
const server = http.createServer((req, res) => {
|
||||
const url = new URL(req.url!, host)
|
||||
|
||||
if (url.pathname === "/callback") {
|
||||
// Handle CORS preflight request.
|
||||
if (req.method === "OPTIONS") {
|
||||
res.writeHead(204, corsHeaders)
|
||||
res.end()
|
||||
return
|
||||
}
|
||||
|
||||
if (url.pathname === "/callback" && req.method === "POST") {
|
||||
const receivedState = url.searchParams.get("state")
|
||||
const token = url.searchParams.get("token")
|
||||
const error = url.searchParams.get("error")
|
||||
|
||||
const sendJsonResponse = (status: number, body: object) => {
|
||||
res.writeHead(status, {
|
||||
...corsHeaders,
|
||||
"Content-Type": "application/json",
|
||||
})
|
||||
res.end(JSON.stringify(body))
|
||||
}
|
||||
|
||||
if (error) {
|
||||
const errorUrl = new URL(`${AUTH_BASE_URL}/cli/sign-in?error=error-in-callback`)
|
||||
errorUrl.searchParams.set("message", error)
|
||||
res.writeHead(302, { Location: errorUrl.toString() })
|
||||
res.end()
|
||||
// Wait for response to be fully sent before closing server and rejecting.
|
||||
// The 'close' event fires when the underlying connection is terminated,
|
||||
// ensuring the browser has received the redirect before we shut down.
|
||||
sendJsonResponse(400, { success: false, error })
|
||||
res.on("close", () => {
|
||||
server.close()
|
||||
reject(new Error(error))
|
||||
})
|
||||
} else if (!token) {
|
||||
const errorUrl = new URL(`${AUTH_BASE_URL}/cli/sign-in?error=missing-token`)
|
||||
errorUrl.searchParams.set("message", "Missing token in callback")
|
||||
res.writeHead(302, { Location: errorUrl.toString() })
|
||||
res.end()
|
||||
sendJsonResponse(400, { success: false, error: "Missing token in callback" })
|
||||
res.on("close", () => {
|
||||
server.close()
|
||||
reject(new Error("Missing token in callback"))
|
||||
})
|
||||
} else if (receivedState !== state) {
|
||||
const errorUrl = new URL(`${AUTH_BASE_URL}/cli/sign-in?error=invalid-state-parameter`)
|
||||
errorUrl.searchParams.set("message", "Invalid state parameter (possible CSRF attack)")
|
||||
res.writeHead(302, { Location: errorUrl.toString() })
|
||||
res.end()
|
||||
sendJsonResponse(400, { success: false, error: "Invalid state parameter" })
|
||||
res.on("close", () => {
|
||||
server.close()
|
||||
reject(new Error("Invalid state parameter"))
|
||||
})
|
||||
} else {
|
||||
res.writeHead(302, { Location: `${AUTH_BASE_URL}/cli/sign-in?success=true` })
|
||||
res.end()
|
||||
sendJsonResponse(200, { success: true })
|
||||
res.on("close", () => {
|
||||
server.close()
|
||||
resolve({ token, state: receivedState })
|
||||
|
|
@ -90,12 +101,7 @@ export async function login({ timeout = 5 * 60 * 1000, verbose = false }: LoginO
|
|||
reject(new Error("Authentication timed out"))
|
||||
}, timeout)
|
||||
|
||||
server.on("listening", () => {
|
||||
console.log(`[Auth] Callback server listening on port ${port}`)
|
||||
})
|
||||
|
||||
server.on("close", () => {
|
||||
console.log("[Auth] Callback server closed")
|
||||
clearTimeout(timeoutId)
|
||||
})
|
||||
})
|
||||
|
|
@ -121,7 +127,7 @@ export async function login({ timeout = 5 * 60 * 1000, verbose = false }: LoginO
|
|||
const { token } = await tokenPromise
|
||||
await saveToken(token)
|
||||
console.log("✓ Successfully authenticated!")
|
||||
return { success: true }
|
||||
return { success: true, token }
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(`✗ Authentication failed: ${message}`)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import { ExtensionHost, ExtensionHostOptions } from "@/agent/index.js"
|
|||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
export async function run(workspaceArg: string, flagOptions: FlagOptions) {
|
||||
export async function run(prompt: string | undefined, flagOptions: FlagOptions) {
|
||||
setLogger({
|
||||
info: () => {},
|
||||
warn: () => {},
|
||||
|
|
@ -39,8 +39,8 @@ export async function run(workspaceArg: string, flagOptions: FlagOptions) {
|
|||
// Options
|
||||
|
||||
const isTuiSupported = process.stdin.isTTY && process.stdout.isTTY
|
||||
const isTuiEnabled = flagOptions.tui && isTuiSupported
|
||||
const rooToken = await loadToken()
|
||||
const isTuiEnabled = !flagOptions.print && isTuiSupported
|
||||
let rooToken = await loadToken()
|
||||
|
||||
const extensionHostOptions: ExtensionHostOptions = {
|
||||
mode: flagOptions.mode || DEFAULT_FLAGS.mode,
|
||||
|
|
@ -48,12 +48,12 @@ export async function run(workspaceArg: string, flagOptions: FlagOptions) {
|
|||
user: null,
|
||||
provider: flagOptions.provider ?? (rooToken ? "roo" : "openrouter"),
|
||||
model: flagOptions.model || DEFAULT_FLAGS.model,
|
||||
workspacePath: path.resolve(workspaceArg),
|
||||
workspacePath: process.cwd(),
|
||||
extensionPath: path.resolve(flagOptions.extension || getDefaultExtensionPath(__dirname)),
|
||||
nonInteractive: flagOptions.yes,
|
||||
ephemeral: flagOptions.ephemeral,
|
||||
debug: flagOptions.debug,
|
||||
exitOnComplete: flagOptions.exitOnComplete,
|
||||
exitOnComplete: flagOptions.print,
|
||||
}
|
||||
|
||||
// Roo Code Cloud Authentication
|
||||
|
|
@ -62,8 +62,9 @@ export async function run(workspaceArg: string, flagOptions: FlagOptions) {
|
|||
let { onboardingProviderChoice } = await loadSettings()
|
||||
|
||||
if (!onboardingProviderChoice) {
|
||||
const result = await runOnboarding()
|
||||
onboardingProviderChoice = result.choice
|
||||
const { choice, token } = await runOnboarding()
|
||||
onboardingProviderChoice = choice
|
||||
rooToken = token ?? null
|
||||
}
|
||||
|
||||
if (onboardingProviderChoice === OnboardingProviderChoice.Roo) {
|
||||
|
|
@ -139,15 +140,15 @@ export async function run(workspaceArg: string, flagOptions: FlagOptions) {
|
|||
}
|
||||
|
||||
if (!isTuiEnabled) {
|
||||
if (!flagOptions.prompt) {
|
||||
console.error("[CLI] Error: prompt is required in plain text mode")
|
||||
console.error("[CLI] Usage: roo [workspace] -P <prompt> [options]")
|
||||
console.error("[CLI] Use TUI mode (without --no-tui) for interactive input")
|
||||
if (!prompt) {
|
||||
console.error("[CLI] Error: prompt is required in print mode")
|
||||
console.error("[CLI] Usage: roo -p <prompt> [options]")
|
||||
console.error("[CLI] Run without -p for interactive mode")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (flagOptions.tui) {
|
||||
console.warn("[CLI] TUI disabled (no TTY support), falling back to plain text mode")
|
||||
if (!flagOptions.print) {
|
||||
console.warn("[CLI] TUI disabled (no TTY support), falling back to print mode")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -161,7 +162,7 @@ export async function run(workspaceArg: string, flagOptions: FlagOptions) {
|
|||
render(
|
||||
createElement(App, {
|
||||
...extensionHostOptions,
|
||||
initialPrompt: flagOptions.prompt,
|
||||
initialPrompt: prompt,
|
||||
version: VERSION,
|
||||
createExtensionHost: (opts: ExtensionHostOptions) => new ExtensionHost(opts),
|
||||
}),
|
||||
|
|
@ -200,12 +201,9 @@ export async function run(workspaceArg: string, flagOptions: FlagOptions) {
|
|||
|
||||
try {
|
||||
await host.activate()
|
||||
await host.runTask(flagOptions.prompt!)
|
||||
await host.runTask(prompt!)
|
||||
await host.dispose()
|
||||
|
||||
if (!flagOptions.waitOnComplete) {
|
||||
process.exit(0)
|
||||
}
|
||||
process.exit(0)
|
||||
} catch (error) {
|
||||
console.error("[CLI] Error:", error instanceof Error ? error.message : String(error))
|
||||
|
||||
|
|
|
|||
|
|
@ -6,31 +6,27 @@ import { run, login, logout, status } from "@/commands/index.js"
|
|||
|
||||
const program = new Command()
|
||||
|
||||
program.name("roo").description("Roo Code CLI - Run the Roo Code agent from the command line").version(VERSION)
|
||||
program
|
||||
.name("roo")
|
||||
.description("Roo Code CLI - starts an interactive session by default, use -p/--print for non-interactive output")
|
||||
.version(VERSION)
|
||||
|
||||
program
|
||||
.argument("[workspace]", "Workspace path to operate in", process.cwd())
|
||||
.option("-P, --prompt <prompt>", "The prompt/task to execute (optional in TUI mode)")
|
||||
.argument("[prompt]", "Your prompt")
|
||||
.option("-p, --print", "Print response and exit (non-interactive mode)", false)
|
||||
.option("-e, --extension <path>", "Path to the extension bundle directory")
|
||||
.option("-d, --debug", "Enable debug output (includes detailed debug information)", false)
|
||||
.option("-y, --yes", "Auto-approve all prompts (non-interactive mode)", false)
|
||||
.option("-y, --yes", "Auto-approve all prompts", false)
|
||||
.option("-k, --api-key <key>", "API key for the LLM provider")
|
||||
.option("-p, --provider <provider>", "API provider (roo, anthropic, openai, openrouter, etc.)")
|
||||
.option("--provider <provider>", "API provider (roo, anthropic, openai, openrouter, etc.)")
|
||||
.option("-m, --model <model>", "Model to use", DEFAULT_FLAGS.model)
|
||||
.option("-M, --mode <mode>", "Mode to start in (code, architect, ask, debug, etc.)", DEFAULT_FLAGS.mode)
|
||||
.option("--mode <mode>", "Mode to start in (code, architect, ask, debug, etc.)", DEFAULT_FLAGS.mode)
|
||||
.option(
|
||||
"-r, --reasoning-effort <effort>",
|
||||
"Reasoning effort level (unspecified, disabled, none, minimal, low, medium, high, xhigh)",
|
||||
DEFAULT_FLAGS.reasoningEffort,
|
||||
)
|
||||
.option("-x, --exit-on-complete", "Exit the process when the task completes (applies to TUI mode only)", false)
|
||||
.option(
|
||||
"-w, --wait-on-complete",
|
||||
"Keep the process running when the task completes (applies to plain text mode only)",
|
||||
false,
|
||||
)
|
||||
.option("--ephemeral", "Run without persisting state (uses temporary storage)", false)
|
||||
.option("--no-tui", "Disable TUI, use plain text output")
|
||||
.action(run)
|
||||
|
||||
const authCommand = program.command("auth").description("Manage authentication for Roo Code Cloud")
|
||||
|
|
|
|||
|
|
@ -17,9 +17,14 @@ export async function runOnboarding(): Promise<OnboardingResult> {
|
|||
console.log("")
|
||||
|
||||
if (choice === OnboardingProviderChoice.Roo) {
|
||||
const { success: authenticated } = await login()
|
||||
const result = await login()
|
||||
await saveSettings({ onboardingProviderChoice: choice })
|
||||
resolve({ choice: OnboardingProviderChoice.Roo, authenticated, skipped: false })
|
||||
|
||||
resolve({
|
||||
choice: OnboardingProviderChoice.Roo,
|
||||
token: result.success ? result.token : undefined,
|
||||
skipped: false,
|
||||
})
|
||||
} else {
|
||||
console.log("Using your own API key.")
|
||||
console.log("Set your API key via --api-key or environment variable.")
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export function isSupportedProvider(provider: string): provider is SupportedProv
|
|||
export type ReasoningEffortFlagOptions = ReasoningEffortExtended | "unspecified" | "disabled"
|
||||
|
||||
export type FlagOptions = {
|
||||
prompt?: string
|
||||
print: boolean
|
||||
extension?: string
|
||||
debug: boolean
|
||||
yes: boolean
|
||||
|
|
@ -27,10 +27,7 @@ export type FlagOptions = {
|
|||
model?: string
|
||||
mode?: string
|
||||
reasoningEffort?: ReasoningEffortFlagOptions
|
||||
exitOnComplete: boolean
|
||||
waitOnComplete: boolean
|
||||
ephemeral: boolean
|
||||
tui: boolean
|
||||
}
|
||||
|
||||
export enum OnboardingProviderChoice {
|
||||
|
|
@ -40,7 +37,7 @@ export enum OnboardingProviderChoice {
|
|||
|
||||
export interface OnboardingResult {
|
||||
choice: OnboardingProviderChoice
|
||||
authenticated?: boolean
|
||||
token?: string
|
||||
skipped: boolean
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,37 +36,27 @@ export const runTaskWithCli = async ({ run, task, publish, logger, jobToken }: R
|
|||
const controller = new AbortController()
|
||||
const cancelSignal = controller.signal
|
||||
|
||||
const cliArgs = [
|
||||
"--filter",
|
||||
"@roo-code/cli",
|
||||
"start",
|
||||
"--yes",
|
||||
"--exit-on-complete",
|
||||
"--reasoning-effort",
|
||||
"disabled",
|
||||
"--workspace",
|
||||
workspacePath,
|
||||
]
|
||||
const cliArgs = ["--filter", "@roo-code/cli", "start", "--yes", "--print", "--reasoning-effort", "disabled"]
|
||||
|
||||
if (run.settings?.mode) {
|
||||
cliArgs.push("-M", run.settings.mode)
|
||||
cliArgs.push("--mode", run.settings.mode)
|
||||
}
|
||||
|
||||
if (run.settings?.apiProvider) {
|
||||
cliArgs.push("-p", run.settings.apiProvider)
|
||||
cliArgs.push("--provider", run.settings.apiProvider)
|
||||
}
|
||||
|
||||
const modelId = run.settings?.apiModelId || run.settings?.openRouterModelId
|
||||
|
||||
if (modelId) {
|
||||
cliArgs.push("-m", modelId)
|
||||
cliArgs.push("--model", modelId)
|
||||
}
|
||||
|
||||
cliArgs.push(prompt)
|
||||
|
||||
logger.info(`CLI command: pnpm ${cliArgs.join(" ")}`)
|
||||
|
||||
const subprocess = execa("pnpm", cliArgs, { env, cancelSignal, cwd: process.cwd() })
|
||||
const subprocess = execa("pnpm", cliArgs, { env, cancelSignal, cwd: workspacePath })
|
||||
|
||||
// Buffer for accumulating streaming output until we have complete lines.
|
||||
let stdoutBuffer = ""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue