diff --git a/README (Copy).md b/README (Copy).md deleted file mode 100644 index cfa82d9..0000000 --- a/README (Copy).md +++ /dev/null @@ -1,405 +0,0 @@ -# Mint - -
-
-
- Unified AI Desktop Assistant, Agentic CLI, and local-first automation workspace. -
- - - -Mint is an AI assistant built to live in your desktop and terminal. It combines a transparent Electron desktop assistant, a unified agentic CLI, project-aware coding tools, local memory, automation, multi-provider AI routing, MCP extensions, and safety controls. - -## What's New - -- **Antigravity-style Desktop Layout:** Desktop UI now has a collapsible sidebar, Chat/Pictures navigation, smoother page transitions, startup loading polish, and clearer destructive-action confirmations. -- **Local Pictures Library:** Images sent from the desktop chat are saved locally under `~/.config/mint/Pictures` after the user sends the message, with an in-app Pictures gallery and local metadata index. -- **Image Privacy Hardening:** Chat history no longer stores raw image base64 data for saved images; history keeps a text placeholder while the actual file stays in the local Pictures folder. -- **Theme & UI Controls:** Settings now include theme, accent color, system text color, glass blur, font family, and font size controls that apply to the desktop interface. -- **Unified CLI Agent:** `mint` now routes every normal message through the same agent loop. It can think, answer conversationally, inspect projects, edit files, run tools, and finish directly for simple chat. -- **Fast Mode:** `/fast` switches the interactive CLI into a quieter `[Fast]` status that keeps the working indicator visible but hides internal `Thinking:` and tool-progress trace messages. -- **Live CLI Replies:** Mint responses now appear in one live-updating `Mint` message instead of waiting for the whole final answer to render at once. -- **Learned Skills:** `mint learn
-
-
-
- Desktop Assistant UI with Live2D model, chat panel, sidebar navigation, and local Pictures view. Settings UI for providers, automation, theme, voice, plugins, and MCP configuration.
-
-
-
- Unified CLI Agent for chat, coding tasks, tool use, workspace context, image input, and command workflows.
-
Made with love by Pheem49
diff --git a/README.md b/README.md index 82263b0..eccacfd 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ Mint is a local-first AI assistant running on your machine, capable of handling ### 5. 🔌 Tool & MCP Integrations - Support **Model Context Protocol (MCP)** to connect tools like Google/Brave Search, Filesystem servers, and GitHub context. +- **Auto GitHub Link Resolver:** Automatically detects GitHub URLs in chat messages (CLI, Web, and Desktop) and Code Agent tasks. It fetches and injects the repository's metadata, directory structure, and README as prompt context, serving as an instant fallback when the GitHub MCP server is not active. - Local plugins for Spotify playback control, Google Calendar, Gmail drafts, and Notion workspace reading. --- @@ -307,6 +308,7 @@ Mint includes native workspace tools for code inspection, planning, editing, and ```bash mint code agent "inspect this repo and fix the failing tests" +mint code github-overview "Pheem49/Mint" mint code summary . mint code search "shell approval flow" . mint symbols . diff --git a/crates/mint-cli/src/agent.rs b/crates/mint-cli/src/agent.rs index 4fdbf1e..e707a0a 100644 --- a/crates/mint-cli/src/agent.rs +++ b/crates/mint-cli/src/agent.rs @@ -785,6 +785,51 @@ fn confirm_pausing_interrupt(prompt: &str, approval_active: &AtomicBool) -> bool } fn format_markdown_bold(text: &str) -> String { + let mut formatted_lines = Vec::new(); + let mut in_code_block = false; + + for line in text.lines() { + let mut formatted_line = line.to_string(); + let trimmed = line.trim_start(); + + if trimmed.starts_with("```") { + in_code_block = !in_code_block; + } + + if !in_code_block { + let hash_count = trimmed.chars().take_while(|&c| c == '#').count(); + if hash_count > 0 && hash_count <= 6 { + let next_char = trimmed.chars().nth(hash_count); + let is_heading = match next_char { + Some(' ') => true, + Some(c) => !c.is_alphanumeric(), + None => true, + }; + if is_heading { + // Make the entire heading line bold and bright white + formatted_line = format!("{}{}{}", BRIGHT, line, RESET); + } else { + formatted_line = process_inline_bold(&formatted_line); + } + } else { + formatted_line = process_inline_bold(&formatted_line); + } + } else { + // Do not format markdown inside code blocks + formatted_line = line.to_string(); + } + + formatted_lines.push(formatted_line); + } + + let mut result = formatted_lines.join("\n"); + if text.ends_with('\n') { + result.push('\n'); + } + result +} + +fn process_inline_bold(text: &str) -> String { let count = text.matches("**").count(); let pair_limit = (count / 2) * 2; let mut result = String::with_capacity(text.len()); diff --git a/crates/mint-cli/src/main.rs b/crates/mint-cli/src/main.rs index 9dfeab3..28e593e 100644 --- a/crates/mint-cli/src/main.rs +++ b/crates/mint-cli/src/main.rs @@ -14,6 +14,7 @@ use mint_core::{ load_config, native_plugins, orchestrate_chat_stream_with_fallback, orchestrate_chat_with_fallback, propose_code_edits, read_code_file, repository_summary, run_shell_command, search_code, search_semantic_code, set_config_value, + parse_github_url, fetch_github_repo_summary, }; mod agent; @@ -400,6 +401,11 @@ enum CodeCommand { #[arg(long, default_value = ".")] root: PathBuf, }, + /// Fetch GitHub repository metadata and README, then get an overview of the repo. + GithubOverview { + /// The GitHub repository URL or name (e.g. "https://github.com/owner/repo" or "owner/repo"). + repo: String, + }, } #[derive(Debug, Subcommand)] @@ -895,6 +901,9 @@ async fn main() -> Result<()> { &config, )?)? ), + CodeCommand::GithubOverview { repo } => { + run_github_overview(&repo, &config).await?; + } } } Command::Open { target } => { @@ -2737,6 +2746,48 @@ fn print_shell_output(output: &mint_core::ShellOutput) { ); } + + +async fn run_github_overview(repo: &str, config: &MintConfig) -> Result<()> { + let Some((owner, repo_name)) = parse_github_url(repo) else { + anyhow::bail!("Invalid GitHub repository URL/format. Please use 'owner/repo' or a full GitHub URL."); + }; + + println!("Fetching information for {}/{} from GitHub...", owner, repo_name); + let summary = match fetch_github_repo_summary(&owner, &repo_name).await { + Ok(s) => s, + Err(e) => { + anyhow::bail!("Failed to fetch repository summary: {}. Check that the repository is public and spelled correctly.", e); + } + }; + + println!("Analyzing repository with AI model..."); + let prompt = format!( + "Here is the metadata, top-level directory structure, and README.md content for the GitHub repository {}/{}:\n\n{}\n\nBased on this information, please provide a high-level overview of what this repository is about, what tech stack it uses, its overall architecture, and how it is organized.", + owner, repo_name, summary + ); + + let (response, _) = orchestrate_chat_with_fallback( + config, + &ChatRequest { + message: prompt, + system_instruction: "You are a professional software architect providing a high-level overview of a code repository based on its metadata and README.".to_string(), + chat_id: Some("github_review".to_string()), + image_data_uri: None, + audio_data_uri: None, + document_attachment: None, + workspace_path: None, + }, + ) + .await?; + + println!("\n--- AI Repository Overview for {}/{} ---", owner, repo_name); + println!("{}", response.text); + println!("--------------------------------------------------"); + Ok(()) +} + + #[cfg(test)] mod tests { use super::*; diff --git a/crates/mint-core/src/code_tools.rs b/crates/mint-core/src/code_tools.rs index 08777e4..5a8f2d1 100644 --- a/crates/mint-core/src/code_tools.rs +++ b/crates/mint-core/src/code_tools.rs @@ -465,6 +465,101 @@ fn is_ignored_directory(path: &Path) -> bool { .is_some_and(|name| IGNORED_DIRECTORIES.contains(&name)) } +pub fn parse_github_url(url: &str) -> Option<(String, String)> { + let cleaned = url.trim() + .trim_start_matches("https://") + .trim_start_matches("http://") + .trim_start_matches("www.") + .trim_start_matches("github.com/"); + + let parts: Vec<&str> = cleaned.split('/').collect(); + if parts.len() >= 2 { + let owner = parts[0].to_string(); + let mut repo = parts[1].to_string(); + if repo.ends_with(".git") { + repo = repo[..repo.len() - 4].to_string(); + } + Some((owner, repo)) + } else { + None + } +} + +pub async fn fetch_github_repo_summary(owner: &str, repo: &str) -> Result