Skip to content

Commit

Permalink
Merge branch 'v1.0' into micn/hermitize-mcp
Browse files Browse the repository at this point in the history
* v1.0:
  feat: update the cli to be cross platform (#624)
  fix: make sure dev setup works and document (#623)
  chore [v1.0]: tidy up processes when session closes (#622)
  • Loading branch information
michaelneale committed Jan 16, 2025
2 parents 825005c + 4e0d803 commit 512146b
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 96 deletions.
99 changes: 99 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Contributing

We welcome Pull Requests for general contributions! If you have a larger new feature or any questions on how to develop a fix, we recommend you open an [issue][issues] before starting.

## Prerequisites

Goose includes rust binaries alongside an electron app for the GUI. To work
on the rust backend, you will need to [install rust and cargo][rustup]. To work
on the App, you will also need to [install node and npm][nvm] - we recommend through nvm.

We provide a shortcut to standard commands using [just][just] in our `justfile`.

## Getting Started

### Rust

First let's compile goose and try it out

```
cargo build
```

when that is done, you should now have debug builds of the binaries like the goose cli:

```
./target/debug/goose --help
```

If you haven't used the CLI before, you can use this compiled version to do first time configuration:

```
./target/debug/goose configure
```

And then once you have a connection to an LLM provider working, you can run a session!

```
./target/debug/goose session
```

These same commands can be recompiled and immediately run using `cargo run -p goose-cli` for iteration.
As you make changes to the rust code, you can try it out on the CLI, or also run checks and tests:

```
cargo check # do your changes compile
cargo test # do the tests pass with your changes.
```

### Node

Now let's make sure you can run the app.

```
just run-ui
```

The start gui will both build a release build of rust (as if you had done `cargo build -r`) and start the electron process.
You should see the app open a window, and drop you into first time setup. When you've gone through the setup,
you can talk to goose!

You can now make changes in the code in ui/desktop to iterate on the GUI half of goose.

## Env Vars

You may want to make more frequent changes to your provider setup or similar to test things out
as a developer. You can use environment variables to change things on the fly without redoing
your configuration.

> [!TIP]
> At the moment, we are still updating some of the CLI configuration to make sure this is
> respected.
You can change the provider goose points to via the `GOOSE_PROVIDER` env var. If you already
have a credential for that provider in your keychain from previously setting up, it should
reuse it. For things like automations or to test without doing official setup, you can also
set the relevant env vars for that provider. For example `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`,
or `DATABRICKS_HOST`. Refer to the provider details for more info on required keys.

## Enable traces in Goose with [locally hosted Langfuse](https://langfuse.com/docs/deployment/self-host)

- Run `just langfuse-server` to start your local Langfuse server. It requires Docker.
- Go to http://localhost:3000 and log in with the default email/password output by the shell script (values can also be found in the `.env.langfuse.local` file).
- Set the environment variables so that rust can connect to the langfuse server

```
export LANGFUSE_INIT_PROJECT_PUBLIC_KEY=publickey-local
export LANGFUSE_INIT_PROJECT_SECRET_KEY=secretkey-local
```

Then you can view your traces at http://localhost:3000

## Conventional Commits

This project follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification for PR titles. Conventional Commits make it easier to understand the history of a project and facilitate automation around versioning and changelog generation.

[issues]: https://github.com/block/goose/issues
[rustup]: https://doc.rust-lang.org/cargo/getting-started/installation.html
[nvm]: https://github.com/nvm-sh/nvm
[just]: https://github.com/casey/just?tab=readme-ov-file#installation
15 changes: 10 additions & 5 deletions crates/goose-cli/src/log_usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ mod tests {
fn test_session_logging() {
run_with_tmp_dir(|| {
let home_dir = dirs::home_dir().unwrap();
let log_dir = home_dir.join(".config").join("goose").join("logs");
let log_file = home_dir
.join(".config")
.join("goose")
.join("logs")
.join("goose.log");

log_usage(
"path.txt".to_string(),
Expand All @@ -73,19 +77,20 @@ mod tests {
);

// Check if log file exists and contains the expected content
let log_file = log_dir.join("goose.log");
assert!(log_file.exists());
assert!(log_file.exists(), "Log file should exist");

let log_content = std::fs::read_to_string(&log_file).unwrap();
let log: SessionLog =
serde_json::from_str(log_content.lines().last().unwrap()).unwrap();
let log: SessionLog = serde_json::from_str(&log_content).unwrap();

assert!(log.session_file.contains("path.txt"));
assert_eq!(log.usage[0].usage.input_tokens, Some(10));
assert_eq!(log.usage[0].usage.output_tokens, Some(20));
assert_eq!(log.usage[0].usage.total_tokens, Some(30));
assert_eq!(log.usage[0].model, "model");
assert_eq!(log.usage[0].cost, Some(dec!(0.5)));

// Remove the log file after test
std::fs::remove_file(&log_file).ok();
})
}
}
46 changes: 34 additions & 12 deletions download_cli.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,44 @@
set -euo pipefail

REPO="block/goose"
FILE="goose"
OUT_FILE="goose"
GITHUB_API_ENDPOINT="api.github.com"

function gh_curl() {
curl -sL -H "Accept: application/vnd.github.v3.raw" $@
}

# Find the goose binary asset id without using jq
# Determine the operating system and architecture
OS=$(uname | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)

case "$ARCH" in
x86_64)
ARCH="x86_64"
;;
arm64)
ARCH="aarch64"
;;
*)
echo "ERROR: Unsupported architecture: $ARCH"
exit 1
;;
esac

FILE="goose-$ARCH-unknown-linux-gnu.tar.bz2"
if [ "$OS" = "darwin" ]; then
FILE="goose-$ARCH-apple-darwin.tar.bz2"
fi

# Find the goose binary asset id
echo "Looking up the most recent goose binary release..."
echo ""
RELEASES=$(gh_curl https://$GITHUB_API_ENDPOINT/repos/$REPO/releases)

# Use awk to find the asset ID
# This script looks for the first prerelease and within it finds the asset with matching name
# Parse JSON to find the asset ID
ASSET_ID=$(echo "$RELEASES" | awk -v file="$FILE" '
BEGIN { found_prerelease = 0; found_asset = 0; }
/"prerelease"/ && /true/ { found_prerelease = 1; next }
found_prerelease && /"assets"/ { in_assets = 1; next }
BEGIN { found_asset = 0; }
/"assets"/ { in_assets = 1; next }
in_assets && /"id":/ {
match($0, /[0-9]+/);
current_id = substr($0, RSTART, RLENGTH);
Expand All @@ -38,10 +57,13 @@ if [ -z "$ASSET_ID" ]; then
fi

# Download the goose binary
echo "Downloading goose..."
echo "Downloading $FILE..."
echo ""
curl -sL --header 'Accept: application/octet-stream' https://$GITHUB_API_ENDPOINT/repos/$REPO/releases/assets/$ASSET_ID > $OUT_FILE
chmod +x $OUT_FILE
curl -sL --header 'Accept: application/octet-stream' https://$GITHUB_API_ENDPOINT/repos/$REPO/releases/assets/$ASSET_ID > $FILE
tar -xjf $FILE
echo "Cleaning up $FILE..."
rm $FILE
chmod +x goose goosed

LOCAL_BIN="$HOME/.local/bin"
if [ ! -d "$LOCAL_BIN" ]; then
Expand All @@ -53,8 +75,8 @@ fi

echo "Sending goose to $LOCAL_BIN/$OUT_FILE"
echo ""
chmod +x $OUT_FILE
mv $OUT_FILE $LOCAL_BIN/$OUT_FILE
mv goose $LOCAL_BIN/$OUT_FILE
mv goosed $LOCAL_BIN

# Check if the directory is in the PATH
if [[ ":$PATH:" != *":$LOCAL_BIN:"* ]]; then
Expand Down
51 changes: 26 additions & 25 deletions ui/desktop/bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,66 +4,67 @@
from typing import Dict, Union
import os

def replace_env_macro() -> bool:

def replace_env_macro() -> bool:
"""
Replace content between environment macro markers with formatted environment variables.
Args:
provider_type (str): The type of provider (e.g., 'databricks')
host (str): The host URL
model (str): The model name
Returns:
bool: True if successful, False otherwise
"""
file_path = './src/main.ts'
file_path = "./src/main.ts"

try:
# Read the file content
with open(file_path, 'r', encoding='utf-8') as f:
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()

# Format the environment variables
# For example: GOOSE_BNDLE_TYPE=databricks, GOOSE_BUNDLE_HOST=host, GOOSE_BUNDLE_MODEL=model
formatted_vars = [
f" process.env.GOOSE_PROVIDER__TYPE = '{os.getenv("GOOSE_BUNDLE_TYPE")}';",
f" process.env.GOOSE_PROVIDER__HOST = '{os.getenv("GOOSE_BUNDLE_HOST")}';",
f" process.env.GOOSE_PROVIDER__MODEL = '{os.getenv("GOOSE_BUNDLE_MODEL")}';"
f" process.env.GOOSE_PROVIDER = '{os.getenv('GOOSE_BUNDLE_TYPE')}';",
f" process.env.{os.getenv('GOOSE_BUNDLE_TYPE').upper()}_HOST = '{os.getenv('GOOSE_BUNDLE_HOST')}';",
f" process.env.{os.getenv('GOOSE_BUNDLE_TYPE').upper()}_MODEL = '{os.getenv('GOOSE_BUNDLE_MODEL')}';",
]

replacement_content = "\n".join(formatted_vars)
replacement_content += "\n return true;"

# Define the pattern to match content between markers
pattern = r'//{env-macro-start}//.*?//{env-macro-end}//'
pattern = r"//{env-macro-start}//.*?//{env-macro-end}//"
flags = re.DOTALL # Allow matching across multiple lines

# Create the replacement string with the markers and new content
replacement = f"//{{env-macro-start}}//\n{replacement_content}\n//{{env-macro-end}}//"

replacement = (
f"//{{env-macro-start}}//\n{replacement_content}\n//{{env-macro-end}}//"
)

# Perform the replacement
new_content, count = re.subn(pattern, replacement, content, flags=flags)

if count == 0:
print(f"Error: Could not find macro markers in {file_path}")
return False

# Write the modified content back to the file
with open(file_path, 'w', encoding='utf-8') as f:
with open(file_path, "w", encoding="utf-8") as f:
f.write(new_content)

print(f"Successfully updated {file_path}")
return True

except Exception as e:
print(f"Error processing file {file_path}: {str(e)}")
return False


# Example usage
if __name__ == '__main__':
if __name__ == "__main__":
success = replace_env_macro()

if not success:
print("Failed to update environment variables")
exit(1)
exit(1)
Loading

0 comments on commit 512146b

Please sign in to comment.