Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0cfe911
feat(cli): add cost estimation for goose cli
GaryZhous Jul 9, 2025
509a1c9
feat: add support for 'cu' command in extension installation (#3261)
blackgirlbytes Jul 7, 2025
ee6444d
Goose projects docs (#3272)
blackgirlbytes Jul 7, 2025
1535fc8
feat: cli can work with gui generated recipes (#3254)
jsibbison-square Jul 7, 2025
33cf20a
refactor(tests): make logging test in goose-cli less flaky on macos (…
cloud-on-prem Jul 7, 2025
a7d9e6e
docs: add Alby MCP tutorial (#3217)
rolznz Jul 7, 2025
701beb2
Add support in goose configure for streaming http mcp tools (#3256)
dcieslak19973 Jul 7, 2025
5765fda
feat: bedrock image content support (#3266)
mr-brobot Jul 7, 2025
8ae1244
fix Windows Env Vars (#3282)
Kvadratni Jul 7, 2025
23e561b
chore(release): release version 1.0.33 (#3284)
lily-de Jul 7, 2025
4a69ad8
Fix: Allow native Cmd+Up/Down cursor movement when user has typed tex…
aharvard Jul 7, 2025
54b4852
Enabling npx command to install on Windows Desktop (#3283)
blackgirlbytes Jul 7, 2025
32e2c3f
fix(devcontainer): install protoc to fix build (#3267)
mr-brobot Jul 7, 2025
da86a19
chore(release): release version 1.0.34 (#3285)
lily-de Jul 7, 2025
58d6ea0
feat: Add environment variables to override model context limits (#3260)
dcieslak19973 Jul 7, 2025
22183c5
fix cu (#3291)
lily-de Jul 8, 2025
043004d
docs: enhanced code editing topic (#3287)
dianed-square Jul 8, 2025
1e10a43
chore(release): release version 1.0.35 (#3292)
lily-de Jul 8, 2025
67e5d74
site analytics (#3293)
angiejones Jul 8, 2025
0e37e18
docs: move topics to tutorials section (#3297)
dianed-square Jul 8, 2025
1a3e12c
docs: add sub-recipes topic (#3241)
dianed-square Jul 8, 2025
551c8a8
Add YouTube video to Netlify MCP documentation (#3302)
agiuliano-square Jul 8, 2025
3a27971
docs: fixed broken link (#3306)
angiejones Jul 8, 2025
952cc0e
docs: VS Code MCP video (#3307)
angiejones Jul 9, 2025
f9d29eb
fix: format issues
GaryZhous Jul 9, 2025
525e4c4
fix: format issues
GaryZhous Jul 9, 2025
55869eb
fix: format issues again
GaryZhous Jul 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ RUN apt-get update && apt-get install -y \
libdbus-1-dev \
gnome-keyring \
libxcb1-dev \
protobuf-compiler \
&& apt-get clean
14 changes: 7 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ resolver = "2"

[workspace.package]
edition = "2021"
version = "1.0.32"
version = "1.0.35"
authors = ["Block <[email protected]>"]
license = "Apache-2.0"
repository = "https://github.com/block/goose"
Expand Down
136 changes: 134 additions & 2 deletions crates/goose-cli/src/commands/configure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,8 +498,13 @@ pub fn configure_extensions_dialog() -> Result<(), Box<dyn Error>> {
)
.item(
"sse",
"Remote Extension",
"Connect to a remote extension via SSE",
"Remote Extension (SSE)",
"Connect to a remote extension via Server-Sent Events",
)
.item(
"streamable_http",
"Remote Extension (Streaming HTTP)",
"Connect to a remote extension via MCP Streaming HTTP",
)
.interact()?;

Expand Down Expand Up @@ -767,6 +772,133 @@ pub fn configure_extensions_dialog() -> Result<(), Box<dyn Error>> {

cliclack::outro(format!("Added {} extension", style(name).green()))?;
}
"streamable_http" => {
let extensions = ExtensionConfigManager::get_all_names()?;
let name: String = cliclack::input("What would you like to call this extension?")
.placeholder("my-remote-extension")
.validate(move |input: &String| {
if input.is_empty() {
Err("Please enter a name")
} else if extensions.contains(input) {
Err("An extension with this name already exists")
} else {
Ok(())
}
})
.interact()?;

let uri: String = cliclack::input("What is the Streaming HTTP endpoint URI?")
.placeholder("http://localhost:8000/messages")
.validate(|input: &String| {
if input.is_empty() {
Err("Please enter a URI")
} else if !(input.starts_with("http://") || input.starts_with("https://")) {
Err("URI should start with http:// or https://")
} else {
Ok(())
}
})
.interact()?;

let timeout: u64 = cliclack::input("Please set the timeout for this tool (in secs):")
.placeholder(&goose::config::DEFAULT_EXTENSION_TIMEOUT.to_string())
.validate(|input: &String| match input.parse::<u64>() {
Ok(_) => Ok(()),
Err(_) => Err("Please enter a valid timeout"),
})
.interact()?;

let add_desc = cliclack::confirm("Would you like to add a description?").interact()?;

let description = if add_desc {
let desc = cliclack::input("Enter a description for this extension:")
.placeholder("Description")
.validate(|input: &String| {
if input.trim().is_empty() {
Err("Please enter a valid description")
} else {
Ok(())
}
})
.interact()?;
Some(desc)
} else {
None
};

let add_headers =
cliclack::confirm("Would you like to add custom headers?").interact()?;

let mut headers = HashMap::new();
if add_headers {
loop {
let key: String = cliclack::input("Header name:")
.placeholder("Authorization")
.interact()?;

let value: String = cliclack::input("Header value:")
.placeholder("Bearer token123")
.interact()?;

headers.insert(key, value);

if !cliclack::confirm("Add another header?").interact()? {
break;
}
}
}

let add_env = false; // No env prompt for Streaming HTTP

let mut envs = HashMap::new();
let mut env_keys = Vec::new();
let config = Config::global();

if add_env {
loop {
let key: String = cliclack::input("Environment variable name:")
.placeholder("API_KEY")
.interact()?;

let value: String = cliclack::password("Environment variable value:")
.mask('▪')
.interact()?;

// Try to store in keychain
let keychain_key = key.to_string();
match config.set_secret(&keychain_key, Value::String(value.clone())) {
Ok(_) => {
// Successfully stored in keychain, add to env_keys
env_keys.push(keychain_key);
}
Err(_) => {
// Failed to store in keychain, store directly in envs
envs.insert(key, value);
}
}

if !cliclack::confirm("Add another environment variable?").interact()? {
break;
}
}
}

ExtensionConfigManager::set(ExtensionEntry {
enabled: true,
config: ExtensionConfig::StreamableHttp {
name: name.clone(),
uri,
envs: Envs::new(envs),
env_keys,
headers,
description,
timeout: Some(timeout),
bundled: None,
},
})?;

cliclack::outro(format!("Added {} extension", style(name).green()))?;
}
_ => unreachable!(),
};

Expand Down
37 changes: 20 additions & 17 deletions crates/goose-cli/src/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,23 +224,26 @@ mod tests {
}

async fn do_test_log_file_name(session_name: Option<&str>, _with_error_capture: bool) {
// Create a unique test directory for each test
let test_name = session_name.unwrap_or("no_session");
let random_suffix = rand::random::<u32>() % 100000000;
let test_dir = PathBuf::from(format!(
"/tmp/goose_test_home_{}_{}",
test_name, random_suffix
));
if test_dir.exists() {
fs::remove_dir_all(&test_dir).unwrap();
}
fs::create_dir_all(&test_dir).unwrap();
use tempfile::TempDir;

// Create a unique prefix to avoid test interference
let test_id = format!(
"{}_{}",
session_name.unwrap_or("no_session"),
rand::random::<u32>()
);

// Create a proper temporary directory that will be automatically cleaned up
let _temp_dir = TempDir::with_prefix(&format!("goose_test_{}_", test_id)).unwrap();
let test_dir = _temp_dir.path();

// Set up environment
if cfg!(windows) {
env::set_var("USERPROFILE", &test_dir);
env::set_var("USERPROFILE", test_dir);
} else {
env::set_var("HOME", &test_dir);
env::set_var("HOME", test_dir);
// Also set TMPDIR to prevent temp directory sharing between tests
env::set_var("TMPDIR", test_dir);
}

// Create error capture if needed - but don't use it in tests to avoid tokio runtime issues
Expand All @@ -251,8 +254,10 @@ mod tests {
println!("Before timestamp: {}", before_timestamp);

// Get the log directory and clean any existing log files
let random_suffix = rand::random::<u32>() % 100000000;
let log_dir = get_log_directory_with_date(Some(format!("test-{}", random_suffix))).unwrap();
println!("Log directory: {}", log_dir.display());
println!("Test directory: {}", test_dir.display());
if log_dir.exists() {
for entry in fs::read_dir(&log_dir).unwrap() {
let entry = entry.unwrap();
Expand Down Expand Up @@ -429,10 +434,8 @@ mod tests {
// Wait a moment to ensure all files are written
std::thread::sleep(std::time::Duration::from_millis(100));

// Clean up test directory
fs::remove_dir_all(&test_dir).unwrap_or_else(|e| {
println!("Warning: Failed to clean up test directory: {}", e);
});
// Keep _temp_dir alive until the end so it doesn't get cleaned up prematurely
drop(_temp_dir);
}

#[tokio::test]
Expand Down
35 changes: 33 additions & 2 deletions crates/goose-cli/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use goose::agents::extension::{Envs, ExtensionConfig};
use goose::agents::{Agent, SessionConfig};
use goose::config::Config;
use goose::message::{Message, MessageContent};
use goose::providers::pricing::initialize_pricing_cache;
use goose::session;
use input::InputResult;
use mcp_core::handler::ToolError;
Expand Down Expand Up @@ -1303,13 +1304,42 @@ impl Session {
pub async fn display_context_usage(&self) -> Result<()> {
let provider = self.agent.provider().await?;
let model_config = provider.get_model_config();
let context_limit = model_config.context_limit.unwrap_or(32000);
let context_limit = model_config.context_limit();

let config = Config::global();
let show_cost = config
.get_param::<bool>("GOOSE_CLI_SHOW_COST")
.unwrap_or(false);

let provider_name = config
.get_param::<String>("GOOSE_PROVIDER")
.unwrap_or_else(|_| "unknown".to_string());

// Initialize pricing cache on startup
tracing::info!("Initializing pricing cache...");
if let Err(e) = initialize_pricing_cache().await {
tracing::warn!(
"Failed to initialize pricing cache: {e}. Pricing data may not be available."
);
}

match self.get_metadata() {
Ok(metadata) => {
let total_tokens = metadata.total_tokens.unwrap_or(0) as usize;

output::display_context_usage(total_tokens, context_limit);

if show_cost {
let input_tokens = metadata.input_tokens.unwrap_or(0) as usize;
let output_tokens = metadata.output_tokens.unwrap_or(0) as usize;
output::display_cost_usage(
&provider_name,
&model_config.model_name,
input_tokens,
output_tokens,
)
.await;
}
}
Err(_) => {
output::display_context_usage(0, context_limit);
Expand Down Expand Up @@ -1450,7 +1480,8 @@ fn get_reasoner() -> Result<Arc<dyn Provider>, anyhow::Error> {
.expect("No model configured. Run 'goose configure' first")
};

let model_config = ModelConfig::new(model);
let model_config =
ModelConfig::new_with_context_env(model, Some("GOOSE_PLANNER_CONTEXT_LIMIT"));
let reasoner = create(&provider, model_config)?;

Ok(reasoner)
Expand Down
Loading