Add camofox-browser integration: gyandoot-browser crate + agent tools

New crate: crates/gyandoot-browser/
  - client.rs: Full REST client for camofox-browser API (health, tabs,
    navigation, snapshots, click/type/scroll, search macros)
  - macros.rs: 14 SearchMacro enum (Google, YouTube, Amazon, Reddit,
    Wikipedia, Twitter, Yelp, Spotify, Netflix, LinkedIn, Instagram,
    TikTok, Twitch) with parse/display
  - snapshot.rs: PageSnapshot, PageLinks, NavigationResult, HealthResponse
  - session.rs: BrowserSession, SessionManager with idle cleanup

New module: crates/gyandoot-agent/src/browser_tools.rs
  - WebSearchTool: Search the web using anti-detection browser
  - WebBrowseTool: Browse URLs, click, type, scroll with element refs
  - register_browser_tools(): Wire both tools into ToolExecutor

CLI: Added
                                 o
                                 |
               - - - - - -     .---.     - - - - - -
          - - - - - - - - -  [ ^   ^ ]  - - - - - - - - -
             - - - - - - -   |  ___  |   - - - - - - -
                              `-----'
           ____ __   __  ___   _   _  ____   ___   ___  _____
          / ___|\ \ / / / _ \ | \ | ||  _ \ / _ \ / _ \|_   _|
         | |  _  \ V / / /_\ \|  \| || | | | | | | | | | | |
         | |_| |  | |  |  _  || |\  || |_| | |_| | |_| | | |
          \____/  |_|  |_| |_||_| \_||____/ \___/ \___/  |_|

        o - - - - - - -  knowledge. delivered. - - - - - - - o

Unknown command: browser
Run 'gyandoot help' for usage. subcommand with health/search/browse actions

Installer: Updated install.sh with camofox-browser cloning, Node.js
detection, npm dependency install, and systemd service setup

Camofox-browser running at http://localhost:9377 (Docker, verified working)

Workspace: 12 crates + CLI binary — cargo check passes with 0 errors.
This commit is contained in:
2026-06-11 20:23:35 +05:30
parent 851c838ca5
commit 605747023b
13 changed files with 1578 additions and 5 deletions

View File

@@ -0,0 +1,165 @@
//! Search macros for common websites.
//!
//! Camofox-browser supports 14+ built-in search macros that map to
//! site-specific search URLs. Using macros is more reliable than
//! manually constructing URLs because the browser handles all
//! redirects, cookies, and anti-bot challenges.
use serde::{Deserialize, Serialize};
use std::fmt;
/// All supported search macros.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SearchMacro {
/// `@google_search` — Search Google
GoogleSearch,
/// `@youtube_search` — Search YouTube
YoutubeSearch,
/// `@amazon_search` — Search Amazon
AmazonSearch,
/// `@reddit_search` — Search Reddit (returns JSON)
RedditSearch,
/// `@reddit_subreddit` — Browse a subreddit (returns JSON)
RedditSubreddit,
/// `@wikipedia_search` — Search Wikipedia
WikipediaSearch,
/// `@twitter_search` — Search Twitter/X
TwitterSearch,
/// `@yelp_search` — Search Yelp
YelpSearch,
/// `@spotify_search` — Search Spotify
SpotifySearch,
/// `@netflix_search` — Search Netflix
NetflixSearch,
/// `@linkedin_search` — Search LinkedIn
LinkedinSearch,
/// `@instagram_search` — Search Instagram
InstagramSearch,
/// `@tiktok_search` — Search TikTok
TiktokSearch,
/// `@twitch_search` — Search Twitch
TwitchSearch,
}
impl SearchMacro {
/// All supported macros as a static slice.
pub const ALL: &'static [SearchMacro] = &[
SearchMacro::GoogleSearch,
SearchMacro::YoutubeSearch,
SearchMacro::AmazonSearch,
SearchMacro::RedditSearch,
SearchMacro::RedditSubreddit,
SearchMacro::WikipediaSearch,
SearchMacro::TwitterSearch,
SearchMacro::YelpSearch,
SearchMacro::SpotifySearch,
SearchMacro::NetflixSearch,
SearchMacro::LinkedinSearch,
SearchMacro::InstagramSearch,
SearchMacro::TiktokSearch,
SearchMacro::TwitchSearch,
];
/// The `@name` string sent to the camofox API.
pub fn as_macro_str(&self) -> &'static str {
match self {
SearchMacro::GoogleSearch => "@google_search",
SearchMacro::YoutubeSearch => "@youtube_search",
SearchMacro::AmazonSearch => "@amazon_search",
SearchMacro::RedditSearch => "@reddit_search",
SearchMacro::RedditSubreddit => "@reddit_subreddit",
SearchMacro::WikipediaSearch => "@wikipedia_search",
SearchMacro::TwitterSearch => "@twitter_search",
SearchMacro::YelpSearch => "@yelp_search",
SearchMacro::SpotifySearch => "@spotify_search",
SearchMacro::NetflixSearch => "@netflix_search",
SearchMacro::LinkedinSearch => "@linkedin_search",
SearchMacro::InstagramSearch => "@instagram_search",
SearchMacro::TiktokSearch => "@tiktok_search",
SearchMacro::TwitchSearch => "@twitch_search",
}
}
/// Human-readable site name.
pub fn site_name(&self) -> &'static str {
match self {
SearchMacro::GoogleSearch => "Google",
SearchMacro::YoutubeSearch => "YouTube",
SearchMacro::AmazonSearch => "Amazon",
SearchMacro::RedditSearch => "Reddit",
SearchMacro::RedditSubreddit => "Reddit (subreddit)",
SearchMacro::WikipediaSearch => "Wikipedia",
SearchMacro::TwitterSearch => "Twitter/X",
SearchMacro::YelpSearch => "Yelp",
SearchMacro::SpotifySearch => "Spotify",
SearchMacro::NetflixSearch => "Netflix",
SearchMacro::LinkedinSearch => "LinkedIn",
SearchMacro::InstagramSearch => "Instagram",
SearchMacro::TiktokSearch => "TikTok",
SearchMacro::TwitchSearch => "Twitch",
}
}
/// Parse a `@name` string into a `SearchMacro`.
pub fn from_str(s: &str) -> Option<Self> {
match s {
"@google_search" => Some(SearchMacro::GoogleSearch),
"@youtube_search" => Some(SearchMacro::YoutubeSearch),
"@amazon_search" => Some(SearchMacro::AmazonSearch),
"@reddit_search" => Some(SearchMacro::RedditSearch),
"@reddit_subreddit" => Some(SearchMacro::RedditSubreddit),
"@wikipedia_search" => Some(SearchMacro::WikipediaSearch),
"@twitter_search" => Some(SearchMacro::TwitterSearch),
"@yelp_search" => Some(SearchMacro::YelpSearch),
"@spotify_search" => Some(SearchMacro::SpotifySearch),
"@netflix_search" => Some(SearchMacro::NetflixSearch),
"@linkedin_search" => Some(SearchMacro::LinkedinSearch),
"@instagram_search" => Some(SearchMacro::InstagramSearch),
"@tiktok_search" => Some(SearchMacro::TiktokSearch),
"@twitch_search" => Some(SearchMacro::TwitchSearch),
_ => None,
}
}
}
impl fmt::Display for SearchMacro {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_macro_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_all_macros_have_valid_names() {
for m in SearchMacro::ALL {
let name = m.as_macro_str();
assert!(name.starts_with('@'), "Macro should start with @");
assert!(name.len() > 2, "Macro should have a name");
}
}
#[test]
fn test_roundtrip_parse() {
for m in SearchMacro::ALL {
let name = m.as_macro_str();
let parsed = SearchMacro::from_str(name);
assert_eq!(parsed, Some(*m), "Roundtrip failed for {name}");
}
}
#[test]
fn test_invalid_macro() {
assert_eq!(SearchMacro::from_str("@invalid_search"), None);
assert_eq!(SearchMacro::from_str("google_search"), None);
}
#[test]
fn test_site_names() {
assert_eq!(SearchMacro::GoogleSearch.site_name(), "Google");
assert_eq!(SearchMacro::YoutubeSearch.site_name(), "YouTube");
}
}