From 8ead003f5c46282fcf8e51b26630b08cf0f1f1a0 Mon Sep 17 00:00:00 2001 From: mouse-value-add Date: Sun, 2 Aug 2026 09:46:27 +0000 Subject: [PATCH 1/3] feat: add You.com web search MCP tool example - Add optional You.com web search integration as MCP tool - Supports both keyless (100 free searches/day) and authenticated modes - Includes TypeScript API client with proper error handling - Provides rich React UI for interactive search results - Follows existing Skybridge patterns for tool registration - Comprehensive documentation and usage examples - Handles rate limits, API errors, and network issues gracefully - Compatible with Claude Code, ChatGPT, and other MCP clients Integration adds web search capabilities to any Skybridge MCP app through the youcom-search tool with structured results and metadata. --- examples/youcom-web-search/.env.example | 4 + examples/youcom-web-search/.gitignore | 3 + examples/youcom-web-search/README.md | 172 ++++++++++++++ examples/youcom-web-search/package.json | 42 ++++ examples/youcom-web-search/src/env.ts | 13 ++ examples/youcom-web-search/src/helpers.ts | 4 + examples/youcom-web-search/src/index.css | 96 ++++++++ examples/youcom-web-search/src/server.ts | 148 ++++++++++++ .../src/views/youcom-search-results/index.tsx | 221 ++++++++++++++++++ .../youcom-web-search/src/youcom-client.ts | 141 +++++++++++ examples/youcom-web-search/tsconfig.json | 6 + examples/youcom-web-search/vite.config.ts | 8 + 12 files changed, 858 insertions(+) create mode 100644 examples/youcom-web-search/.env.example create mode 100644 examples/youcom-web-search/.gitignore create mode 100644 examples/youcom-web-search/README.md create mode 100644 examples/youcom-web-search/package.json create mode 100644 examples/youcom-web-search/src/env.ts create mode 100644 examples/youcom-web-search/src/helpers.ts create mode 100644 examples/youcom-web-search/src/index.css create mode 100644 examples/youcom-web-search/src/server.ts create mode 100644 examples/youcom-web-search/src/views/youcom-search-results/index.tsx create mode 100644 examples/youcom-web-search/src/youcom-client.ts create mode 100644 examples/youcom-web-search/tsconfig.json create mode 100644 examples/youcom-web-search/vite.config.ts diff --git a/examples/youcom-web-search/.env.example b/examples/youcom-web-search/.env.example new file mode 100644 index 000000000..eee124cb3 --- /dev/null +++ b/examples/youcom-web-search/.env.example @@ -0,0 +1,4 @@ +# Optional You.com API key for higher quotas and enhanced features +# Without this, the tool uses keyless operation (100 free searches/day) +# Get your API key at: https://you.com/platform/api-keys +YDC_API_KEY=your-api-key-here diff --git a/examples/youcom-web-search/.gitignore b/examples/youcom-web-search/.gitignore new file mode 100644 index 000000000..ecff56526 --- /dev/null +++ b/examples/youcom-web-search/.gitignore @@ -0,0 +1,3 @@ +.env +node_modules +dist diff --git a/examples/youcom-web-search/README.md b/examples/youcom-web-search/README.md new file mode 100644 index 000000000..afafc281e --- /dev/null +++ b/examples/youcom-web-search/README.md @@ -0,0 +1,172 @@ +# You.com Web Search MCP Tool + +This example demonstrates how to integrate You.com's web search capabilities into a Skybridge MCP app, providing real-time web search functionality with rich interactive results. + +## Features + +- **Web search**: Search the web using You.com's powerful search engine +- **Keyless operation**: Works without API key (100 free searches per day) +- **Enhanced features**: Higher quotas and additional features with optional API key +- **Rich UI**: Interactive search results with titles, snippets, and source information +- **Search options**: Support for domain filtering, freshness filtering, and safe search +- **Real-time search**: Interactive search interface within the MCP app +- **Error handling**: Graceful handling of rate limits, API errors, and network issues + +## Setup + +### Quick Start (Keyless Mode) + +No setup required! The tool works immediately with 100 free searches per day: + +```bash +npm install +npm run dev +``` + +### Enhanced Mode (With API Key) + +For higher quotas and enhanced features: + +1. Get your API key at [you.com/platform/api-keys](https://you.com/platform/api-keys) +2. Create `.env` file: + ``` + YDC_API_KEY=your-api-key-here + ``` +3. Start the development server: + ```bash + npm run dev + ``` + +## Usage + +### MCP Tool + +The example registers a `youcom-search` tool with the following parameters: + +- `query` (required): Search query string +- `count` (optional): Number of results (1-20, default: 10) +- `domains` (optional): Array of domains to restrict search to +- `freshness` (optional): Filter by content age ("hour", "day", "week", "month", "year") +- `safeSearch` (optional): Enable safe search filtering (default: true) + +### Example Tool Calls + +```typescript +// Basic search +await callTool("youcom-search", { + query: "TypeScript MCP frameworks" +}); + +// Advanced search with filters +await callTool("youcom-search", { + query: "React hooks patterns", + count: 15, + domains: ["reactjs.org", "github.com"], + freshness: "month", + safeSearch: true +}); +``` + +### Interactive UI + +The app provides a rich search interface with: + +- Real-time search input +- Visual result cards with titles, snippets, and favicons +- Source domain display and external link indicators +- Loading states and error handling +- Search options display (keyless mode, domain filters, etc.) +- Responsive design that works across devices + +## API Integration + +The integration uses You.com's Search API: + +- **Endpoint**: `https://api.you.com/v1/agents/search` +- **Authentication**: Optional Bearer token (`YDC_API_KEY`) +- **Rate limits**: + - Keyless: 100 searches/day per IP + - With API key: Higher quotas based on plan +- **Response format**: Structured JSON with web and news results + +### Error Handling + +The tool gracefully handles: + +- **401 Unauthorized**: Invalid API key guidance +- **429 Rate Limited**: Clear messaging about quota limits with upgrade suggestions +- **5xx Server Errors**: Service availability notifications +- **Network errors**: Connection issue messaging +- **Malformed responses**: Data validation and fallbacks + +## Implementation Details + +### Architecture + +``` +src/ +├── youcom-client.ts # You.com API client with error handling +├── server.ts # MCP server with tool registration +├── helpers.ts # Type-safe tool calling helpers +├── env.ts # Environment configuration +└── views/ + └── youcom-search-results/ + └── index.tsx # React search results UI +``` + +### Key Components + +1. **YouComSearchClient**: Handles API communication, authentication, and error handling +2. **MCP Tool Registration**: Defines the tool schema and implementation +3. **React UI Component**: Interactive search interface with real-time updates +4. **Type Safety**: Full TypeScript support with proper type inference + +### Integration Patterns + +The example follows Skybridge's established patterns: + +- Uses `McpServer.registerTool()` for tool definition +- Implements structured content for model consumption +- Provides rich UI views for human interaction +- Includes proper error handling and user feedback +- Supports both keyless and authenticated operation modes + +## Development + +```bash +# Install dependencies +npm install + +# Development with hot reload +npm run dev + +# Development with tunnel (for ChatGPT/Claude testing) +npm run dev:tunnel + +# Build for production +npm run build + +# Start production server +npm start +``` + +## Integration with AI Assistants + +This MCP tool works seamlessly with: + +- **Claude Code**: Install via plugin marketplace +- **ChatGPT**: Deploy as MCP app +- **VSCode Extensions**: Via MCP protocol +- **Any MCP Client**: Standard MCP tool interface + +Ask your AI assistant to search for information: + +> "Use the youcom-search tool to find recent TypeScript best practices" + +> "Search for React 19 new features from the last month" + +> "Find documentation about MCP servers on GitHub" + +## License + +MIT - see the main Skybridge repository for details. diff --git a/examples/youcom-web-search/package.json b/examples/youcom-web-search/package.json new file mode 100644 index 000000000..f892ae090 --- /dev/null +++ b/examples/youcom-web-search/package.json @@ -0,0 +1,42 @@ +{ + "name": "skybridge-youcom-web-search-example", + "version": "0.0.1", + "private": true, + "description": "You.com Web Search MCP Tool Example", + "type": "module", + "scripts": { + "dev": "skybridge dev", + "dev:tunnel": "skybridge dev --tunnel", + "build": "skybridge build", + "start": "skybridge start" + }, + "dependencies": { + "@alpic-ai/insights": "^1.142.1", + "@modelcontextprotocol/sdk": "^1.29.0", + "@t3-oss/env-core": "^0.13.11", + "clsx": "^2.1.1", + "dotenv": "^17.4.2", + "express": "^5.2.1", + "lucide-react": "^0.562.0", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "react-router-dom": "^7.18.0", + "skybridge": "^1.1.0", + "tailwind-merge": "^3.6.0", + "tailwindcss": "^4.3.1", + "zod": "^4.4.3" + }, + "devDependencies": { + "@skybridge/devtools": "^1.2.3", + "@tailwindcss/vite": "^4.3.1", + "@types/express": "^5.0.6", + "@types/node": "^22.20.0", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.2", + "tsx": "^4.22.4", + "tw-animate-css": "^1.4.0", + "typescript": "^5.9.3", + "vite": "^8.1.5" + } +} diff --git a/examples/youcom-web-search/src/env.ts b/examples/youcom-web-search/src/env.ts new file mode 100644 index 000000000..1c0a08c80 --- /dev/null +++ b/examples/youcom-web-search/src/env.ts @@ -0,0 +1,13 @@ +import "dotenv/config"; + +import { createEnv } from "@t3-oss/env-core"; +import { z } from "zod"; + +export const env = createEnv({ + server: { + NODE_ENV: z.enum(["development", "production"]).default("development"), + YDC_API_KEY: z.string().optional(), + }, + runtimeEnv: process.env, + emptyStringAsUndefined: true, +}); diff --git a/examples/youcom-web-search/src/helpers.ts b/examples/youcom-web-search/src/helpers.ts new file mode 100644 index 000000000..9fb4d6fa0 --- /dev/null +++ b/examples/youcom-web-search/src/helpers.ts @@ -0,0 +1,4 @@ +import { generateHelpers } from "skybridge/web"; +import type { AppType } from "./server.js"; + +export const { useCallTool, useToolInfo } = generateHelpers(); diff --git a/examples/youcom-web-search/src/index.css b/examples/youcom-web-search/src/index.css new file mode 100644 index 000000000..ebeb83663 --- /dev/null +++ b/examples/youcom-web-search/src/index.css @@ -0,0 +1,96 @@ +@import "tailwindcss"; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); +} + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} + +/* Utility classes for result cards */ +.line-clamp-2 { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.line-clamp-3 { + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} diff --git a/examples/youcom-web-search/src/server.ts b/examples/youcom-web-search/src/server.ts new file mode 100644 index 000000000..eadbbd966 --- /dev/null +++ b/examples/youcom-web-search/src/server.ts @@ -0,0 +1,148 @@ +import { intentMiddleware } from "@alpic-ai/insights"; +import { McpServer } from "skybridge/server"; +import * as z from "zod"; +import { YouComSearchClient, type YouComSearchOptions } from "./youcom-client.js"; + +const client = new YouComSearchClient(); + +const server = new McpServer( + { + name: "youcom-web-search", + version: "0.0.1", + }, + { capabilities: {} }, +) + .mcpMiddleware(intentMiddleware()) + .registerTool( + { + name: "youcom-search", + description: + "Search the web using You.com for current information, news, and research. Returns structured results with titles, URLs, snippets, and source information. Supports both keyless operation (100 free searches/day) and authenticated mode with higher quotas when YDC_API_KEY is provided.", + inputSchema: { + query: z + .string() + .min(1) + .describe("Search query to find information on the web"), + count: z + .number() + .min(1) + .max(20) + .optional() + .default(10) + .describe("Number of search results to return (1-20, default: 10)"), + domains: z + .array(z.string()) + .optional() + .describe("Limit search to specific domains (e.g., ['github.com', 'stackoverflow.com'])"), + freshness: z + .enum(["hour", "day", "week", "month", "year"]) + .optional() + .describe("Filter by content freshness (hour, day, week, month, year)"), + safeSearch: z + .boolean() + .optional() + .default(true) + .describe("Enable safe search filtering (default: true)"), + }, + annotations: { + readOnlyHint: true, + openWorldHint: true, + destructiveHint: false, + }, + view: { + component: "youcom-search-results", + description: "You.com web search results with rich formatting", + csp: { + resourceDomains: [ + "https://via.placeholder.com", + "https://www.google.com/s2/favicons", + ], + }, + }, + _meta: { + "openai/widgetAccessible": true, + }, + }, + async ({ query, count = 10, domains, freshness, safeSearch = true }) => { + try { + const searchOptions: YouComSearchOptions = { + query, + count, + domains, + freshness, + safeSearch, + }; + + const searchResponse = await client.search(searchOptions); + const allResults = [ + ...(searchResponse.results.web || []), + ...(searchResponse.results.news || []), + ]; + + return { + _meta: { + searchMeta: searchResponse.searchMeta, + resultCount: allResults.length, + keylessMode: !client.apiKey, + }, + structuredContent: { + query: searchResponse.query, + results: allResults, + searchOptions, + }, + content: [ + { + type: "text", + text: formatResultsForModel(searchResponse, allResults), + }, + ], + isError: false, + }; + } catch (error) { + const message = error instanceof Error ? error.message : "Search failed"; + + return { + _meta: { + error: message, + keylessMode: !client.apiKey, + }, + structuredContent: { + error: message, + query, + }, + content: [ + { + type: "text", + text: `Search failed: ${message}`, + }, + ], + isError: true, + }; + } + }, + ); + +function formatResultsForModel(searchResponse: any, results: any[]): string { + const parts = [ + `Found ${results.length} results for "${searchResponse.query}":`, + ]; + + results.forEach((result, index) => { + parts.push( + `${index + 1}. ${result.title}`, + ` URL: ${result.url}`, + ` ${result.snippet}`, + ` Source: ${result.domain || "Unknown"}`, + "" + ); + }); + + if (results.length === 0) { + parts.push("No results found. Try a different search query."); + } + + return parts.join("\n"); +} + +export default await server.run(); +export type AppType = typeof server; diff --git a/examples/youcom-web-search/src/views/youcom-search-results/index.tsx b/examples/youcom-web-search/src/views/youcom-search-results/index.tsx new file mode 100644 index 000000000..d47bfa7e7 --- /dev/null +++ b/examples/youcom-web-search/src/views/youcom-search-results/index.tsx @@ -0,0 +1,221 @@ +import { ExternalLinkIcon, SearchIcon, ClockIcon, ShieldCheckIcon } from "lucide-react"; +import { useState } from "react"; +import type { YouComSearchResult } from "../../youcom-client.js"; +import { useCallTool } from "../../helpers.js"; + +interface SearchResultsProps { + query: string; + results: YouComSearchResult[]; + searchOptions?: { + count?: number; + domains?: string[]; + freshness?: string; + safeSearch?: boolean; + }; + searchMeta?: { + totalResults?: number; + searchTime?: string; + }; + keylessMode?: boolean; +} + +export default function YouComSearchResults({ + query: initialQuery, + results: initialResults, + searchOptions = {}, + searchMeta, + keylessMode +}: SearchResultsProps) { + const [query, setQuery] = useState(initialQuery || ""); + const [results, setResults] = useState(initialResults || []); + const [loading, setLoading] = useState(false); + const callTool = useCallTool(); + + const handleSearch = async () => { + if (!query.trim()) return; + + setLoading(true); + try { + const result = await callTool("youcom-search", { + query: query.trim(), + count: searchOptions.count || 10, + domains: searchOptions.domains, + freshness: searchOptions.freshness, + safeSearch: searchOptions.safeSearch, + }); + + if (result.structuredContent?.results) { + setResults(result.structuredContent.results); + } + } catch (error) { + console.error("Search failed:", error); + } finally { + setLoading(false); + } + }; + + const handleKeyPress = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + handleSearch(); + } + }; + + return ( +
+ {/* Header */} +
+
+ + You.com Web Search +
+

+ Powered by You.com • {keylessMode ? "Keyless Mode" : "Authenticated"} +

+
+ + {/* Search Bar */} +
+ setQuery(e.target.value)} + onKeyPress={handleKeyPress} + placeholder="Search the web..." + className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + disabled={loading} + /> + +
+ + {/* Search Options Display */} + {(searchOptions.domains || searchOptions.freshness || keylessMode) && ( +
+ {keylessMode && ( + + + Free Mode (100 searches/day) + + )} + {searchOptions.domains && ( + + Domains: {searchOptions.domains.join(", ")} + + )} + {searchOptions.freshness && ( + + + {searchOptions.freshness} + + )} +
+ )} + + {/* Search Meta */} + {searchMeta && ( +
+ {searchMeta.totalResults && `About ${searchMeta.totalResults.toLocaleString()} results`} + {searchMeta.searchTime && ` in ${searchMeta.searchTime}`} +
+ )} + + {/* Results */} + {results.length > 0 ? ( +
+ {results.map((result, index) => ( + + ))} +
+ ) : ( +
+ +

No results found. Try a different search query.

+
+ )} + + {/* Footer */} +
+

+ Search powered by{" "} + + You.com + + {keylessMode && ( + <> + {" • "} + + Get API key for higher quotas + + + )} +

+
+
+ ); +} + +interface SearchResultCardProps { + result: YouComSearchResult; + index: number; +} + +function SearchResultCard({ result, index }: SearchResultCardProps) { + const handleClick = () => { + window.open(result.url, "_blank", "noopener,noreferrer"); + }; + + return ( +
+
+
+ {index + 1} +
+
+
+ {result.favicon && ( + { + const target = e.target as HTMLImageElement; + target.style.display = "none"; + }} + /> + )} + {result.domain} + +
+

+ {result.title} +

+

{result.snippet}

+
{result.url}
+
+
+
+ ); +} diff --git a/examples/youcom-web-search/src/youcom-client.ts b/examples/youcom-web-search/src/youcom-client.ts new file mode 100644 index 000000000..07a957305 --- /dev/null +++ b/examples/youcom-web-search/src/youcom-client.ts @@ -0,0 +1,141 @@ +import { env } from "./env.js"; + +export interface YouComSearchResult { + title: string; + url: string; + snippet: string; + favicon?: string; + domain?: string; +} + +export interface YouComSearchResponse { + results: { + web?: YouComSearchResult[]; + news?: YouComSearchResult[]; + }; + query: string; + searchMeta?: { + totalResults?: number; + searchTime?: string; + }; +} + +export interface YouComSearchOptions { + query: string; + count?: number; + domains?: string[]; + freshness?: string; + safeSearch?: boolean; +} + +export class YouComSearchClient { + private readonly baseUrl = "https://api.you.com/v1/agents/search"; + private readonly apiKey?: string; + + constructor() { + this.apiKey = env.YDC_API_KEY; + } + + async search(options: YouComSearchOptions): Promise { + const { query, count = 10, domains, freshness, safeSearch } = options; + + const searchParams = new URLSearchParams({ + query, + count: count.toString(), + }); + + if (domains && domains.length > 0) { + searchParams.set("domains", domains.join(",")); + } + + if (freshness) { + searchParams.set("freshness", freshness); + } + + if (safeSearch !== undefined) { + searchParams.set("safesearch", safeSearch.toString()); + } + + const url = `${this.baseUrl}?${searchParams.toString()}`; + + const headers: Record = { + "Accept": "application/json", + "User-Agent": "Skybridge-YouCom-Integration/1.0", + }; + + // Add API key if available for authenticated requests + if (this.apiKey) { + headers["Authorization"] = `Bearer ${this.apiKey}`; + } + + try { + const response = await fetch(url, { + method: "GET", + headers, + }); + + if (!response.ok) { + // Handle specific error cases + if (response.status === 401) { + throw new Error("Invalid You.com API key. Check your YDC_API_KEY environment variable."); + } else if (response.status === 429) { + const message = this.apiKey + ? "You.com API rate limit exceeded. Please try again later." + : "You.com rate limit exceeded. Consider setting YDC_API_KEY for higher quotas."; + throw new Error(message); + } else if (response.status >= 500) { + throw new Error("You.com service is temporarily unavailable. Please try again later."); + } else { + throw new Error(`Search failed: ${response.status} ${response.statusText}`); + } + } + + const data = await response.json(); + + // Validate and format response + return this.formatResponse(data, query); + } catch (error) { + if (error instanceof Error) { + throw error; + } + throw new Error("Unexpected error occurred while searching"); + } + } + + private formatResponse(data: any, query: string): YouComSearchResponse { + // Handle both direct results and nested results structure + const results = data.results || data; + + return { + query, + results: { + web: this.formatResults(results.web || results.results || []), + news: this.formatResults(results.news || []), + }, + searchMeta: { + totalResults: data.searchMeta?.totalResults || results.web?.length || 0, + searchTime: data.searchMeta?.searchTime, + }, + }; + } + + private formatResults(results: any[]): YouComSearchResult[] { + if (!Array.isArray(results)) return []; + + return results.map((result) => ({ + title: result.title || result.name || "Untitled", + url: result.url || result.link || "", + snippet: result.snippet || result.description || "", + favicon: result.favicon, + domain: result.domain || this.extractDomain(result.url || result.link || ""), + })); + } + + private extractDomain(url: string): string { + try { + return new URL(url).hostname; + } catch { + return ""; + } + } +} diff --git a/examples/youcom-web-search/tsconfig.json b/examples/youcom-web-search/tsconfig.json new file mode 100644 index 000000000..4d0130cd7 --- /dev/null +++ b/examples/youcom-web-search/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../packages/core/tsconfig.base.json", + "compilerOptions": { + "allowSyntheticDefaultImports": true + } +} diff --git a/examples/youcom-web-search/vite.config.ts b/examples/youcom-web-search/vite.config.ts new file mode 100644 index 000000000..147e46d63 --- /dev/null +++ b/examples/youcom-web-search/vite.config.ts @@ -0,0 +1,8 @@ +import tailwindcss from "@tailwindcss/vite"; +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; +import { skybridge } from "skybridge/vite"; + +export default defineConfig({ + plugins: [react(), tailwindcss(), skybridge()], +}); From 8fd5670ef075f7459f8af94b9b920026ddeb13db Mon Sep 17 00:00:00 2001 From: mouse-value-add Date: Tue, 4 Aug 2026 09:06:57 +0000 Subject: [PATCH 2/3] Fix encapsulation issues from Greptile review - add hasApiKey getter and update usage --- examples/youcom-web-search/src/server.ts | 4 +- .../src/views/youcom-search-results/index.tsx | 168 +++++++++--------- .../youcom-web-search/src/youcom-client.ts | 49 ++--- 3 files changed, 115 insertions(+), 106 deletions(-) diff --git a/examples/youcom-web-search/src/server.ts b/examples/youcom-web-search/src/server.ts index eadbbd966..133b22280 100644 --- a/examples/youcom-web-search/src/server.ts +++ b/examples/youcom-web-search/src/server.ts @@ -83,7 +83,7 @@ const server = new McpServer( _meta: { searchMeta: searchResponse.searchMeta, resultCount: allResults.length, - keylessMode: !client.apiKey, + keylessMode: !client.hasApiKey, }, structuredContent: { query: searchResponse.query, @@ -104,7 +104,7 @@ const server = new McpServer( return { _meta: { error: message, - keylessMode: !client.apiKey, + keylessMode: !client.hasApiKey, }, structuredContent: { error: message, diff --git a/examples/youcom-web-search/src/views/youcom-search-results/index.tsx b/examples/youcom-web-search/src/views/youcom-search-results/index.tsx index d47bfa7e7..44ee49eee 100644 --- a/examples/youcom-web-search/src/views/youcom-search-results/index.tsx +++ b/examples/youcom-web-search/src/views/youcom-search-results/index.tsx @@ -1,11 +1,11 @@ -import { ExternalLinkIcon, SearchIcon, ClockIcon, ShieldCheckIcon } from "lucide-react"; -import { useState } from "react"; -import type { YouComSearchResult } from "../../youcom-client.js"; -import { useCallTool } from "../../helpers.js"; +import { ExternalLinkIcon, SearchIcon, ClockIcon, ShieldCheckIcon } from \"lucide-react\"; +import { useState } from \"react\"; +import type { YouComSearchResult } from \"../../youcom-client.js\"; +import { useCallTool, useToolInfo } from \"../../helpers.js\"; interface SearchResultsProps { - query: string; - results: YouComSearchResult[]; + query?: string; + results?: YouComSearchResult[]; searchOptions?: { count?: number; domains?: string[]; @@ -19,150 +19,154 @@ interface SearchResultsProps { keylessMode?: boolean; } -export default function YouComSearchResults({ - query: initialQuery, - results: initialResults, - searchOptions = {}, - searchMeta, - keylessMode -}: SearchResultsProps) { - const [query, setQuery] = useState(initialQuery || ""); - const [results, setResults] = useState(initialResults || []); +export default function YouComSearchResults(props: SearchResultsProps = {}) { + // Get initial data from tool output via Skybridge hooks + const { output, responseMetadata } = useToolInfo(); + + // Extract initial data from tool output or use props as fallback + const initialQuery = output?.structuredContent?.query || props.query || \"\"; + const initialResults = output?.structuredContent?.results || props.results || []; + const initialSearchMeta = responseMetadata?.searchMeta || props.searchMeta; + const initialKeylessMode = responseMetadata?.keylessMode ?? props.keylessMode ?? false; + const initialSearchOptions = output?.structuredContent?.searchOptions || props.searchOptions || {}; + + const [query, setQuery] = useState(initialQuery); + const [results, setResults] = useState(initialResults); const [loading, setLoading] = useState(false); - const callTool = useCallTool(); + const { callTool } = useCallTool(\"youcom-search\"); const handleSearch = async () => { if (!query.trim()) return; setLoading(true); try { - const result = await callTool("youcom-search", { + const result = await callTool(\"youcom-search\", { query: query.trim(), - count: searchOptions.count || 10, - domains: searchOptions.domains, - freshness: searchOptions.freshness, - safeSearch: searchOptions.safeSearch, + count: initialSearchOptions.count || 10, + domains: initialSearchOptions.domains, + freshness: initialSearchOptions.freshness, + safeSearch: initialSearchOptions.safeSearch, }); if (result.structuredContent?.results) { setResults(result.structuredContent.results); } } catch (error) { - console.error("Search failed:", error); + console.error(\"Search failed:\", error); } finally { setLoading(false); } }; const handleKeyPress = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { + if (e.key === \"Enter\") { handleSearch(); } }; return ( -
+
{/* Header */} -
-
- +
+
+ You.com Web Search
-

- Powered by You.com • {keylessMode ? "Keyless Mode" : "Authenticated"} +

+ Powered by You.com • {initialKeylessMode ? \"Keyless Mode\" : \"Authenticated\"}

{/* Search Bar */} -
+
setQuery(e.target.value)} onKeyPress={handleKeyPress} - placeholder="Search the web..." - className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder=\"Search the web...\" + className=\"flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent\" disabled={loading} />
{/* Search Options Display */} - {(searchOptions.domains || searchOptions.freshness || keylessMode) && ( -
- {keylessMode && ( - - + {(initialSearchOptions.domains || initialSearchOptions.freshness || initialKeylessMode) && ( +
+ {initialKeylessMode && ( + + Free Mode (100 searches/day) )} - {searchOptions.domains && ( - - Domains: {searchOptions.domains.join(", ")} + {initialSearchOptions.domains && ( + + Domains: {initialSearchOptions.domains.join(\", \")} )} - {searchOptions.freshness && ( - - - {searchOptions.freshness} + {initialSearchOptions.freshness && ( + + + {initialSearchOptions.freshness} )}
)} {/* Search Meta */} - {searchMeta && ( -
- {searchMeta.totalResults && `About ${searchMeta.totalResults.toLocaleString()} results`} - {searchMeta.searchTime && ` in ${searchMeta.searchTime}`} + {initialSearchMeta && ( +
+ {initialSearchMeta.totalResults && `About ${initialSearchMeta.totalResults.toLocaleString()} results`} + {initialSearchMeta.searchTime && ` in ${initialSearchMeta.searchTime}`}
)} {/* Results */} {results.length > 0 ? ( -
+
{results.map((result, index) => ( ))}
) : ( -
- +
+

No results found. Try a different search query.

)} {/* Footer */} -
+

- Search powered by{" "} + Search powered by{\" \"} You.com - {keylessMode && ( + {initialKeylessMode && ( <> - {" • "} + {\" • \"} Get API key for higher quotas @@ -181,41 +185,41 @@ interface SearchResultCardProps { function SearchResultCard({ result, index }: SearchResultCardProps) { const handleClick = () => { - window.open(result.url, "_blank", "noopener,noreferrer"); + window.open(result.url, \"_blank\", \"noopener,noreferrer\"); }; return (

-
-
+
+
{index + 1}
-
-
+
+
{result.favicon && ( { const target = e.target as HTMLImageElement; - target.style.display = "none"; + target.style.display = \"none\"; }} /> )} - {result.domain} - + {result.domain} +
-

+

{result.title}

-

{result.snippet}

-
{result.url}
+

{result.snippet}

+
{result.url}
); -} +} \ No newline at end of file diff --git a/examples/youcom-web-search/src/youcom-client.ts b/examples/youcom-web-search/src/youcom-client.ts index 07a957305..6284fcbf3 100644 --- a/examples/youcom-web-search/src/youcom-client.ts +++ b/examples/youcom-web-search/src/youcom-client.ts @@ -1,4 +1,4 @@ -import { env } from "./env.js"; +import { env } from \"./env.js\"; export interface YouComSearchResult { title: string; @@ -29,13 +29,18 @@ export interface YouComSearchOptions { } export class YouComSearchClient { - private readonly baseUrl = "https://api.you.com/v1/agents/search"; + private readonly baseUrl = \"https://api.you.com/v1/agents/search\"; private readonly apiKey?: string; constructor() { this.apiKey = env.YDC_API_KEY; } + // Public getter for checking if API key is available + get hasApiKey(): boolean { + return this.apiKey !== undefined && this.apiKey.trim() !== \"\"; + } + async search(options: YouComSearchOptions): Promise { const { query, count = 10, domains, freshness, safeSearch } = options; @@ -45,46 +50,46 @@ export class YouComSearchClient { }); if (domains && domains.length > 0) { - searchParams.set("domains", domains.join(",")); + searchParams.set(\"domains\", domains.join(\",\")); } if (freshness) { - searchParams.set("freshness", freshness); + searchParams.set(\"freshness\", freshness); } if (safeSearch !== undefined) { - searchParams.set("safesearch", safeSearch.toString()); + searchParams.set(\"safesearch\", safeSearch.toString()); } const url = `${this.baseUrl}?${searchParams.toString()}`; const headers: Record = { - "Accept": "application/json", - "User-Agent": "Skybridge-YouCom-Integration/1.0", + \"Accept\": \"application/json\", + \"User-Agent\": \"Skybridge-YouCom-Integration/1.0\", }; // Add API key if available for authenticated requests - if (this.apiKey) { - headers["Authorization"] = `Bearer ${this.apiKey}`; + if (this.hasApiKey) { + headers[\"Authorization\"] = `Bearer ${this.apiKey}`; } try { const response = await fetch(url, { - method: "GET", + method: \"GET\", headers, }); if (!response.ok) { // Handle specific error cases if (response.status === 401) { - throw new Error("Invalid You.com API key. Check your YDC_API_KEY environment variable."); + throw new Error(\"Invalid You.com API key. Check your YDC_API_KEY environment variable.\"); } else if (response.status === 429) { - const message = this.apiKey - ? "You.com API rate limit exceeded. Please try again later." - : "You.com rate limit exceeded. Consider setting YDC_API_KEY for higher quotas."; + const message = this.hasApiKey + ? \"You.com API rate limit exceeded. Please try again later.\" + : \"You.com rate limit exceeded. Consider setting YDC_API_KEY for higher quotas.\"; throw new Error(message); } else if (response.status >= 500) { - throw new Error("You.com service is temporarily unavailable. Please try again later."); + throw new Error(\"You.com service is temporarily unavailable. Please try again later.\"); } else { throw new Error(`Search failed: ${response.status} ${response.statusText}`); } @@ -98,7 +103,7 @@ export class YouComSearchClient { if (error instanceof Error) { throw error; } - throw new Error("Unexpected error occurred while searching"); + throw new Error(\"Unexpected error occurred while searching\"); } } @@ -123,11 +128,11 @@ export class YouComSearchClient { if (!Array.isArray(results)) return []; return results.map((result) => ({ - title: result.title || result.name || "Untitled", - url: result.url || result.link || "", - snippet: result.snippet || result.description || "", + title: result.title || result.name || \"Untitled\", + url: result.url || result.link || \"\", + snippet: result.snippet || result.description || \"\", favicon: result.favicon, - domain: result.domain || this.extractDomain(result.url || result.link || ""), + domain: result.domain || this.extractDomain(result.url || result.link || \"\"), })); } @@ -135,7 +140,7 @@ export class YouComSearchClient { try { return new URL(url).hostname; } catch { - return ""; + return \"\"; } } -} +} \ No newline at end of file From 8d23e2df544af414b7efee287e277c7b279707f3 Mon Sep 17 00:00:00 2001 From: mouse-value-add Date: Mon, 10 Aug 2026 09:05:15 +0000 Subject: [PATCH 3/3] Fix Greptile review issues properly - clean implementation - Add hasApiKey getter to YouComSearchClient for proper encapsulation - Update server.ts to use hasApiKey instead of accessing private apiKey - Fix React component hooks usage in youcom-search-results component - Update tsconfig.json to match other examples pattern - Restore files from corruption and reimplement fixes cleanly - Build passes all checks --- .../src/views/youcom-search-results/index.tsx | 197 ++++++++-------- .../youcom-web-search/src/youcom-client.ts | 42 ++-- examples/youcom-web-search/tsconfig.json | 11 +- examples/youcom-web-search/vite.config.js | 8 + examples/youcom-web-search/vite.config.js.map | 1 + package-lock.json | 223 ++++++++++++++++++ pnpm-lock.yaml | 79 +++++++ 7 files changed, 434 insertions(+), 127 deletions(-) create mode 100644 examples/youcom-web-search/vite.config.js create mode 100644 examples/youcom-web-search/vite.config.js.map create mode 100644 package-lock.json diff --git a/examples/youcom-web-search/src/views/youcom-search-results/index.tsx b/examples/youcom-web-search/src/views/youcom-search-results/index.tsx index 44ee49eee..676efe82e 100644 --- a/examples/youcom-web-search/src/views/youcom-search-results/index.tsx +++ b/examples/youcom-web-search/src/views/youcom-search-results/index.tsx @@ -1,15 +1,15 @@ -import { ExternalLinkIcon, SearchIcon, ClockIcon, ShieldCheckIcon } from \"lucide-react\"; -import { useState } from \"react\"; -import type { YouComSearchResult } from \"../../youcom-client.js\"; -import { useCallTool, useToolInfo } from \"../../helpers.js\"; +import { ExternalLinkIcon, SearchIcon, ClockIcon, ShieldCheckIcon } from "lucide-react"; +import React, { useState } from "react"; +import type { YouComSearchResult } from "../../youcom-client.js"; +import { useCallTool } from "../../helpers.js"; interface SearchResultsProps { - query?: string; - results?: YouComSearchResult[]; + query: string; + results: YouComSearchResult[]; searchOptions?: { count?: number; domains?: string[]; - freshness?: string; + freshness?: "hour" | "day" | "week" | "month" | "year"; safeSearch?: boolean; }; searchMeta?: { @@ -19,154 +19,145 @@ interface SearchResultsProps { keylessMode?: boolean; } -export default function YouComSearchResults(props: SearchResultsProps = {}) { - // Get initial data from tool output via Skybridge hooks - const { output, responseMetadata } = useToolInfo(); - - // Extract initial data from tool output or use props as fallback - const initialQuery = output?.structuredContent?.query || props.query || \"\"; - const initialResults = output?.structuredContent?.results || props.results || []; - const initialSearchMeta = responseMetadata?.searchMeta || props.searchMeta; - const initialKeylessMode = responseMetadata?.keylessMode ?? props.keylessMode ?? false; - const initialSearchOptions = output?.structuredContent?.searchOptions || props.searchOptions || {}; - - const [query, setQuery] = useState(initialQuery); - const [results, setResults] = useState(initialResults); - const [loading, setLoading] = useState(false); - const { callTool } = useCallTool(\"youcom-search\"); +export default function YouComSearchResults({ + query: initialQuery, + results: initialResults, + searchOptions = {}, + searchMeta, + keylessMode +}: SearchResultsProps) { + const [query, setQuery] = useState(initialQuery || ""); + const [results, setResults] = useState(initialResults || []); + const { data, isPending, callTool } = useCallTool("youcom-search"); const handleSearch = async () => { if (!query.trim()) return; - setLoading(true); - try { - const result = await callTool(\"youcom-search\", { - query: query.trim(), - count: initialSearchOptions.count || 10, - domains: initialSearchOptions.domains, - freshness: initialSearchOptions.freshness, - safeSearch: initialSearchOptions.safeSearch, - }); - - if (result.structuredContent?.results) { - setResults(result.structuredContent.results); - } - } catch (error) { - console.error(\"Search failed:\", error); - } finally { - setLoading(false); - } + callTool({ + query: query.trim(), + count: searchOptions.count || 10, + domains: searchOptions.domains, + freshness: searchOptions.freshness, + safeSearch: searchOptions.safeSearch !== undefined ? searchOptions.safeSearch : true, + }); }; + // Update results when data changes + React.useEffect(() => { + if (data?.structuredContent?.results) { + setResults(data.structuredContent.results); + } + }, [data]); + const handleKeyPress = (e: React.KeyboardEvent) => { - if (e.key === \"Enter\") { + if (e.key === "Enter") { handleSearch(); } }; return ( -
+
{/* Header */} -
-
- +
+
+ You.com Web Search
-

- Powered by You.com • {initialKeylessMode ? \"Keyless Mode\" : \"Authenticated\"} +

+ Powered by You.com • {keylessMode ? "Keyless Mode" : "Authenticated"}

{/* Search Bar */} -
+
setQuery(e.target.value)} onKeyPress={handleKeyPress} - placeholder=\"Search the web...\" - className=\"flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent\" - disabled={loading} + placeholder="Search the web..." + className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + disabled={isPending} />
{/* Search Options Display */} - {(initialSearchOptions.domains || initialSearchOptions.freshness || initialKeylessMode) && ( -
- {initialKeylessMode && ( - - + {(searchOptions.domains || searchOptions.freshness || keylessMode) && ( +
+ {keylessMode && ( + + Free Mode (100 searches/day) )} - {initialSearchOptions.domains && ( - - Domains: {initialSearchOptions.domains.join(\", \")} + {searchOptions.domains && ( + + Domains: {searchOptions.domains.join(", ")} )} - {initialSearchOptions.freshness && ( - - - {initialSearchOptions.freshness} + {searchOptions.freshness && ( + + + {searchOptions.freshness} )}
)} {/* Search Meta */} - {initialSearchMeta && ( -
- {initialSearchMeta.totalResults && `About ${initialSearchMeta.totalResults.toLocaleString()} results`} - {initialSearchMeta.searchTime && ` in ${initialSearchMeta.searchTime}`} + {searchMeta && ( +
+ {searchMeta.totalResults && `About ${searchMeta.totalResults.toLocaleString()} results`} + {searchMeta.searchTime && ` in ${searchMeta.searchTime}`}
)} {/* Results */} {results.length > 0 ? ( -
+
{results.map((result, index) => ( ))}
) : ( -
- +
+

No results found. Try a different search query.

)} {/* Footer */} -
+

- Search powered by{\" \"} + Search powered by{" "} You.com - {initialKeylessMode && ( + {keylessMode && ( <> - {\" • \"} + {" • "} Get API key for higher quotas @@ -185,41 +176,41 @@ interface SearchResultCardProps { function SearchResultCard({ result, index }: SearchResultCardProps) { const handleClick = () => { - window.open(result.url, \"_blank\", \"noopener,noreferrer\"); + window.open(result.url, "_blank", "noopener,noreferrer"); }; return (

-
-
+
+
{index + 1}
-
-
+
+
{result.favicon && ( \"\" { const target = e.target as HTMLImageElement; - target.style.display = \"none\"; + target.style.display = "none"; }} /> )} - {result.domain} - + {result.domain} +
-

+

{result.title}

-

{result.snippet}

-
{result.url}
+

{result.snippet}

+
{result.url}
); -} \ No newline at end of file +} diff --git a/examples/youcom-web-search/src/youcom-client.ts b/examples/youcom-web-search/src/youcom-client.ts index 6284fcbf3..6f8c8aa76 100644 --- a/examples/youcom-web-search/src/youcom-client.ts +++ b/examples/youcom-web-search/src/youcom-client.ts @@ -1,4 +1,4 @@ -import { env } from \"./env.js\"; +import { env } from "./env.js"; export interface YouComSearchResult { title: string; @@ -29,7 +29,7 @@ export interface YouComSearchOptions { } export class YouComSearchClient { - private readonly baseUrl = \"https://api.you.com/v1/agents/search\"; + private readonly baseUrl = "https://api.you.com/v1/agents/search"; private readonly apiKey?: string; constructor() { @@ -38,7 +38,7 @@ export class YouComSearchClient { // Public getter for checking if API key is available get hasApiKey(): boolean { - return this.apiKey !== undefined && this.apiKey.trim() !== \"\"; + return this.apiKey !== undefined && this.apiKey.trim() !== ""; } async search(options: YouComSearchOptions): Promise { @@ -50,46 +50,46 @@ export class YouComSearchClient { }); if (domains && domains.length > 0) { - searchParams.set(\"domains\", domains.join(\",\")); + searchParams.set("domains", domains.join(",")); } if (freshness) { - searchParams.set(\"freshness\", freshness); + searchParams.set("freshness", freshness); } if (safeSearch !== undefined) { - searchParams.set(\"safesearch\", safeSearch.toString()); + searchParams.set("safesearch", safeSearch.toString()); } const url = `${this.baseUrl}?${searchParams.toString()}`; const headers: Record = { - \"Accept\": \"application/json\", - \"User-Agent\": \"Skybridge-YouCom-Integration/1.0\", + "Accept": "application/json", + "User-Agent": "Skybridge-YouCom-Integration/1.0", }; // Add API key if available for authenticated requests if (this.hasApiKey) { - headers[\"Authorization\"] = `Bearer ${this.apiKey}`; + headers["Authorization"] = `Bearer ${this.apiKey}`; } try { const response = await fetch(url, { - method: \"GET\", + method: "GET", headers, }); if (!response.ok) { // Handle specific error cases if (response.status === 401) { - throw new Error(\"Invalid You.com API key. Check your YDC_API_KEY environment variable.\"); + throw new Error("Invalid You.com API key. Check your YDC_API_KEY environment variable."); } else if (response.status === 429) { const message = this.hasApiKey - ? \"You.com API rate limit exceeded. Please try again later.\" - : \"You.com rate limit exceeded. Consider setting YDC_API_KEY for higher quotas.\"; + ? "You.com API rate limit exceeded. Please try again later." + : "You.com rate limit exceeded. Consider setting YDC_API_KEY for higher quotas."; throw new Error(message); } else if (response.status >= 500) { - throw new Error(\"You.com service is temporarily unavailable. Please try again later.\"); + throw new Error("You.com service is temporarily unavailable. Please try again later."); } else { throw new Error(`Search failed: ${response.status} ${response.statusText}`); } @@ -103,7 +103,7 @@ export class YouComSearchClient { if (error instanceof Error) { throw error; } - throw new Error(\"Unexpected error occurred while searching\"); + throw new Error("Unexpected error occurred while searching"); } } @@ -128,11 +128,11 @@ export class YouComSearchClient { if (!Array.isArray(results)) return []; return results.map((result) => ({ - title: result.title || result.name || \"Untitled\", - url: result.url || result.link || \"\", - snippet: result.snippet || result.description || \"\", + title: result.title || result.name || "Untitled", + url: result.url || result.link || "", + snippet: result.snippet || result.description || "", favicon: result.favicon, - domain: result.domain || this.extractDomain(result.url || result.link || \"\"), + domain: result.domain || this.extractDomain(result.url || result.link || ""), })); } @@ -140,7 +140,7 @@ export class YouComSearchClient { try { return new URL(url).hostname; } catch { - return \"\"; + return ""; } } -} \ No newline at end of file +} diff --git a/examples/youcom-web-search/tsconfig.json b/examples/youcom-web-search/tsconfig.json index 4d0130cd7..0b2c66f70 100644 --- a/examples/youcom-web-search/tsconfig.json +++ b/examples/youcom-web-search/tsconfig.json @@ -1,6 +1,11 @@ { - "extends": "../../packages/core/tsconfig.base.json", + "extends": "skybridge/tsconfig", + "compilerOptions": { - "allowSyntheticDefaultImports": true - } + "paths": { + "@/*": ["./src/*"] + } + }, + + "include": ["src", ".skybridge/**/*.d.ts"] } diff --git a/examples/youcom-web-search/vite.config.js b/examples/youcom-web-search/vite.config.js new file mode 100644 index 000000000..1f64b975a --- /dev/null +++ b/examples/youcom-web-search/vite.config.js @@ -0,0 +1,8 @@ +import tailwindcss from "@tailwindcss/vite"; +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; +import { skybridge } from "skybridge/vite"; +export default defineConfig({ + plugins: [react(), tailwindcss(), skybridge()], +}); +//# sourceMappingURL=vite.config.js.map \ No newline at end of file diff --git a/examples/youcom-web-search/vite.config.js.map b/examples/youcom-web-search/vite.config.js.map new file mode 100644 index 000000000..6e790ba9d --- /dev/null +++ b/examples/youcom-web-search/vite.config.js.map @@ -0,0 +1 @@ +{"version":3,"file":"vite.config.js","sourceRoot":"","sources":["vite.config.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,mBAAmB,CAAC;AAC5C,OAAO,KAAK,MAAM,sBAAsB,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,eAAe,YAAY,CAAC;IAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,WAAW,EAAE,EAAE,SAAS,EAAE,CAAC;CAC/C,CAAC,CAAC"} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..1db646847 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,223 @@ +{ + "name": "@skybridge/monorepo", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@skybridge/monorepo", + "version": "0.0.0", + "license": "ISC", + "devDependencies": { + "@biomejs/biome": "^2.5.4", + "@total-typescript/tsconfig": "^1.0.4", + "@types/node": "^24.13.3", + "typescript": "^6.0.3" + }, + "engines": { + "node": ">=24.18.0" + } + }, + "node_modules/@biomejs/biome": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.5.7.tgz", + "integrity": "sha512-zr8K/DcY5tYsQOQwqMJ0AWElo6QgmgNI7idXgXLhevVszlt8RGVpesEJPqx3ThazLaOwjJ5Y8fz3BtH5fGZNsw==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.5.7", + "@biomejs/cli-darwin-x64": "2.5.7", + "@biomejs/cli-linux-arm64": "2.5.7", + "@biomejs/cli-linux-arm64-musl": "2.5.7", + "@biomejs/cli-linux-x64": "2.5.7", + "@biomejs/cli-linux-x64-musl": "2.5.7", + "@biomejs/cli-win32-arm64": "2.5.7", + "@biomejs/cli-win32-x64": "2.5.7" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.5.7.tgz", + "integrity": "sha512-vxo/Ls3/PYdQWyLhYYcgMOCzQypAjcY+iihS8M0wW03l16TCLW4zqZzGo75gm1VdCMj38hTVZ31KBWrZ4G9dJw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.5.7.tgz", + "integrity": "sha512-Cd3Ga61amT/Yl/0x8elP5hhGYaFy4bw6WuysTgf7oo8TA5tJ5A1k+DkVoJ2BHbTVil51gTX9VPzArnrlLJ3Kyg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.5.7.tgz", + "integrity": "sha512-rR2QE0yF2GYSuYuKIa7pKvODGJqnOH+2eDREAM8wV+mWKSkMQKdAp4zXEZfTaxY8PMoNONnpgSWcBCyLDPDOKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.7.tgz", + "integrity": "sha512-xPI5yB6XlpDbNkS+bm1t42olw5c4l3UrlOmLg7KtLJvjvkNF/1V4tnUgfkylGIeb3u/T+BzMGYqgQhzjAoJzuQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.5.7.tgz", + "integrity": "sha512-FQgqJhscrqJUFptGaRSUJWlXAExwWcDwLuK49dvKfkQ1bB5SEEyFssnsxQY83Xm6jR0EbbX3+8+D5bfvYqUG2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.7.tgz", + "integrity": "sha512-rE5VZi+qtmPgQH+l7jVxYoZ18b/TiHEhulhMpjmCZH1PltSbjRcxNWywC3HZ9tYottG7ORkeTtoscBilKSBm0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.5.7.tgz", + "integrity": "sha512-Oq4x0CCwP4jirrcTywXs5kOGZ4v5vuEP+gWrbtjApOA2CL9F3F9GlIdQIci8AKSCa/zURanMRpX/4wQ7Am6hHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.5.7.tgz", + "integrity": "sha512-V+0wu/nrj2S+MhP4EQ0uHNolP0IALEsz45pg0WoKkHfDeh0+ItHwP/p7bX5RPoMOl9NkpHYWdYPhIcy2mACHvQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@total-typescript/tsconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@total-typescript/tsconfig/-/tsconfig-1.0.4.tgz", + "integrity": "sha512-fO4ctMPGz1kOFOQ4RCPBRBfMy3gDn+pegUfrGyUFRMv/Rd0ZM3/SHH3hFCYG4u6bPLG8OlmOGcBLDexvyr3A5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b35219261..4f18a8ab1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1371,6 +1371,85 @@ importers: specifier: ^5.9.3 version: 5.9.3 + examples/youcom-web-search: + dependencies: + '@alpic-ai/insights': + specifier: ^1.142.1 + version: 1.158.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(react@19.2.7)(skybridge@packages+core) + '@modelcontextprotocol/sdk': + specifier: ^1.29.0 + version: 1.29.0(zod@4.4.3) + '@t3-oss/env-core': + specifier: ^0.13.11 + version: 0.13.11(arktype@2.1.27)(typescript@5.9.3)(zod@4.4.3) + clsx: + specifier: ^2.1.1 + version: 2.1.1 + dotenv: + specifier: ^17.4.2 + version: 17.4.2 + express: + specifier: ^5.2.1 + version: 5.2.1 + lucide-react: + specifier: ^0.562.0 + version: 0.562.0(react@19.2.7) + react: + specifier: ^19.2.7 + version: 19.2.7 + react-dom: + specifier: ^19.2.7 + version: 19.2.7(react@19.2.7) + react-router-dom: + specifier: ^7.18.0 + version: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + skybridge: + specifier: workspace:* + version: link:../../packages/core + tailwind-merge: + specifier: ^3.6.0 + version: 3.6.0 + tailwindcss: + specifier: ^4.3.1 + version: 4.3.3 + zod: + specifier: ^4.4.3 + version: 4.4.3 + devDependencies: + '@skybridge/devtools': + specifier: ^1.2.3 + version: 1.2.7(arktype@2.1.27)(typescript@5.9.3) + '@tailwindcss/vite': + specifier: ^4.3.1 + version: 4.3.3(vite@8.1.5(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.44.1)(tsx@4.23.1)(yaml@2.9.0)) + '@types/express': + specifier: ^5.0.6 + version: 5.0.6 + '@types/node': + specifier: ^22.20.0 + version: 22.20.1 + '@types/react': + specifier: ^19.2.17 + version: 19.2.17 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.17) + '@vitejs/plugin-react': + specifier: ^6.0.2 + version: 6.0.3(vite@8.1.5(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.44.1)(tsx@4.23.1)(yaml@2.9.0)) + tsx: + specifier: ^4.22.4 + version: 4.23.1 + tw-animate-css: + specifier: ^1.4.0 + version: 1.4.0 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vite: + specifier: ^8.1.5 + version: 8.1.5(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.44.1)(tsx@4.23.1)(yaml@2.9.0) + infrastructure: dependencies: aws-cdk-lib: