Skip to content
This repository was archived by the owner on May 29, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion 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
@@ -1,6 +1,6 @@
[package]
name = "lm-bridge"
version = "0.2.1"
version = "0.3.0"
edition = "2021"
description = "MCP server connecting Google Antigravity to local LLMs via LM Studio"

Expand Down
3 changes: 3 additions & 0 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Requirements:
- Stay within the existing repository stack and patterns implied by the context.
- Do not switch frameworks, libraries, routing systems, build tools, or UI stacks unless the task explicitly requires it.
- Do not invent unrelated scaffolding or sample app structure.
- Ensure all module imports are strictly necessary and remove unused dependencies.
- Handle macro evaluations natively; do not print raw macros (e.g., `include_str!`) as string literals unless explicitly requested.
- If the task is for an existing file, generate code that fits that file's current role and surrounding code.

Provide only the necessary code, ensuring it is correct, clean, and fulfills the task constraints.
Expand All @@ -53,6 +55,7 @@ Requirements:
- Make only the requested change.
- Preserve the existing framework, stack, and file purpose.
- Do not rewrite the file into a different framework or architecture.
- Ensure all module imports are strictly necessary and prune removed dependencies.
- Do not add unrelated cleanup, refactors, formatting-only changes, or commentary.
- Return only the modified code.

Expand Down
1 change: 1 addition & 0 deletions integrations/google_antigravity/GEMINI.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ Whenever you are asked to generate or edit source code, you should prioritize de
* Always briefly explain your architectural plan before invoking the MCP tools to write the code.
* Do not apologize or use filler phrases; keep your responses concise and technical.
* When debugging, state your hypothesis clearly before editing files.
* When using `local_llm` tools to generate entirely new modules, explicitly map out the surrounding variables, borrowed scopes, and module dependencies in the tool's `Context` parameter to ensure the generated code integrates seamlessly into the broader AST without lifetime mismatching.
130 changes: 130 additions & 0 deletions src/installer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// src/installer.rs

use std::env;
use std::fs;
use std::io::{self, Write};
use std::path::PathBuf;

use serde_json::{json, Value};

/// Interactive installer for the Gemini integration.
pub async fn run_interactive_installer(
exe_path: String,
model: String,
) -> Result<(), Box<dyn std::error::Error>> {
// Resolve home directory and gemini paths
#[cfg(windows)]
let home_dir = env::var("USERPROFILE").unwrap_or_else(|_| "C:\\".to_string());
#[cfg(not(windows))]
let home_dir = env::var("HOME").unwrap_or_else(|_| "/".to_string());

let gemini_dir = PathBuf::from(home_dir).join(".gemini");
let mcp_config_path = gemini_dir.join("antigravity/mcp_config.json");
let gemini_md_path = gemini_dir.join("GEMINI.md");

// Helper to read a line from stdin
fn read_line(prompt: &str) -> io::Result<String> {
print!("{}", prompt);
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
Ok(input.trim().to_string())
}

println!("Select an option:");
println!("[1] Setup Manually (Generate files locally)");
println!("[2] Auto-Install for Google Antigravity");
let choice = read_line("Enter your choice: ")?;

match choice.as_str() {
"1" => {
println!("\n✅ Successfully generated mcp_registration.json and config.toml in this directory.");
println!("MCP Setup is complete! Please attach this executable to your AI client.");
return Ok(()); // Let main.rs handle local write
}
"2" => {
if !gemini_dir.exists() {
println!("Unable to find Antigravity configuration folder at {}. Falling back to manual mode.", gemini_dir.display());
return Ok(());
}

println!("\nWhat would you like to install?");
println!("[1] MCP Config Only");
println!("[2] Agent Rules Only");
println!("[3] Both");
let sub_choice = read_line("Enter your choice: ")?;

println!(
"\nThe following files will be modified:\n- {}\n- {}",
mcp_config_path.display(),
gemini_md_path.display()
);
println!("Note: Existing agent rules may be commented out.");
let confirm = read_line("Proceed? (y/n): ")?;
if !confirm.eq_ignore_ascii_case("y") {
println!("Installation aborted.");
return Ok(());
}

// MCP Config
if sub_choice == "1" || sub_choice == "3" {
let mut config: Value = if mcp_config_path.exists() {
let data = fs::read_to_string(&mcp_config_path)?;
match serde_json::from_str(&data) {
Ok(v) => v,
Err(e) => {
eprintln!("Failed to parse existing MCP config: {}. Please fix it manually. Aborting auto-install.", e);
return Ok(());
}
}
} else {
json!({"mcpServers": {}})
};

let local_node = json!({
"command": exe_path,
"args": [],
"env": { "LM_STUDIO_MODEL": model }
});

if let Some(obj) = config.as_object_mut() {
if !obj.contains_key("mcpServers") {
obj.insert("mcpServers".to_string(), json!({}));
}
if let Some(servers) = obj.get_mut("mcpServers").and_then(|v| v.as_object_mut())
{
servers.insert("local_llm".to_string(), local_node);
let new_json = serde_json::to_string_pretty(&config)?;
fs::write(&mcp_config_path, new_json)?;
println!("Updated MCP config at {}", mcp_config_path.display());
}
}
}

// GEMINI.md
if sub_choice == "2" || sub_choice == "3" {
let mut content = String::new();
if gemini_md_path.exists() {
content = fs::read_to_string(&gemini_md_path)?;
}

// Append the integration snippet
let include_snippet = include_str!("../integrations/google_antigravity/GEMINI.md");
if !content.contains("Privacy & Tool Routing") {
content.push_str("\n\n");
content.push_str(include_snippet);
fs::write(&gemini_md_path, content)?;
println!("Updated GEMINI.md at {}", gemini_md_path.display());
} else {
println!("Local LLM rules already exist in GEMINI.md. Skipping.");
}
}
}
_ => {
eprintln!("Invalid choice. Exiting.");
return Ok(());
}
}

Ok(())
}
30 changes: 22 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ use reqwest::ClientBuilder;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::env;
use std::io::IsTerminal;
use std::time::Duration;
use tokio::io::{AsyncBufReadExt, BufReader};

mod installer;

#[derive(Deserialize, Debug, Clone)]
struct Config {
#[serde(default = "default_url")]
Expand Down Expand Up @@ -82,9 +85,14 @@ async fn check_for_updates() {
.user_agent("lm-bridge-updater")
.timeout(Duration::from_secs(4))
.build();
let Ok(client) = client else { return; };

let resp = client.get("https://api.github.com/repos/psipher/lm-bridge/releases/latest").send().await;
let Ok(client) = client else {
return;
};

let resp = client
.get("https://api.github.com/repos/psipher/lm-bridge/releases/latest")
.send()
.await;
if let Ok(resp) = resp {
if let Ok(json) = resp.json::<Value>().await {
if let Some(tag_name) = json.get("tag_name").and_then(|v| v.as_str()) {
Expand Down Expand Up @@ -182,18 +190,24 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

// Check for interactive (double-click) mode or --register flag
let args: Vec<String> = env::args().collect();
use std::io::IsTerminal;
let is_interactive = std::io::stdin().is_terminal();

if args.contains(&"--register".to_string()) || is_interactive {
println!("\n✅ Successfully generated mcp_registration.json and config.toml in this directory.");
if is_interactive {
println!("MCP Setup is complete! Please attach this executable to your AI client.");
let exe_path = env::current_exe()?.to_string_lossy().to_string();
let result = installer::run_interactive_installer(exe_path, config.model.clone()).await;
if let Err(e) = result {
eprintln!("Installer error: {}", e);
}
// Ask user to press enter to close window
println!("\nPress Enter to close this window...");
let mut buf = String::new();
let _ = std::io::stdin().read_line(&mut buf);
} else {
// CLI raw --register call
println!("\n✅ Successfully generated mcp_registration.json and config.toml in this directory.");
}
return Ok(());
std::process::exit(0);
}

let client = ClientBuilder::new()
Expand Down Expand Up @@ -245,7 +259,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
},
"serverInfo": {
"name": "local_llm",
"version": "0.2.1"
"version": "0.3.0"
}
})),
error: None,
Expand Down