Clean up .gitignore: remove binaries (images, video, audio, PDFs) from tracking
@ -1,5 +0,0 @@
|
|||||||
# Browser Use MCP Environment Variables
|
|
||||||
# DO NOT COMMIT TO PUBLIC REPOS
|
|
||||||
|
|
||||||
BROWSER_USE_API_KEY=not_set
|
|
||||||
|
|
||||||
110
.gitignore
vendored
@ -1,29 +1,87 @@
|
|||||||
|
|
||||||
.DS_Store
|
|
||||||
.env
|
|
||||||
.idea/
|
|
||||||
.reonomy-credentials.*
|
|
||||||
.vscode/
|
|
||||||
*.env
|
|
||||||
*.env.local
|
|
||||||
*.log
|
|
||||||
*.png
|
|
||||||
*.swo
|
|
||||||
*.swp
|
|
||||||
/tmp/reonomy-*.html
|
|
||||||
/tmp/reonomy-*.json
|
|
||||||
/tmp/reonomy-*.png
|
|
||||||
/tmp/reonomy-*.txt
|
|
||||||
# Credentials
|
|
||||||
# IDE files
|
|
||||||
# Logs
|
|
||||||
# Node modules
|
|
||||||
# OS files
|
# OS files
|
||||||
# Screenshots
|
.DS_Store
|
||||||
# Test files
|
|
||||||
node_modules/
|
|
||||||
npm-debug.log*
|
|
||||||
package-lock.json
|
|
||||||
reonomy-scraper.log
|
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
|
# IDE files
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Environment / Secrets
|
||||||
|
.env
|
||||||
|
*.env
|
||||||
|
*.env.*
|
||||||
|
.reonomy-credentials.*
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
reonomy-scraper.log
|
||||||
|
|
||||||
|
# Node
|
||||||
|
node_modules/
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
# ========================
|
||||||
|
# Binary / Media Files
|
||||||
|
# (These don't belong in git)
|
||||||
|
# ========================
|
||||||
|
|
||||||
|
# Images
|
||||||
|
*.png
|
||||||
|
*.jpg
|
||||||
|
*.jpeg
|
||||||
|
*.gif
|
||||||
|
*.webp
|
||||||
|
*.ico
|
||||||
|
*.svg
|
||||||
|
*.bmp
|
||||||
|
*.tiff
|
||||||
|
|
||||||
|
# Video
|
||||||
|
*.mp4
|
||||||
|
*.webm
|
||||||
|
*.mov
|
||||||
|
*.avi
|
||||||
|
*.mkv
|
||||||
|
*.wmv
|
||||||
|
|
||||||
|
# Audio
|
||||||
|
*.wav
|
||||||
|
*.mp3
|
||||||
|
*.ogg
|
||||||
|
*.flac
|
||||||
|
*.aac
|
||||||
|
*.m4a
|
||||||
|
*.caf
|
||||||
|
|
||||||
|
# Documents / PDFs
|
||||||
|
*.pdf
|
||||||
|
|
||||||
|
# Archives
|
||||||
|
*.zip
|
||||||
|
*.tar
|
||||||
|
*.gz
|
||||||
|
*.rar
|
||||||
|
*.7z
|
||||||
|
|
||||||
|
# Compiled / Binary
|
||||||
|
*.pyc
|
||||||
|
__pycache__/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# ========================
|
||||||
|
# Project-Specific
|
||||||
|
# ========================
|
||||||
|
|
||||||
|
# Reonomy
|
||||||
|
/tmp/reonomy-*
|
||||||
|
reonomy-auth.json
|
||||||
|
reonomy-daily-stats.json
|
||||||
|
|
||||||
|
# Frameworks
|
||||||
pageindex-framework/
|
pageindex-framework/
|
||||||
|
|
||||||
|
# Temp files
|
||||||
|
/tmp/
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 399 KiB |
|
Before Width: | Height: | Size: 328 KiB |
|
Before Width: | Height: | Size: 391 KiB |
|
Before Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 379 KiB |
|
Before Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 225 KiB |
|
Before Width: | Height: | Size: 351 KiB |
|
Before Width: | Height: | Size: 337 KiB |
|
Before Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 312 KiB |
|
Before Width: | Height: | Size: 628 KiB |
|
Before Width: | Height: | Size: 797 KiB |
|
Before Width: | Height: | Size: 671 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 35 KiB |
1
das-surya-review/lyrics/01-skin-intro.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Take a step to do it. Yes. Yes! You did it! I didn't know all the feelings I could miss. Until I looked inside my mind I've been lost since. I don't know how to fit in my own skin. Don't feel at home in this world I'm living in.
|
||||||
1
das-surya-review/lyrics/02-u-saved-me.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
There's no me, there's just us When I said it, I swear I meant it You were always enough I didn't mean to take you for granted You saved me from my broken soul We gave each other full control I left you crying on the road We both know that the night gets cold We gave each other full control We both know that the night gets cold You call me crazy, I'm just faded I'm escaping from your clutch I'm just chasing sweet sedation Using your heart as my crutch Self-inflated expectations Never met or overcome Now I'm jaded, complicated No surprise you chose to run You saved me from my broken soul We gave each other full control I left you crying on the road We both know that the night gets cold We gave each other full control We both know that the night gets cold You saved me from my broken soul We gave each other full control I left you crying on the road We both know that the night gets cold
|
||||||
1
das-surya-review/lyrics/03-nothing.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
I lost my heart, now I feel nothing It's been a while since I've felt something The blame cuts deep and thoughts start crushing I ease the pain with self-destruction I've been so out of touch Mistaken kindness for affection, it's not love Have I been cursed with bad luck? Cause my stars don't align with my sun I lost my heart, now I feel nothing It's been a while since I've felt something The blame cuts deep and thoughts start crushing I ease the pain with self-destruction
|
||||||
1
das-surya-review/lyrics/04-sweet-relief.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
The teardrop in your eye Contains a single lethal dose You're lying by my side But I can't seem to find a pulse The days keep passing by This love's a waste of time, you know Another sleepless night And I might need to overdose Please don't go Won't you stay? Please don't go Won't you stay? You've got me on my knees I'm begging for sweet relief Each time you go You break a piece of my soul You cut me then stomp the blade And convince me you're all I need I'm seeing ghosts They've got their hands around my throat You keep on turning little nothings into somethings You throw in pillows slamming undeserving doors Your words cut deeper than a knife Can't even look me in the eye So please just go Please just go Don't you stay Please just go Don't you stay You've got me on my knees I'm begging for sweet relief Each time you go You break a piece of my soul You cut me then stomp the blade And convince me you're all I need I'm seeing ghosts They've got their hands around my throat You cut me then stomp the blade And convince me you're all I need
|
||||||
1
das-surya-review/lyrics/05-tiptoe.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
To your right is the Baguio Botanical Garden To your left is the Baguio Botanical Garden To your right is the Baguio Botanical Garden To your right is the Baguio Botanical Garden To your left is the Baguio Botanical Garden To your right is the Baguio Botanical Garden To your left is the Baguio Botanical Garden To your right is the Baguio Botanical Garden To your left is the Baguio Botanical Garden To your right is the Baguio Botanical Garden To your left is the Baguio Botanical Garden To your right is the Baguio Botanical Garden
|
||||||
1
das-surya-review/lyrics/06-natures-call.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
We are turning right to Session Road We are turning right to Session Road Are you still there? If so, we'd like to thank you for joining us in today's drive. Thank you for joining us on this drive. We are turning right to Session Road Are you still there? If so, we'd like to thank you for joining us in today's drive.
|
||||||
|
Before Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 227 KiB |
|
Before Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 204 KiB |
@ -1,11 +0,0 @@
|
|||||||
# GoHighLevel API Configuration
|
|
||||||
GHL_API_KEY=your_private_integration_api_key_here
|
|
||||||
GHL_BASE_URL=https://services.leadconnectorhq.com
|
|
||||||
GHL_LOCATION_ID=your_location_id_here
|
|
||||||
|
|
||||||
# Server Configuration
|
|
||||||
MCP_SERVER_PORT=8000
|
|
||||||
NODE_ENV=development
|
|
||||||
|
|
||||||
# Optional: For AI features
|
|
||||||
OPENAI_API_KEY=your_openai_key_here_optional
|
|
||||||
654
mcp-diagrams/ghl-mcp-apps-only/dist/apps/index.js
vendored
@ -1,654 +0,0 @@
|
|||||||
/**
|
|
||||||
* MCP Apps Manager
|
|
||||||
* Manages rich UI components for GoHighLevel MCP Server
|
|
||||||
*/
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as path from 'path';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
/**
|
|
||||||
* MCP Apps Manager class
|
|
||||||
* Registers app tools and handles structuredContent responses
|
|
||||||
*/
|
|
||||||
// ESM equivalent of __dirname
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
|
||||||
const __dirname = path.dirname(__filename);
|
|
||||||
// Resolve UI build path - works regardless of working directory
|
|
||||||
function getUIBuildPath() {
|
|
||||||
// When compiled, this file is at dist/apps/index.js
|
|
||||||
// UI files are at dist/app-ui/
|
|
||||||
const fromDist = path.resolve(__dirname, '..', 'app-ui');
|
|
||||||
if (fs.existsSync(fromDist)) {
|
|
||||||
return fromDist;
|
|
||||||
}
|
|
||||||
// Fallback: try process.cwd() based paths
|
|
||||||
const appUiPath = path.join(process.cwd(), 'dist', 'app-ui');
|
|
||||||
if (fs.existsSync(appUiPath)) {
|
|
||||||
return appUiPath;
|
|
||||||
}
|
|
||||||
// Default fallback
|
|
||||||
return fromDist;
|
|
||||||
}
|
|
||||||
export class MCPAppsManager {
|
|
||||||
ghlClient;
|
|
||||||
resourceHandlers = new Map();
|
|
||||||
uiBuildPath;
|
|
||||||
constructor(ghlClient) {
|
|
||||||
this.ghlClient = ghlClient;
|
|
||||||
this.uiBuildPath = getUIBuildPath();
|
|
||||||
process.stderr.write(`[MCP Apps] UI build path: ${this.uiBuildPath}\n`);
|
|
||||||
this.registerResourceHandlers();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Register all UI resource handlers
|
|
||||||
*/
|
|
||||||
registerResourceHandlers() {
|
|
||||||
const resources = [
|
|
||||||
// All 11 MCP Apps
|
|
||||||
{ uri: 'ui://ghl/mcp-app', file: 'mcp-app.html' },
|
|
||||||
{ uri: 'ui://ghl/pipeline-board', file: 'pipeline-board.html' },
|
|
||||||
{ uri: 'ui://ghl/quick-book', file: 'quick-book.html' },
|
|
||||||
{ uri: 'ui://ghl/opportunity-card', file: 'opportunity-card.html' },
|
|
||||||
{ uri: 'ui://ghl/contact-grid', file: 'contact-grid.html' },
|
|
||||||
{ uri: 'ui://ghl/calendar-view', file: 'calendar-view.html' },
|
|
||||||
{ uri: 'ui://ghl/invoice-preview', file: 'invoice-preview.html' },
|
|
||||||
{ uri: 'ui://ghl/campaign-stats', file: 'campaign-stats.html' },
|
|
||||||
{ uri: 'ui://ghl/agent-stats', file: 'agent-stats.html' },
|
|
||||||
{ uri: 'ui://ghl/contact-timeline', file: 'contact-timeline.html' },
|
|
||||||
{ uri: 'ui://ghl/workflow-status', file: 'workflow-status.html' },
|
|
||||||
];
|
|
||||||
for (const resource of resources) {
|
|
||||||
this.resourceHandlers.set(resource.uri, {
|
|
||||||
uri: resource.uri,
|
|
||||||
mimeType: 'text/html;profile=mcp-app',
|
|
||||||
getContent: () => this.loadUIResource(resource.file),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Load UI resource from build directory
|
|
||||||
*/
|
|
||||||
loadUIResource(filename) {
|
|
||||||
const filePath = path.join(this.uiBuildPath, filename);
|
|
||||||
try {
|
|
||||||
return fs.readFileSync(filePath, 'utf-8');
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
process.stderr.write(`[MCP Apps] UI resource not found: ${filePath}\n`);
|
|
||||||
return this.getFallbackHTML(filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Generate fallback HTML when UI resource is not built
|
|
||||||
*/
|
|
||||||
getFallbackHTML(filename) {
|
|
||||||
const componentName = filename.replace('.html', '');
|
|
||||||
return `
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>GHL ${componentName}</title>
|
|
||||||
<style>
|
|
||||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 20px; }
|
|
||||||
.fallback { text-align: center; color: #666; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="fallback">
|
|
||||||
<p>UI component "${componentName}" is loading...</p>
|
|
||||||
<p>Run <code>npm run build:ui</code> to build UI components.</p>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
window.addEventListener('message', (e) => {
|
|
||||||
if (e.data?.type === 'mcp-app-init') {
|
|
||||||
console.log('MCP App data:', e.data.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`.trim();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Get tool definitions for all app tools
|
|
||||||
*/
|
|
||||||
getToolDefinitions() {
|
|
||||||
return [
|
|
||||||
// 1. Contact Grid - search and display contacts
|
|
||||||
{
|
|
||||||
name: 'view_contact_grid',
|
|
||||||
description: 'Display contact search results in a data grid with sorting and pagination. Returns a visual UI component.',
|
|
||||||
inputSchema: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
query: { type: 'string', description: 'Search query string' },
|
|
||||||
limit: { type: 'number', description: 'Maximum results (default: 25)' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_meta: {
|
|
||||||
ui: { resourceUri: 'ui://ghl/contact-grid' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 2. Pipeline Board - Kanban view of opportunities
|
|
||||||
{
|
|
||||||
name: 'view_pipeline_board',
|
|
||||||
description: 'Display a pipeline as an interactive Kanban board with opportunities. Returns a visual UI component.',
|
|
||||||
inputSchema: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
pipelineId: { type: 'string', description: 'Pipeline ID to display' }
|
|
||||||
},
|
|
||||||
required: ['pipelineId']
|
|
||||||
},
|
|
||||||
_meta: {
|
|
||||||
ui: { resourceUri: 'ui://ghl/pipeline-board' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 3. Quick Book - appointment booking
|
|
||||||
{
|
|
||||||
name: 'view_quick_book',
|
|
||||||
description: 'Display a quick booking interface for scheduling appointments. Returns a visual UI component.',
|
|
||||||
inputSchema: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
calendarId: { type: 'string', description: 'Calendar ID for booking' },
|
|
||||||
contactId: { type: 'string', description: 'Optional contact ID to pre-fill' }
|
|
||||||
},
|
|
||||||
required: ['calendarId']
|
|
||||||
},
|
|
||||||
_meta: {
|
|
||||||
ui: { resourceUri: 'ui://ghl/quick-book' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 4. Opportunity Card - single opportunity details
|
|
||||||
{
|
|
||||||
name: 'view_opportunity_card',
|
|
||||||
description: 'Display a single opportunity with details, value, and stage info. Returns a visual UI component.',
|
|
||||||
inputSchema: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
opportunityId: { type: 'string', description: 'Opportunity ID to display' }
|
|
||||||
},
|
|
||||||
required: ['opportunityId']
|
|
||||||
},
|
|
||||||
_meta: {
|
|
||||||
ui: { resourceUri: 'ui://ghl/opportunity-card' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 5. Calendar View - calendar with events
|
|
||||||
{
|
|
||||||
name: 'view_calendar',
|
|
||||||
description: 'Display a calendar with events and appointments. Returns a visual UI component.',
|
|
||||||
inputSchema: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
calendarId: { type: 'string', description: 'Calendar ID to display' },
|
|
||||||
startDate: { type: 'string', description: 'Start date (ISO format)' },
|
|
||||||
endDate: { type: 'string', description: 'End date (ISO format)' }
|
|
||||||
},
|
|
||||||
required: ['calendarId']
|
|
||||||
},
|
|
||||||
_meta: {
|
|
||||||
ui: { resourceUri: 'ui://ghl/calendar-view' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 6. Invoice Preview - invoice details
|
|
||||||
{
|
|
||||||
name: 'view_invoice',
|
|
||||||
description: 'Display an invoice preview with line items and payment status. Returns a visual UI component.',
|
|
||||||
inputSchema: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
invoiceId: { type: 'string', description: 'Invoice ID to display' }
|
|
||||||
},
|
|
||||||
required: ['invoiceId']
|
|
||||||
},
|
|
||||||
_meta: {
|
|
||||||
ui: { resourceUri: 'ui://ghl/invoice-preview' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 7. Campaign Stats - campaign performance metrics
|
|
||||||
{
|
|
||||||
name: 'view_campaign_stats',
|
|
||||||
description: 'Display campaign statistics and performance metrics. Returns a visual UI component.',
|
|
||||||
inputSchema: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
campaignId: { type: 'string', description: 'Campaign ID to display stats for' }
|
|
||||||
},
|
|
||||||
required: ['campaignId']
|
|
||||||
},
|
|
||||||
_meta: {
|
|
||||||
ui: { resourceUri: 'ui://ghl/campaign-stats' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 8. Agent Stats - agent/user performance
|
|
||||||
{
|
|
||||||
name: 'view_agent_stats',
|
|
||||||
description: 'Display agent/user performance statistics and metrics. Returns a visual UI component.',
|
|
||||||
inputSchema: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
userId: { type: 'string', description: 'User/Agent ID to display stats for' },
|
|
||||||
dateRange: { type: 'string', description: 'Date range (e.g., "last7days", "last30days")' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_meta: {
|
|
||||||
ui: { resourceUri: 'ui://ghl/agent-stats' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 9. Contact Timeline - activity history for a contact
|
|
||||||
{
|
|
||||||
name: 'view_contact_timeline',
|
|
||||||
description: 'Display a contact\'s activity timeline with all interactions. Returns a visual UI component.',
|
|
||||||
inputSchema: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
contactId: { type: 'string', description: 'Contact ID to display timeline for' }
|
|
||||||
},
|
|
||||||
required: ['contactId']
|
|
||||||
},
|
|
||||||
_meta: {
|
|
||||||
ui: { resourceUri: 'ui://ghl/contact-timeline' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 10. Workflow Status - workflow execution status
|
|
||||||
{
|
|
||||||
name: 'view_workflow_status',
|
|
||||||
description: 'Display workflow execution status and history. Returns a visual UI component.',
|
|
||||||
inputSchema: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
workflowId: { type: 'string', description: 'Workflow ID to display status for' }
|
|
||||||
},
|
|
||||||
required: ['workflowId']
|
|
||||||
},
|
|
||||||
_meta: {
|
|
||||||
ui: { resourceUri: 'ui://ghl/workflow-status' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 11. MCP App - generic/main dashboard
|
|
||||||
{
|
|
||||||
name: 'view_dashboard',
|
|
||||||
description: 'Display the main GHL dashboard overview. Returns a visual UI component.',
|
|
||||||
inputSchema: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {}
|
|
||||||
},
|
|
||||||
_meta: {
|
|
||||||
ui: { resourceUri: 'ui://ghl/mcp-app' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 12. Update Opportunity - action tool for UI to update opportunities
|
|
||||||
{
|
|
||||||
name: 'update_opportunity',
|
|
||||||
description: 'Update an opportunity (move to stage, change value, status, etc.)',
|
|
||||||
inputSchema: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
opportunityId: { type: 'string', description: 'Opportunity ID to update' },
|
|
||||||
pipelineStageId: { type: 'string', description: 'New stage ID (for moving)' },
|
|
||||||
name: { type: 'string', description: 'Opportunity name' },
|
|
||||||
monetaryValue: { type: 'number', description: 'Monetary value' },
|
|
||||||
status: { type: 'string', enum: ['open', 'won', 'lost', 'abandoned'], description: 'Opportunity status' }
|
|
||||||
},
|
|
||||||
required: ['opportunityId']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Get app tool names for routing
|
|
||||||
*/
|
|
||||||
getAppToolNames() {
|
|
||||||
return [
|
|
||||||
'view_contact_grid',
|
|
||||||
'view_pipeline_board',
|
|
||||||
'view_quick_book',
|
|
||||||
'view_opportunity_card',
|
|
||||||
'view_calendar',
|
|
||||||
'view_invoice',
|
|
||||||
'view_campaign_stats',
|
|
||||||
'view_agent_stats',
|
|
||||||
'view_contact_timeline',
|
|
||||||
'view_workflow_status',
|
|
||||||
'view_dashboard',
|
|
||||||
'update_opportunity'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Check if a tool is an app tool
|
|
||||||
*/
|
|
||||||
isAppTool(toolName) {
|
|
||||||
return this.getAppToolNames().includes(toolName);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Execute an app tool
|
|
||||||
*/
|
|
||||||
async executeTool(toolName, args) {
|
|
||||||
process.stderr.write(`[MCP Apps] Executing app tool: ${toolName}\n`);
|
|
||||||
switch (toolName) {
|
|
||||||
case 'view_contact_grid':
|
|
||||||
return await this.viewContactGrid(args.query, args.limit);
|
|
||||||
case 'view_pipeline_board':
|
|
||||||
return await this.viewPipelineBoard(args.pipelineId);
|
|
||||||
case 'view_quick_book':
|
|
||||||
return await this.viewQuickBook(args.calendarId, args.contactId);
|
|
||||||
case 'view_opportunity_card':
|
|
||||||
return await this.viewOpportunityCard(args.opportunityId);
|
|
||||||
case 'view_calendar':
|
|
||||||
return await this.viewCalendar(args.calendarId, args.startDate, args.endDate);
|
|
||||||
case 'view_invoice':
|
|
||||||
return await this.viewInvoice(args.invoiceId);
|
|
||||||
case 'view_campaign_stats':
|
|
||||||
return await this.viewCampaignStats(args.campaignId);
|
|
||||||
case 'view_agent_stats':
|
|
||||||
return await this.viewAgentStats(args.userId, args.dateRange);
|
|
||||||
case 'view_contact_timeline':
|
|
||||||
return await this.viewContactTimeline(args.contactId);
|
|
||||||
case 'view_workflow_status':
|
|
||||||
return await this.viewWorkflowStatus(args.workflowId);
|
|
||||||
case 'view_dashboard':
|
|
||||||
return await this.viewDashboard();
|
|
||||||
case 'update_opportunity':
|
|
||||||
return await this.updateOpportunity(args);
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown app tool: ${toolName}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* View contact grid (search results)
|
|
||||||
*/
|
|
||||||
async viewContactGrid(query, limit) {
|
|
||||||
const response = await this.ghlClient.searchContacts({
|
|
||||||
locationId: this.ghlClient.getConfig().locationId,
|
|
||||||
query: query,
|
|
||||||
limit: limit || 25
|
|
||||||
});
|
|
||||||
if (!response.success) {
|
|
||||||
throw new Error(response.error?.message || 'Failed to search contacts');
|
|
||||||
}
|
|
||||||
const data = response.data;
|
|
||||||
const resourceHandler = this.resourceHandlers.get('ui://ghl/contact-grid');
|
|
||||||
return this.createAppResult(`Found ${data?.contacts?.length || 0} contacts`, resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), data);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* View pipeline board (Kanban)
|
|
||||||
*/
|
|
||||||
async viewPipelineBoard(pipelineId) {
|
|
||||||
const [pipelinesResponse, opportunitiesResponse] = await Promise.all([
|
|
||||||
this.ghlClient.getPipelines(),
|
|
||||||
this.ghlClient.searchOpportunities({
|
|
||||||
location_id: this.ghlClient.getConfig().locationId,
|
|
||||||
pipeline_id: pipelineId
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
if (!pipelinesResponse.success) {
|
|
||||||
throw new Error(pipelinesResponse.error?.message || 'Failed to get pipeline');
|
|
||||||
}
|
|
||||||
const pipeline = pipelinesResponse.data?.pipelines?.find((p) => p.id === pipelineId);
|
|
||||||
const opportunities = opportunitiesResponse.data?.opportunities || [];
|
|
||||||
// Simplify opportunity data to only include fields the UI needs (reduces payload size)
|
|
||||||
const simplifiedOpportunities = opportunities.map((opp) => ({
|
|
||||||
id: opp.id,
|
|
||||||
name: opp.name || 'Untitled',
|
|
||||||
pipelineStageId: opp.pipelineStageId,
|
|
||||||
status: opp.status || 'open',
|
|
||||||
monetaryValue: opp.monetaryValue || 0,
|
|
||||||
contact: opp.contact ? {
|
|
||||||
name: opp.contact.name || 'Unknown',
|
|
||||||
email: opp.contact.email,
|
|
||||||
phone: opp.contact.phone
|
|
||||||
} : { name: 'Unknown' },
|
|
||||||
updatedAt: opp.updatedAt || opp.createdAt,
|
|
||||||
createdAt: opp.createdAt,
|
|
||||||
source: opp.source
|
|
||||||
}));
|
|
||||||
const data = {
|
|
||||||
pipeline,
|
|
||||||
opportunities: simplifiedOpportunities,
|
|
||||||
stages: pipeline?.stages || []
|
|
||||||
};
|
|
||||||
const resourceHandler = this.resourceHandlers.get('ui://ghl/pipeline-board');
|
|
||||||
return this.createAppResult(`Pipeline: ${pipeline?.name || 'Unknown'} (${opportunities.length} opportunities)`, resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), data);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* View quick book interface
|
|
||||||
*/
|
|
||||||
async viewQuickBook(calendarId, contactId) {
|
|
||||||
const [calendarResponse, contactResponse] = await Promise.all([
|
|
||||||
this.ghlClient.getCalendar(calendarId),
|
|
||||||
contactId ? this.ghlClient.getContact(contactId) : Promise.resolve({ success: true, data: null })
|
|
||||||
]);
|
|
||||||
if (!calendarResponse.success) {
|
|
||||||
throw new Error(calendarResponse.error?.message || 'Failed to get calendar');
|
|
||||||
}
|
|
||||||
const data = {
|
|
||||||
calendar: calendarResponse.data,
|
|
||||||
contact: contactResponse.data,
|
|
||||||
locationId: this.ghlClient.getConfig().locationId
|
|
||||||
};
|
|
||||||
const resourceHandler = this.resourceHandlers.get('ui://ghl/quick-book');
|
|
||||||
return this.createAppResult(`Quick booking for calendar: ${calendarResponse.data?.name || calendarId}`, resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), data);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* View opportunity card
|
|
||||||
*/
|
|
||||||
async viewOpportunityCard(opportunityId) {
|
|
||||||
const response = await this.ghlClient.getOpportunity(opportunityId);
|
|
||||||
if (!response.success) {
|
|
||||||
throw new Error(response.error?.message || 'Failed to get opportunity');
|
|
||||||
}
|
|
||||||
const opportunity = response.data;
|
|
||||||
const resourceHandler = this.resourceHandlers.get('ui://ghl/opportunity-card');
|
|
||||||
return this.createAppResult(`Opportunity: ${opportunity?.name || opportunityId}`, resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), opportunity);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* View calendar
|
|
||||||
*/
|
|
||||||
async viewCalendar(calendarId, startDate, endDate) {
|
|
||||||
const now = new Date();
|
|
||||||
const start = startDate || new Date(now.getFullYear(), now.getMonth(), 1).toISOString();
|
|
||||||
const end = endDate || new Date(now.getFullYear(), now.getMonth() + 1, 0).toISOString();
|
|
||||||
const [calendarResponse, eventsResponse] = await Promise.all([
|
|
||||||
this.ghlClient.getCalendar(calendarId),
|
|
||||||
this.ghlClient.getCalendarEvents({
|
|
||||||
calendarId: calendarId,
|
|
||||||
startTime: start,
|
|
||||||
endTime: end,
|
|
||||||
locationId: this.ghlClient.getConfig().locationId
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
if (!calendarResponse.success) {
|
|
||||||
throw new Error(calendarResponse.error?.message || 'Failed to get calendar');
|
|
||||||
}
|
|
||||||
const calendar = calendarResponse.data;
|
|
||||||
const data = {
|
|
||||||
calendar: calendarResponse.data,
|
|
||||||
events: eventsResponse.data?.events || [],
|
|
||||||
startDate: start,
|
|
||||||
endDate: end
|
|
||||||
};
|
|
||||||
const resourceHandler = this.resourceHandlers.get('ui://ghl/calendar-view');
|
|
||||||
return this.createAppResult(`Calendar: ${calendar?.name || 'Unknown'} (${data.events.length} events)`, resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), data);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* View campaign stats
|
|
||||||
*/
|
|
||||||
async viewCampaignStats(campaignId) {
|
|
||||||
// Get email campaigns
|
|
||||||
const response = await this.ghlClient.getEmailCampaigns({});
|
|
||||||
const campaigns = response.data?.schedules || [];
|
|
||||||
const campaign = campaigns.find((c) => c.id === campaignId) || { id: campaignId };
|
|
||||||
const data = {
|
|
||||||
campaign,
|
|
||||||
campaigns,
|
|
||||||
campaignId,
|
|
||||||
locationId: this.ghlClient.getConfig().locationId
|
|
||||||
};
|
|
||||||
const resourceHandler = this.resourceHandlers.get('ui://ghl/campaign-stats');
|
|
||||||
return this.createAppResult(`Campaign stats: ${campaign?.name || campaignId}`, resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), data);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* View agent stats
|
|
||||||
*/
|
|
||||||
async viewAgentStats(userId, dateRange) {
|
|
||||||
// Get location info which may include user data
|
|
||||||
const locationResponse = await this.ghlClient.getLocationById(this.ghlClient.getConfig().locationId);
|
|
||||||
const data = {
|
|
||||||
userId,
|
|
||||||
dateRange: dateRange || 'last30days',
|
|
||||||
location: locationResponse.data,
|
|
||||||
locationId: this.ghlClient.getConfig().locationId
|
|
||||||
};
|
|
||||||
const resourceHandler = this.resourceHandlers.get('ui://ghl/agent-stats');
|
|
||||||
return this.createAppResult(userId ? `Agent stats: ${userId}` : 'Agent overview', resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), data);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* View contact timeline
|
|
||||||
*/
|
|
||||||
async viewContactTimeline(contactId) {
|
|
||||||
const [contactResponse, notesResponse, tasksResponse] = await Promise.all([
|
|
||||||
this.ghlClient.getContact(contactId),
|
|
||||||
this.ghlClient.getContactNotes(contactId),
|
|
||||||
this.ghlClient.getContactTasks(contactId)
|
|
||||||
]);
|
|
||||||
if (!contactResponse.success) {
|
|
||||||
throw new Error(contactResponse.error?.message || 'Failed to get contact');
|
|
||||||
}
|
|
||||||
const contact = contactResponse.data;
|
|
||||||
const data = {
|
|
||||||
contact: contactResponse.data,
|
|
||||||
notes: notesResponse.data || [],
|
|
||||||
tasks: tasksResponse.data || []
|
|
||||||
};
|
|
||||||
const resourceHandler = this.resourceHandlers.get('ui://ghl/contact-timeline');
|
|
||||||
return this.createAppResult(`Timeline for ${contact?.firstName || ''} ${contact?.lastName || ''}`, resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), data);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* View workflow status
|
|
||||||
*/
|
|
||||||
async viewWorkflowStatus(workflowId) {
|
|
||||||
const response = await this.ghlClient.getWorkflows({
|
|
||||||
locationId: this.ghlClient.getConfig().locationId
|
|
||||||
});
|
|
||||||
const workflows = response.data?.workflows || [];
|
|
||||||
const workflow = workflows.find((w) => w.id === workflowId) || { id: workflowId };
|
|
||||||
const data = {
|
|
||||||
workflow,
|
|
||||||
workflows,
|
|
||||||
workflowId,
|
|
||||||
locationId: this.ghlClient.getConfig().locationId
|
|
||||||
};
|
|
||||||
const resourceHandler = this.resourceHandlers.get('ui://ghl/workflow-status');
|
|
||||||
return this.createAppResult(`Workflow: ${workflow?.name || workflowId}`, resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), data);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* View main dashboard
|
|
||||||
*/
|
|
||||||
async viewDashboard() {
|
|
||||||
const [contactsResponse, pipelinesResponse, calendarsResponse] = await Promise.all([
|
|
||||||
this.ghlClient.searchContacts({ locationId: this.ghlClient.getConfig().locationId, limit: 10 }),
|
|
||||||
this.ghlClient.getPipelines(),
|
|
||||||
this.ghlClient.getCalendars()
|
|
||||||
]);
|
|
||||||
const data = {
|
|
||||||
recentContacts: contactsResponse.data?.contacts || [],
|
|
||||||
pipelines: pipelinesResponse.data?.pipelines || [],
|
|
||||||
calendars: calendarsResponse.data?.calendars || [],
|
|
||||||
locationId: this.ghlClient.getConfig().locationId
|
|
||||||
};
|
|
||||||
const resourceHandler = this.resourceHandlers.get('ui://ghl/mcp-app');
|
|
||||||
return this.createAppResult('GHL Dashboard Overview', resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), data);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* View invoice
|
|
||||||
*/
|
|
||||||
async viewInvoice(invoiceId) {
|
|
||||||
const response = await this.ghlClient.getInvoice(invoiceId, {
|
|
||||||
altId: this.ghlClient.getConfig().locationId,
|
|
||||||
altType: 'location'
|
|
||||||
});
|
|
||||||
if (!response.success) {
|
|
||||||
throw new Error(response.error?.message || 'Failed to get invoice');
|
|
||||||
}
|
|
||||||
const invoice = response.data;
|
|
||||||
const resourceHandler = this.resourceHandlers.get('ui://ghl/invoice-preview');
|
|
||||||
return this.createAppResult(`Invoice #${invoice?.invoiceNumber || invoiceId} - ${invoice?.status || 'Unknown status'}`, resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), invoice);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Update opportunity (action tool for UI)
|
|
||||||
*/
|
|
||||||
async updateOpportunity(args) {
|
|
||||||
const { opportunityId, ...updates } = args;
|
|
||||||
// Build the update payload
|
|
||||||
const updatePayload = {};
|
|
||||||
if (updates.pipelineStageId)
|
|
||||||
updatePayload.pipelineStageId = updates.pipelineStageId;
|
|
||||||
if (updates.name)
|
|
||||||
updatePayload.name = updates.name;
|
|
||||||
if (updates.monetaryValue !== undefined)
|
|
||||||
updatePayload.monetaryValue = updates.monetaryValue;
|
|
||||||
if (updates.status)
|
|
||||||
updatePayload.status = updates.status;
|
|
||||||
process.stderr.write(`[MCP Apps] Updating opportunity ${opportunityId}: ${JSON.stringify(updatePayload)}\n`);
|
|
||||||
const response = await this.ghlClient.updateOpportunity(opportunityId, updatePayload);
|
|
||||||
if (!response.success) {
|
|
||||||
throw new Error(response.error?.message || 'Failed to update opportunity');
|
|
||||||
}
|
|
||||||
const opportunity = response.data;
|
|
||||||
return {
|
|
||||||
content: [{ type: 'text', text: `Updated opportunity: ${opportunity?.name || opportunityId}` }],
|
|
||||||
structuredContent: {
|
|
||||||
success: true,
|
|
||||||
opportunity: {
|
|
||||||
id: opportunity?.id,
|
|
||||||
name: opportunity?.name,
|
|
||||||
pipelineStageId: opportunity?.pipelineStageId,
|
|
||||||
monetaryValue: opportunity?.monetaryValue,
|
|
||||||
status: opportunity?.status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Create app tool result with structuredContent
|
|
||||||
*/
|
|
||||||
createAppResult(textSummary, resourceUri, mimeType, htmlContent, data) {
|
|
||||||
// structuredContent is the data object that gets passed to ontoolresult
|
|
||||||
// The UI accesses it via result.structuredContent
|
|
||||||
return {
|
|
||||||
content: [{ type: 'text', text: textSummary }],
|
|
||||||
structuredContent: data
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Inject data into HTML as a script tag
|
|
||||||
*/
|
|
||||||
injectDataIntoHTML(html, data) {
|
|
||||||
const dataScript = `<script>window.__MCP_APP_DATA__ = ${JSON.stringify(data)};</script>`;
|
|
||||||
// Insert before </head> or at the beginning of <body>
|
|
||||||
if (html.includes('</head>')) {
|
|
||||||
return html.replace('</head>', `${dataScript}</head>`);
|
|
||||||
}
|
|
||||||
else if (html.includes('<body>')) {
|
|
||||||
return html.replace('<body>', `<body>${dataScript}`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return dataScript + html;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Get resource handler by URI
|
|
||||||
*/
|
|
||||||
getResourceHandler(uri) {
|
|
||||||
return this.resourceHandlers.get(uri);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Get all registered resource URIs
|
|
||||||
*/
|
|
||||||
getResourceURIs() {
|
|
||||||
return Array.from(this.resourceHandlers.keys());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
139
mcp-diagrams/ghl-mcp-apps-only/dist/server.js
vendored
@ -1,139 +0,0 @@
|
|||||||
/**
|
|
||||||
* GoHighLevel MCP Apps Server (Slimmed Down)
|
|
||||||
* Only includes MCP Apps - no other tools
|
|
||||||
*/
|
|
||||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
||||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
||||||
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
||||||
import * as dotenv from 'dotenv';
|
|
||||||
import { GHLApiClient } from './clients/ghl-api-client.js';
|
|
||||||
import { MCPAppsManager } from './apps/index.js';
|
|
||||||
// Load environment variables
|
|
||||||
dotenv.config();
|
|
||||||
/**
|
|
||||||
* MCP Apps Only Server
|
|
||||||
*/
|
|
||||||
class GHLMCPAppsServer {
|
|
||||||
server;
|
|
||||||
ghlClient;
|
|
||||||
mcpAppsManager;
|
|
||||||
constructor() {
|
|
||||||
// Initialize MCP server with capabilities
|
|
||||||
this.server = new Server({
|
|
||||||
name: 'ghl-mcp-apps-only',
|
|
||||||
version: '1.0.0',
|
|
||||||
}, {
|
|
||||||
capabilities: {
|
|
||||||
tools: {},
|
|
||||||
resources: {},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// Initialize GHL API client
|
|
||||||
this.ghlClient = this.initializeGHLClient();
|
|
||||||
// Initialize MCP Apps Manager
|
|
||||||
this.mcpAppsManager = new MCPAppsManager(this.ghlClient);
|
|
||||||
this.setupHandlers();
|
|
||||||
this.setupErrorHandling();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Initialize the GHL API client with config from environment
|
|
||||||
*/
|
|
||||||
initializeGHLClient() {
|
|
||||||
const config = {
|
|
||||||
accessToken: process.env.GHL_API_KEY || '',
|
|
||||||
locationId: process.env.GHL_LOCATION_ID || '',
|
|
||||||
baseUrl: process.env.GHL_BASE_URL || 'https://services.leadconnectorhq.com',
|
|
||||||
version: '2021-07-28'
|
|
||||||
};
|
|
||||||
if (!config.accessToken) {
|
|
||||||
process.stderr.write('Warning: GHL_API_KEY not set in environment\n');
|
|
||||||
}
|
|
||||||
if (!config.locationId) {
|
|
||||||
process.stderr.write('Warning: GHL_LOCATION_ID not set in environment\n');
|
|
||||||
}
|
|
||||||
return new GHLApiClient(config);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Setup request handlers
|
|
||||||
*/
|
|
||||||
setupHandlers() {
|
|
||||||
// List tools - only MCP App tools
|
|
||||||
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
||||||
const appTools = this.mcpAppsManager.getToolDefinitions();
|
|
||||||
process.stderr.write(`[MCP Apps Only] Listing ${appTools.length} app tools\n`);
|
|
||||||
return { tools: appTools };
|
|
||||||
});
|
|
||||||
// List resources - MCP App UI resources
|
|
||||||
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
||||||
const resourceUris = this.mcpAppsManager.getResourceURIs();
|
|
||||||
const resources = resourceUris.map(uri => {
|
|
||||||
const handler = this.mcpAppsManager.getResourceHandler(uri);
|
|
||||||
return {
|
|
||||||
uri: uri,
|
|
||||||
name: uri,
|
|
||||||
mimeType: handler?.mimeType || 'text/html;profile=mcp-app'
|
|
||||||
};
|
|
||||||
});
|
|
||||||
process.stderr.write(`[MCP Apps Only] Listing ${resources.length} UI resources\n`);
|
|
||||||
return { resources };
|
|
||||||
});
|
|
||||||
// Read resource - serve UI HTML
|
|
||||||
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
||||||
const uri = request.params.uri;
|
|
||||||
process.stderr.write(`[MCP Apps Only] Reading resource: ${uri}\n`);
|
|
||||||
const handler = this.mcpAppsManager.getResourceHandler(uri);
|
|
||||||
if (!handler) {
|
|
||||||
throw new McpError(ErrorCode.InvalidRequest, `Resource not found: ${uri}`);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
contents: [{
|
|
||||||
uri: uri,
|
|
||||||
mimeType: handler.mimeType,
|
|
||||||
text: handler.getContent()
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
});
|
|
||||||
// Call tool - execute MCP App tools
|
|
||||||
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
||||||
const { name, arguments: args } = request.params;
|
|
||||||
process.stderr.write(`[MCP Apps Only] Calling tool: ${name}\n`);
|
|
||||||
if (!this.mcpAppsManager.isAppTool(name)) {
|
|
||||||
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const result = await this.mcpAppsManager.executeTool(name, args || {});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
process.stderr.write(`[MCP Apps Only] Tool error: ${error.message}\n`);
|
|
||||||
throw new McpError(ErrorCode.InternalError, error.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Setup error handling
|
|
||||||
*/
|
|
||||||
setupErrorHandling() {
|
|
||||||
this.server.onerror = (error) => {
|
|
||||||
process.stderr.write(`[MCP Apps Only] Server error: ${error}\n`);
|
|
||||||
};
|
|
||||||
process.on('SIGINT', async () => {
|
|
||||||
await this.server.close();
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Start the server
|
|
||||||
*/
|
|
||||||
async run() {
|
|
||||||
const transport = new StdioServerTransport();
|
|
||||||
await this.server.connect(transport);
|
|
||||||
process.stderr.write('[MCP Apps Only] Server started - Apps only mode\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Start server
|
|
||||||
const server = new GHLMCPAppsServer();
|
|
||||||
server.run().catch((error) => {
|
|
||||||
process.stderr.write(`Failed to start server: ${error}\n`);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
/**
|
|
||||||
* TypeScript interfaces for GoHighLevel API integration
|
|
||||||
* Based on official OpenAPI specifications v2021-07-28 (Contacts) and v2021-04-15 (Conversations)
|
|
||||||
*/
|
|
||||||
export {};
|
|
||||||
@ -1 +0,0 @@
|
|||||||
import{j as e,A as h,g as n,f as d,R as m,a as g}from"./styles-CphAgR3l.js";function o({contact:s}){const a=s.name||`${s.firstName||""} ${s.lastName||""}`.trim()||"Unknown Contact",i=n(s.firstName,s.lastName),r=()=>{const l=[s.address1,s.city,s.state,s.postalCode,s.country].filter(Boolean);return l.length>0?l.join(", "):null};return e.jsx("div",{className:"ghl-app",children:e.jsxs("div",{className:"ghl-card",children:[e.jsx("div",{className:"ghl-card-header",children:e.jsxs("div",{className:"ghl-flex ghl-items-center ghl-gap-4",children:[e.jsx("div",{className:"ghl-avatar ghl-avatar-lg",style:{background:x(a)},children:i}),e.jsxs("div",{children:[e.jsx("h2",{style:{fontSize:20,fontWeight:600,marginBottom:4},children:a}),s.companyName&&e.jsx("p",{className:"ghl-text-secondary",children:s.companyName})]})]})}),e.jsxs("div",{className:"ghl-card-body",children:[e.jsxs("div",{className:"ghl-grid ghl-grid-2",style:{gap:16},children:[s.email&&e.jsxs("div",{children:[e.jsx("label",{className:"ghl-text-sm ghl-text-muted",style:{display:"block",marginBottom:4},children:"Email"}),e.jsx("a",{href:`mailto:${s.email}`,style:{color:"var(--ghl-primary)",textDecoration:"none"},children:s.email})]}),s.phone&&e.jsxs("div",{children:[e.jsx("label",{className:"ghl-text-sm ghl-text-muted",style:{display:"block",marginBottom:4},children:"Phone"}),e.jsx("a",{href:`tel:${s.phone}`,style:{color:"var(--ghl-primary)",textDecoration:"none"},children:s.phone})]}),s.website&&e.jsxs("div",{children:[e.jsx("label",{className:"ghl-text-sm ghl-text-muted",style:{display:"block",marginBottom:4},children:"Website"}),e.jsx("a",{href:s.website,target:"_blank",rel:"noopener noreferrer",style:{color:"var(--ghl-primary)",textDecoration:"none"},children:s.website})]}),s.source&&e.jsxs("div",{children:[e.jsx("label",{className:"ghl-text-sm ghl-text-muted",style:{display:"block",marginBottom:4},children:"Source"}),e.jsx("span",{children:s.source})]}),r()&&e.jsxs("div",{style:{gridColumn:"span 2"},children:[e.jsx("label",{className:"ghl-text-sm ghl-text-muted",style:{display:"block",marginBottom:4},children:"Address"}),e.jsx("span",{children:r()})]})]}),s.tags&&s.tags.length>0&&e.jsxs("div",{style:{marginTop:16},children:[e.jsx("label",{className:"ghl-text-sm ghl-text-muted",style:{display:"block",marginBottom:8},children:"Tags"}),e.jsx("div",{className:"ghl-flex ghl-gap-2",style:{flexWrap:"wrap"},children:s.tags.map((l,t)=>e.jsx("span",{className:"ghl-badge ghl-badge-primary",children:l},t))})]}),s.customFields&&s.customFields.length>0&&e.jsxs("div",{style:{marginTop:16},children:[e.jsx("label",{className:"ghl-text-sm ghl-text-muted",style:{display:"block",marginBottom:8},children:"Custom Fields"}),e.jsx("div",{className:"ghl-grid ghl-grid-2",style:{gap:12},children:s.customFields.slice(0,6).map(l=>e.jsxs("div",{children:[e.jsxs("span",{className:"ghl-text-sm ghl-text-muted",children:[l.key,": "]}),e.jsx("span",{children:String(l.value)})]},l.id))})]})]}),e.jsx("div",{className:"ghl-card-footer",children:e.jsxs("div",{className:"ghl-flex ghl-justify-between ghl-text-sm ghl-text-muted",children:[e.jsxs("span",{children:["Added: ",d(s.dateAdded)]}),e.jsxs("span",{children:["Updated: ",d(s.dateUpdated)]})]})})]})})}function x(s){let a=0;for(let r=0;r<s.length;r++)a=s.charCodeAt(r)+((a<<5)-a);const i=["#4f46e5","#7c3aed","#2563eb","#0891b2","#059669","#d97706","#dc2626","#db2777","#9333ea","#0d9488"];return i[Math.abs(a)%i.length]}function c(){return e.jsx(h,{children:s=>e.jsx(o,{contact:s})})}m.createRoot(document.getElementById("root")).render(g.createElement(c));
|
|
||||||
@ -1 +0,0 @@
|
|||||||
import{j as e,A as k,r as g,g as w,f as A,R as $,a as z}from"./styles-CphAgR3l.js";function D({data:a}){const[l,o]=g.useState("name"),[n,y]=g.useState("asc"),[c,b]=g.useState(1),[d,x]=g.useState(new Set),j=10,p=a.contacts||[],v=[...p].sort((s,t)=>{let i,r;switch(l){case"name":i=s.name||`${s.firstName||""} ${s.lastName||""}`.trim(),r=t.name||`${t.firstName||""} ${t.lastName||""}`.trim();break;case"email":i=s.email||"",r=t.email||"";break;case"dateAdded":i=s.dateAdded||"",r=t.dateAdded||"";break;default:return 0}const m=i.localeCompare(r);return n==="asc"?m:-m}),u=Math.ceil(v.length/j),h=v.slice((c-1)*j,c*j),N=s=>{l===s?y(n==="asc"?"desc":"asc"):(o(s),y("asc"))},C=s=>{const t=new Set(d);t.has(s)?t.delete(s):t.add(s),x(t)},S=()=>{d.size===h.length?x(new Set):x(new Set(h.map(s=>s.id)))},f=({field:s})=>e.jsx("span",{style:{marginLeft:4,opacity:l===s?1:.3},children:l===s&&n==="desc"?"↓":"↑"});return p.length===0?e.jsx("div",{className:"ghl-app",children:e.jsxs("div",{className:"ghl-empty",children:[e.jsx("div",{className:"ghl-empty-icon",children:"👥"}),e.jsx("p",{children:"No contacts found"})]})}):e.jsx("div",{className:"ghl-app",children:e.jsxs("div",{className:"ghl-card",children:[e.jsx("div",{className:"ghl-card-header",children:e.jsxs("div",{className:"ghl-flex ghl-justify-between ghl-items-center",children:[e.jsxs("div",{children:[e.jsx("h2",{style:{fontSize:18,fontWeight:600},children:"Contacts"}),e.jsxs("p",{className:"ghl-text-sm ghl-text-muted",children:[a.total||p.length," total contacts"]})]}),d.size>0&&e.jsx("div",{className:"ghl-flex ghl-gap-2",children:e.jsxs("span",{className:"ghl-text-sm ghl-text-secondary",children:[d.size," selected"]})})]})}),e.jsx("div",{style:{overflowX:"auto"},children:e.jsxs("table",{className:"ghl-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{style:{width:40},children:e.jsx("input",{type:"checkbox",checked:d.size===h.length&&h.length>0,onChange:S})}),e.jsxs("th",{onClick:()=>N("name"),style:{cursor:"pointer"},children:["Contact ",e.jsx(f,{field:"name"})]}),e.jsxs("th",{onClick:()=>N("email"),style:{cursor:"pointer"},children:["Email ",e.jsx(f,{field:"email"})]}),e.jsx("th",{children:"Phone"}),e.jsx("th",{children:"Tags"}),e.jsxs("th",{onClick:()=>N("dateAdded"),style:{cursor:"pointer"},children:["Added ",e.jsx(f,{field:"dateAdded"})]})]})}),e.jsx("tbody",{children:h.map(s=>{const t=s.name||`${s.firstName||""} ${s.lastName||""}`.trim()||"Unknown",i=w(s.firstName,s.lastName);return e.jsxs("tr",{children:[e.jsx("td",{children:e.jsx("input",{type:"checkbox",checked:d.has(s.id),onChange:()=>C(s.id)})}),e.jsx("td",{children:e.jsxs("div",{className:"ghl-flex ghl-items-center ghl-gap-2",children:[e.jsx("div",{className:"ghl-avatar ghl-avatar-sm",style:{background:P(t)},children:i}),e.jsxs("div",{children:[e.jsx("div",{className:"ghl-font-medium",children:t}),s.companyName&&e.jsx("div",{className:"ghl-text-sm ghl-text-muted",children:s.companyName})]})]})}),e.jsx("td",{children:s.email?e.jsx("a",{href:`mailto:${s.email}`,style:{color:"var(--ghl-primary)",textDecoration:"none"},children:s.email}):e.jsx("span",{className:"ghl-text-muted",children:"-"})}),e.jsx("td",{children:s.phone?e.jsx("a",{href:`tel:${s.phone}`,style:{color:"var(--ghl-primary)",textDecoration:"none"},children:s.phone}):e.jsx("span",{className:"ghl-text-muted",children:"-"})}),e.jsx("td",{children:e.jsxs("div",{className:"ghl-flex ghl-gap-2",style:{flexWrap:"wrap",maxWidth:200},children:[(s.tags||[]).slice(0,3).map((r,m)=>e.jsx("span",{className:"ghl-badge ghl-badge-primary",children:r},m)),(s.tags||[]).length>3&&e.jsxs("span",{className:"ghl-badge",children:["+",s.tags.length-3]})]})}),e.jsx("td",{className:"ghl-text-muted",children:A(s.dateAdded)})]},s.id)})})]})}),u>1&&e.jsx("div",{className:"ghl-card-footer",children:e.jsxs("div",{className:"ghl-flex ghl-justify-between ghl-items-center",children:[e.jsxs("span",{className:"ghl-text-sm ghl-text-muted",children:["Page ",c," of ",u]}),e.jsxs("div",{className:"ghl-flex ghl-gap-2",children:[e.jsx("button",{className:"ghl-btn ghl-btn-secondary ghl-btn-sm",disabled:c===1,onClick:()=>b(s=>s-1),children:"Previous"}),e.jsx("button",{className:"ghl-btn ghl-btn-secondary ghl-btn-sm",disabled:c===u,onClick:()=>b(s=>s+1),children:"Next"})]})]})})]})})}function P(a){let l=0;for(let n=0;n<a.length;n++)l=a.charCodeAt(n)+((l<<5)-l);const o=["#4f46e5","#7c3aed","#2563eb","#0891b2","#059669","#d97706","#dc2626","#db2777","#9333ea","#0d9488"];return o[Math.abs(l)%o.length]}function E(){return e.jsx(k,{children:a=>e.jsx(D,{data:a})})}$.createRoot(document.getElementById("root")).render(z.createElement(E));
|
|
||||||
@ -1 +0,0 @@
|
|||||||
import{j as e,A as x,r as h,R as g,a as u}from"./styles-CphAgR3l.js";function m({data:t}){const{conversation:n,messages:c}=t,l=h.useRef(null),d=n.contactName||n.contact?.name||`${n.contact?.firstName||""} ${n.contact?.lastName||""}`.trim()||"Unknown Contact";h.useEffect(()=>{l.current?.scrollIntoView({behavior:"smooth"})},[]);const r=[...c||[]].sort((a,s)=>{const o=a.dateAdded?new Date(a.dateAdded).getTime():0,p=s.dateAdded?new Date(s.dateAdded).getTime():0;return o-p}),i=new Map;for(const a of r){const s=a.dateAdded?new Date(a.dateAdded).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"}):"Unknown Date";i.has(s)||i.set(s,[]),i.get(s).push(a)}return e.jsx("div",{className:"ghl-app",style:{height:"100%",display:"flex",flexDirection:"column"},children:e.jsxs("div",{className:"ghl-card",style:{flex:1,display:"flex",flexDirection:"column",maxHeight:600},children:[e.jsx("div",{className:"ghl-card-header",children:e.jsxs("div",{className:"ghl-flex ghl-items-center ghl-gap-4",children:[e.jsx("div",{style:{width:44,height:44,borderRadius:"50%",background:"var(--ghl-primary)",display:"flex",alignItems:"center",justifyContent:"center",color:"white",fontWeight:600},children:d.charAt(0).toUpperCase()}),e.jsxs("div",{children:[e.jsx("h2",{style:{fontSize:16,fontWeight:600,marginBottom:2},children:d}),e.jsxs("div",{className:"ghl-flex ghl-items-center ghl-gap-2",children:[n.contact?.phone&&e.jsx("span",{className:"ghl-text-sm ghl-text-muted",children:n.contact.phone}),n.type&&e.jsx("span",{className:"ghl-badge",children:n.type})]})]})]})}),e.jsx("div",{style:{flex:1,overflowY:"auto",padding:16,background:"var(--ghl-bg-secondary)"},children:r.length===0?e.jsxs("div",{className:"ghl-empty",children:[e.jsx("div",{className:"ghl-empty-icon",children:"💬"}),e.jsx("p",{children:"No messages in this conversation"})]}):e.jsxs(e.Fragment,{children:[Array.from(i.entries()).map(([a,s])=>e.jsxs("div",{children:[e.jsxs("div",{style:{textAlign:"center",margin:"16px 0",position:"relative"},children:[e.jsx("span",{style:{background:"var(--ghl-bg-secondary)",padding:"4px 12px",fontSize:12,color:"var(--ghl-text-muted)",position:"relative",zIndex:1},children:a}),e.jsx("div",{style:{position:"absolute",top:"50%",left:0,right:0,height:1,background:"var(--ghl-border)",zIndex:0}})]}),s.map(o=>e.jsx(f,{message:o},o.id))]},a)),e.jsx("div",{ref:l})]})}),e.jsx("div",{className:"ghl-card-footer",children:e.jsxs("div",{className:"ghl-flex ghl-items-center ghl-gap-2",children:[e.jsxs("div",{style:{flex:1,padding:"10px 14px",background:"var(--ghl-bg)",border:"1px solid var(--ghl-border)",borderRadius:20,fontSize:14,color:"var(--ghl-text-muted)"},children:["Reply to ",d,"..."]}),e.jsx("button",{className:"ghl-btn ghl-btn-primary",style:{borderRadius:20,padding:"10px 20px"},children:"Send"})]})})]})})}function f({message:t}){const n=t.direction==="outbound",c=r=>r?new Date(r).toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit"}):"",l=r=>{switch(r){case"delivered":return"✓✓";case"sent":return"✓";case"read":return"✓✓";case"failed":return"✕";default:return""}},d=r=>{switch(r){case"SMS":return"📱";case"Email":return"📧";case"WhatsApp":return"💬";case"FB":return"👤";case"GMB":return"🏢";case"Call":return"📞";default:return""}};return e.jsx("div",{style:{display:"flex",justifyContent:n?"flex-end":"flex-start",marginBottom:8},children:e.jsxs("div",{style:{maxWidth:"75%",background:n?"var(--ghl-primary)":"var(--ghl-bg)",color:n?"white":"var(--ghl-text)",padding:"10px 14px",borderRadius:n?"18px 18px 4px 18px":"18px 18px 18px 4px",boxShadow:"var(--ghl-shadow)"},children:[t.type&&e.jsxs("div",{style:{fontSize:10,marginBottom:4,opacity:.7},children:[d(t.type)," ",t.type]}),e.jsx("div",{style:{wordBreak:"break-word",whiteSpace:"pre-wrap"},children:t.body||e.jsx("span",{style:{opacity:.6,fontStyle:"italic"},children:"[No content]"})}),t.attachments&&t.attachments.length>0&&e.jsx("div",{style:{marginTop:8},children:t.attachments.map((r,i)=>e.jsx("div",{style:{padding:"6px 10px",background:n?"rgba(255,255,255,0.1)":"var(--ghl-bg-secondary)",borderRadius:8,fontSize:12,marginTop:4},children:"📎 Attachment"},i))}),e.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"flex-end",gap:6,marginTop:6,fontSize:11,opacity:.7},children:[e.jsx("span",{children:c(t.dateAdded)}),n&&t.status&&e.jsx("span",{style:{color:t.status==="failed"?"#ef4444":"inherit"},children:l(t.status)})]})]})})}function y(){return e.jsx(x,{children:t=>e.jsx(m,{data:t})})}g.createRoot(document.getElementById("root")).render(u.createElement(y));
|
|
||||||
@ -1 +0,0 @@
|
|||||||
import{j as e,A as u,b as g,f as p,R as v,a as j}from"./styles-CphAgR3l.js";function f({data:s}){const{pipeline:n,opportunities:l,stages:h}=s,c=[...h||[]].sort((t,a)=>(t.position||0)-(a.position||0)),r=new Map;for(const t of c)r.set(t.id,[]);for(const t of l||[]){const a=r.get(t.pipelineStageId||"");a&&a.push(t)}const m=t=>{const a=r.get(t)||[],i=a.length,d=a.reduce((o,x)=>o+(x.monetaryValue||0),0);return{count:i,value:d}};return n?e.jsxs("div",{className:"ghl-app",children:[e.jsxs("div",{style:{marginBottom:16},children:[e.jsx("h2",{style:{fontSize:20,fontWeight:600,marginBottom:4},children:n.name}),e.jsxs("p",{className:"ghl-text-sm ghl-text-muted",children:[l?.length||0," opportunities | ",c.length," stages"]})]}),e.jsx("div",{style:{display:"flex",gap:16,overflowX:"auto",paddingBottom:16},children:c.map(t=>{const{count:a,value:i}=m(t.id),d=r.get(t.id)||[];return e.jsxs("div",{style:{flex:"0 0 280px",display:"flex",flexDirection:"column",maxHeight:500},children:[e.jsxs("div",{style:{padding:"12px 16px",background:"var(--ghl-bg-secondary)",borderRadius:"var(--ghl-radius) var(--ghl-radius) 0 0",border:"1px solid var(--ghl-border)",borderBottom:"none"},children:[e.jsxs("div",{className:"ghl-flex ghl-justify-between ghl-items-center",children:[e.jsx("span",{className:"ghl-font-semibold",children:t.name}),e.jsx("span",{className:"ghl-badge",children:a})]}),i>0&&e.jsx("div",{className:"ghl-text-sm ghl-text-muted",style:{marginTop:4},children:g(i)})]}),e.jsx("div",{style:{flex:1,padding:8,background:"var(--ghl-bg-tertiary)",borderRadius:"0 0 var(--ghl-radius) var(--ghl-radius)",border:"1px solid var(--ghl-border)",borderTop:"none",overflowY:"auto",minHeight:200},children:d.length===0?e.jsx("div",{style:{padding:20,textAlign:"center",color:"var(--ghl-text-muted)",fontSize:13},children:"No opportunities"}):e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:8},children:d.map(o=>e.jsx(b,{opportunity:o},o.id))})})]},t.id)})}),e.jsx("div",{className:"ghl-card",style:{marginTop:16},children:e.jsx("div",{className:"ghl-card-body",children:e.jsxs("div",{className:"ghl-flex ghl-justify-between",children:[e.jsxs("div",{children:[e.jsx("span",{className:"ghl-text-muted",children:"Total Opportunities"}),e.jsx("div",{className:"ghl-font-semibold",style:{fontSize:20},children:l?.length||0})]}),e.jsxs("div",{style:{textAlign:"right"},children:[e.jsx("span",{className:"ghl-text-muted",children:"Total Pipeline Value"}),e.jsx("div",{className:"ghl-font-semibold",style:{fontSize:20,color:"var(--ghl-success)"},children:g(l?.reduce((t,a)=>t+(a.monetaryValue||0),0)||0)})]})]})})})]}):e.jsx("div",{className:"ghl-app",children:e.jsxs("div",{className:"ghl-empty",children:[e.jsx("div",{className:"ghl-empty-icon",children:"📊"}),e.jsx("p",{children:"Pipeline not found"})]})})}function b({opportunity:s}){const n=s.contact?.name||`${s.contact?.firstName||""} ${s.contact?.lastName||""}`.trim()||"Unknown Contact",l={open:"var(--ghl-primary)",won:"var(--ghl-success)",lost:"var(--ghl-danger)",abandoned:"var(--ghl-warning)"};return e.jsxs("div",{style:{background:"var(--ghl-bg)",borderRadius:"var(--ghl-radius)",padding:12,boxShadow:"var(--ghl-shadow)",border:"1px solid var(--ghl-border)"},children:[e.jsx("div",{className:"ghl-font-medium",style:{marginBottom:8},children:s.name}),e.jsxs("div",{className:"ghl-flex ghl-items-center ghl-gap-2",style:{marginBottom:8},children:[e.jsx("span",{style:{width:24,height:24,borderRadius:"50%",background:"var(--ghl-bg-tertiary)",display:"flex",alignItems:"center",justifyContent:"center",fontSize:10},children:"👤"}),e.jsx("span",{className:"ghl-text-sm",children:n})]}),e.jsxs("div",{className:"ghl-flex ghl-justify-between ghl-items-center",children:[s.monetaryValue?e.jsx("span",{className:"ghl-font-semibold",style:{color:"var(--ghl-success)"},children:g(s.monetaryValue)}):e.jsx("span",{className:"ghl-text-muted ghl-text-sm",children:"No value"}),s.status&&e.jsx("span",{className:"ghl-badge",style:{background:`${l[s.status]||"var(--ghl-bg-tertiary)"}20`,color:l[s.status]||"var(--ghl-text-muted)"},children:s.status})]}),s.dateAdded&&e.jsxs("div",{className:"ghl-text-sm ghl-text-muted",style:{marginTop:8},children:["Added ",p(s.dateAdded)]})]})}function y(){return e.jsx(u,{children:s=>e.jsx(f,{data:s})})}v.createRoot(document.getElementById("root")).render(j.createElement(y));
|
|
||||||
@ -1 +0,0 @@
|
|||||||
:root{--ghl-primary: #4f46e5;--ghl-primary-hover: #4338ca;--ghl-success: #22c55e;--ghl-warning: #f59e0b;--ghl-danger: #ef4444;--ghl-info: #3b82f6;--ghl-bg: #ffffff;--ghl-bg-secondary: #f9fafb;--ghl-bg-tertiary: #f3f4f6;--ghl-text: #111827;--ghl-text-secondary: #6b7280;--ghl-text-muted: #9ca3af;--ghl-border: #e5e7eb;--ghl-border-dark: #d1d5db;--ghl-shadow: 0 1px 3px rgba(0, 0, 0, .1);--ghl-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, .1);--ghl-radius: 8px;--ghl-radius-lg: 12px}*{box-sizing:border-box;margin:0;padding:0}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;font-size:14px;line-height:1.5;color:var(--ghl-text);background:var(--ghl-bg)}.ghl-app{padding:16px;max-width:100%;overflow-x:hidden}.ghl-card{background:var(--ghl-bg);border:1px solid var(--ghl-border);border-radius:var(--ghl-radius-lg);box-shadow:var(--ghl-shadow);overflow:hidden}.ghl-card-header{padding:16px;border-bottom:1px solid var(--ghl-border);background:var(--ghl-bg-secondary)}.ghl-card-body{padding:16px}.ghl-card-footer{padding:12px 16px;border-top:1px solid var(--ghl-border);background:var(--ghl-bg-secondary)}.ghl-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:8px 16px;font-size:14px;font-weight:500;border-radius:var(--ghl-radius);border:1px solid transparent;cursor:pointer;transition:all .15s ease}.ghl-btn-primary{background:var(--ghl-primary);color:#fff}.ghl-btn-primary:hover{background:var(--ghl-primary-hover)}.ghl-btn-secondary{background:var(--ghl-bg);color:var(--ghl-text);border-color:var(--ghl-border)}.ghl-btn-secondary:hover{background:var(--ghl-bg-secondary)}.ghl-btn-sm{padding:4px 10px;font-size:12px}.ghl-badge{display:inline-flex;align-items:center;padding:2px 8px;font-size:12px;font-weight:500;border-radius:9999px;background:var(--ghl-bg-tertiary);color:var(--ghl-text-secondary)}.ghl-badge-primary{background:#4f46e51a;color:var(--ghl-primary)}.ghl-badge-success{background:#22c55e1a;color:var(--ghl-success)}.ghl-badge-warning{background:#f59e0b1a;color:var(--ghl-warning)}.ghl-badge-danger{background:#ef44441a;color:var(--ghl-danger)}.ghl-avatar{display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%;background:var(--ghl-primary);color:#fff;font-weight:600;font-size:16px}.ghl-avatar-lg{width:64px;height:64px;font-size:24px}.ghl-avatar-sm{width:32px;height:32px;font-size:12px}.ghl-table{width:100%;border-collapse:collapse}.ghl-table th,.ghl-table td{padding:12px;text-align:left;border-bottom:1px solid var(--ghl-border)}.ghl-table th{font-weight:600;background:var(--ghl-bg-secondary);color:var(--ghl-text-secondary);font-size:12px;text-transform:uppercase;letter-spacing:.05em}.ghl-table tr:hover{background:var(--ghl-bg-secondary)}.ghl-grid{display:grid;gap:16px}.ghl-grid-2{grid-template-columns:repeat(2,1fr)}.ghl-grid-3{grid-template-columns:repeat(3,1fr)}.ghl-grid-4{grid-template-columns:repeat(4,1fr)}.ghl-flex{display:flex}.ghl-flex-col{flex-direction:column}.ghl-items-center{align-items:center}.ghl-justify-between{justify-content:space-between}.ghl-gap-2{gap:8px}.ghl-gap-4{gap:16px}.ghl-text-sm{font-size:12px}.ghl-text-lg{font-size:18px}.ghl-text-muted{color:var(--ghl-text-muted)}.ghl-text-secondary{color:var(--ghl-text-secondary)}.ghl-font-medium{font-weight:500}.ghl-font-semibold{font-weight:600}.ghl-status-paid{color:var(--ghl-success)}.ghl-status-pending{color:var(--ghl-warning)}.ghl-status-overdue{color:var(--ghl-danger)}.ghl-status-draft{color:var(--ghl-text-muted)}.ghl-loading{display:flex;align-items:center;justify-content:center;padding:40px;color:var(--ghl-text-muted)}.ghl-spinner{width:24px;height:24px;border:2px solid var(--ghl-border);border-top-color:var(--ghl-primary);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.ghl-empty{text-align:center;padding:40px;color:var(--ghl-text-muted)}.ghl-empty-icon{font-size:48px;margin-bottom:16px;opacity:.5}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Calendar Widget - GHL MCP</title>
|
|
||||||
<script type="module" crossorigin src="/assets/calendar-widget-CUbShwNj.js"></script>
|
|
||||||
<link rel="modulepreload" crossorigin href="/assets/styles-CphAgR3l.js">
|
|
||||||
<link rel="stylesheet" crossorigin href="/assets/style-BaFxk78P.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Contact Card - GHL MCP</title>
|
|
||||||
<script type="module" crossorigin src="/assets/contact-card-CFJe96SR.js"></script>
|
|
||||||
<link rel="modulepreload" crossorigin href="/assets/styles-CphAgR3l.js">
|
|
||||||
<link rel="stylesheet" crossorigin href="/assets/style-BaFxk78P.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Contact Grid - GHL MCP</title>
|
|
||||||
<script type="module" crossorigin src="/assets/contact-grid-C_Uxn-WJ.js"></script>
|
|
||||||
<link rel="modulepreload" crossorigin href="/assets/styles-CphAgR3l.js">
|
|
||||||
<link rel="stylesheet" crossorigin href="/assets/style-BaFxk78P.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Conversation Thread - GHL MCP</title>
|
|
||||||
<script type="module" crossorigin src="/assets/conversation-thread-DBGTC45D.js"></script>
|
|
||||||
<link rel="modulepreload" crossorigin href="/assets/styles-CphAgR3l.js">
|
|
||||||
<link rel="stylesheet" crossorigin href="/assets/style-BaFxk78P.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Invoice Preview - GHL MCP</title>
|
|
||||||
<script type="module" crossorigin src="/assets/invoice-preview-DphRvjHB.js"></script>
|
|
||||||
<link rel="modulepreload" crossorigin href="/assets/styles-CphAgR3l.js">
|
|
||||||
<link rel="stylesheet" crossorigin href="/assets/style-BaFxk78P.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Pipeline Kanban - GHL MCP</title>
|
|
||||||
<script type="module" crossorigin src="/assets/opportunity-kanban-CSzRcmdW.js"></script>
|
|
||||||
<link rel="modulepreload" crossorigin href="/assets/styles-CphAgR3l.js">
|
|
||||||
<link rel="stylesheet" crossorigin href="/assets/style-BaFxk78P.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||