mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
feat: add mode completion (#1506)
This commit is contained in:
@@ -75,6 +75,46 @@ impl GooseCompleter {
|
||||
Ok((line.len(), vec![]))
|
||||
}
|
||||
|
||||
/// Complete flags for the /mode command
|
||||
fn complete_mode_flags(&self, line: &str) -> Result<(usize, Vec<Pair>)> {
|
||||
let modes = ["auto", "approve", "chat"];
|
||||
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
|
||||
// If we're just after "/mode" with a space, show all options
|
||||
if line == "/mode " {
|
||||
return Ok((
|
||||
line.len(),
|
||||
modes
|
||||
.iter()
|
||||
.map(|mode| Pair {
|
||||
display: mode.to_string(),
|
||||
replacement: format!("{} ", mode),
|
||||
})
|
||||
.collect(),
|
||||
));
|
||||
}
|
||||
|
||||
// If we're typing a mode name, show the flags for that mode
|
||||
if parts.len() == 2 {
|
||||
let partial = parts[1].to_lowercase();
|
||||
return Ok((
|
||||
line.len() - partial.len(),
|
||||
modes
|
||||
.iter()
|
||||
.filter(|mode| mode.to_lowercase().starts_with(&partial.to_lowercase()))
|
||||
.map(|mode| Pair {
|
||||
display: mode.to_string(),
|
||||
replacement: format!("{} ", mode),
|
||||
})
|
||||
.collect(),
|
||||
));
|
||||
}
|
||||
|
||||
// No completions available
|
||||
Ok((line.len(), vec![]))
|
||||
}
|
||||
|
||||
/// Complete slash commands
|
||||
fn complete_slash_commands(&self, line: &str) -> Result<(usize, Vec<Pair>)> {
|
||||
// Define available slash commands
|
||||
@@ -88,6 +128,7 @@ impl GooseCompleter {
|
||||
"/builtin",
|
||||
"/prompts",
|
||||
"/prompt",
|
||||
"/mode",
|
||||
];
|
||||
|
||||
// Find commands that match the prefix
|
||||
@@ -292,6 +333,10 @@ impl Completer for GooseCompleter {
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if line.starts_with("/mode") {
|
||||
return self.complete_mode_flags(line);
|
||||
}
|
||||
}
|
||||
|
||||
// Default: no completions
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use super::completion::GooseCompleter;
|
||||
use anyhow::Result;
|
||||
use rustyline::Editor;
|
||||
use shlex;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::completion::GooseCompleter;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InputResult {
|
||||
Message(String),
|
||||
@@ -15,6 +14,7 @@ pub enum InputResult {
|
||||
Retry,
|
||||
ListPrompts(Option<String>),
|
||||
PromptCommand(PromptCommandOptions),
|
||||
GooseMode(String),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -65,6 +65,14 @@ pub fn get_input(
|
||||
fn handle_slash_command(input: &str) -> Option<InputResult> {
|
||||
let input = input.trim();
|
||||
|
||||
// Command prefix constants
|
||||
const CMD_PROMPTS: &str = "/prompts ";
|
||||
const CMD_PROMPT: &str = "/prompt";
|
||||
const CMD_PROMPT_WITH_SPACE: &str = "/prompt ";
|
||||
const CMD_EXTENSION: &str = "/extension ";
|
||||
const CMD_BUILTIN: &str = "/builtin ";
|
||||
const CMD_MODE: &str = "/mode ";
|
||||
|
||||
match input {
|
||||
"/exit" | "/quit" => Some(InputResult::Exit),
|
||||
"/?" | "/help" => {
|
||||
@@ -73,20 +81,20 @@ fn handle_slash_command(input: &str) -> Option<InputResult> {
|
||||
}
|
||||
"/t" => Some(InputResult::ToggleTheme),
|
||||
"/prompts" => Some(InputResult::ListPrompts(None)),
|
||||
s if s.starts_with("/prompts ") => {
|
||||
s if s.starts_with(CMD_PROMPTS) => {
|
||||
// Parse arguments for /prompts command
|
||||
let args = s.strip_prefix("/prompts ").unwrap_or_default();
|
||||
let args = s.strip_prefix(CMD_PROMPTS).unwrap_or_default();
|
||||
parse_prompts_command(args)
|
||||
}
|
||||
s if s.starts_with("/prompt") => {
|
||||
if s == "/prompt" {
|
||||
s if s.starts_with(CMD_PROMPT) => {
|
||||
if s == CMD_PROMPT {
|
||||
// No arguments case
|
||||
Some(InputResult::PromptCommand(PromptCommandOptions {
|
||||
name: String::new(), // Empty name will trigger the error message in the rendering
|
||||
info: false,
|
||||
arguments: HashMap::new(),
|
||||
}))
|
||||
} else if let Some(stripped) = s.strip_prefix("/prompt ") {
|
||||
} else if let Some(stripped) = s.strip_prefix(CMD_PROMPT_WITH_SPACE) {
|
||||
// Has arguments case
|
||||
parse_prompt_command(stripped)
|
||||
} else {
|
||||
@@ -94,8 +102,15 @@ fn handle_slash_command(input: &str) -> Option<InputResult> {
|
||||
None
|
||||
}
|
||||
}
|
||||
s if s.starts_with("/extension ") => Some(InputResult::AddExtension(s[11..].to_string())),
|
||||
s if s.starts_with("/builtin ") => Some(InputResult::AddBuiltin(s[9..].to_string())),
|
||||
s if s.starts_with(CMD_EXTENSION) => Some(InputResult::AddExtension(
|
||||
s[CMD_EXTENSION.len()..].to_string(),
|
||||
)),
|
||||
s if s.starts_with(CMD_BUILTIN) => {
|
||||
Some(InputResult::AddBuiltin(s[CMD_BUILTIN.len()..].to_string()))
|
||||
}
|
||||
s if s.starts_with(CMD_MODE) => {
|
||||
Some(InputResult::GooseMode(s[CMD_MODE.len()..].to_string()))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use etcetera::choose_app_strategy;
|
||||
use etcetera::AppStrategy;
|
||||
use goose::agents::extension::{Envs, ExtensionConfig};
|
||||
use goose::agents::Agent;
|
||||
use goose::config::Config;
|
||||
use goose::message::{Message, MessageContent};
|
||||
use goose::session;
|
||||
use mcp_core::handler::ToolError;
|
||||
@@ -325,6 +326,27 @@ impl Session {
|
||||
Err(e) => output::render_error(&e.to_string()),
|
||||
}
|
||||
}
|
||||
input::InputResult::GooseMode(mode) => {
|
||||
save_history(&mut editor);
|
||||
|
||||
let config = Config::global();
|
||||
let mode = mode.to_lowercase();
|
||||
|
||||
// Check if mode is valid
|
||||
if !["auto", "approve", "chat"].contains(&mode.as_str()) {
|
||||
output::render_error(&format!(
|
||||
"Invalid mode '{}'. Mode must be one of: auto, approve, chat",
|
||||
mode
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
config
|
||||
.set("GOOSE_MODE", Value::String(mode.to_string()))
|
||||
.unwrap();
|
||||
println!("Goose mode set to '{}'", mode);
|
||||
continue;
|
||||
}
|
||||
input::InputResult::PromptCommand(opts) => {
|
||||
save_history(&mut editor);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user