From 30d55b58995992f2d2e27735555c8d2e9a353585 Mon Sep 17 00:00:00 2001 From: Jake Shore Date: Fri, 30 Jan 2026 23:00:51 -0500 Subject: [PATCH] Daily backup: 2026-01-30 --- HEARTBEAT.md | 2 +- config/mcporter.json | 2 +- mcp-diagrams/GoHighLevel-MCP | 2 +- mcp-diagrams/ghl-mcp-apps-only/.env.example | 11 + .../dist/app-ui/agent-stats.html | 481 ++ .../dist/app-ui/calendar-view.html | 544 ++ .../dist/app-ui/campaign-stats.html | 442 ++ .../dist/app-ui/contact-grid.html | 501 ++ .../dist/app-ui/contact-timeline.html | 355 + .../dist/app-ui/invoice-preview.html | 474 ++ .../dist/app-ui/mcp-app.html | 168 + .../dist/app-ui/opportunity-card.html | 520 ++ .../dist/app-ui/pipeline-board.html | 533 ++ .../dist/app-ui/quick-book.html | 453 ++ .../dist/app-ui/workflow-status.html | 352 + .../ghl-mcp-apps-only/dist/apps/index.js | 654 ++ .../dist/clients/ghl-api-client.js | 5107 ++++++++++++ mcp-diagrams/ghl-mcp-apps-only/dist/server.js | 139 + .../ghl-mcp-apps-only/dist/types/ghl-types.js | 5 + mcp-diagrams/ghl-mcp-apps-only/package.json | 22 + .../ghl-mcp-apps-only/src/apps/index.ts | 824 ++ .../src/clients/ghl-api-client.ts | 6858 +++++++++++++++++ mcp-diagrams/ghl-mcp-apps-only/src/server.ts | 172 + .../ghl-mcp-apps-only/src/types/ghl-types.ts | 6688 ++++++++++++++++ .../dist/assets/calendar-widget-CUbShwNj.js | 1 + .../ui/dist/assets/contact-card-CFJe96SR.js | 1 + .../ui/dist/assets/contact-grid-C_Uxn-WJ.js | 1 + .../assets/conversation-thread-DBGTC45D.js | 1 + .../dist/assets/invoice-preview-DphRvjHB.js | 1 + .../assets/opportunity-kanban-CSzRcmdW.js | 1 + .../src/ui/dist/assets/style-BaFxk78P.css | 1 + .../src/ui/dist/assets/styles-CphAgR3l.js | 9 + .../src/ui/dist/calendar-widget.html | 14 + .../src/ui/dist/contact-card.html | 14 + .../src/ui/dist/contact-grid.html | 14 + .../src/ui/dist/conversation-thread.html | 14 + .../src/ui/dist/invoice-preview.html | 14 + .../src/ui/dist/opportunity-kanban.html | 14 + mcp-diagrams/ghl-mcp-apps-only/tsconfig.json | 16 + memory/2026-01-30.md | 33 + memory/burton-method-research-intel.md | 152 +- pickle_history.txt | 2 + 42 files changed, 25504 insertions(+), 108 deletions(-) create mode 100644 mcp-diagrams/ghl-mcp-apps-only/.env.example create mode 100644 mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/agent-stats.html create mode 100644 mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/calendar-view.html create mode 100644 mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/campaign-stats.html create mode 100644 mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/contact-grid.html create mode 100644 mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/contact-timeline.html create mode 100644 mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/invoice-preview.html create mode 100644 mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/mcp-app.html create mode 100644 mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/opportunity-card.html create mode 100644 mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/pipeline-board.html create mode 100644 mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/quick-book.html create mode 100644 mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/workflow-status.html create mode 100644 mcp-diagrams/ghl-mcp-apps-only/dist/apps/index.js create mode 100644 mcp-diagrams/ghl-mcp-apps-only/dist/clients/ghl-api-client.js create mode 100644 mcp-diagrams/ghl-mcp-apps-only/dist/server.js create mode 100644 mcp-diagrams/ghl-mcp-apps-only/dist/types/ghl-types.js create mode 100644 mcp-diagrams/ghl-mcp-apps-only/package.json create mode 100644 mcp-diagrams/ghl-mcp-apps-only/src/apps/index.ts create mode 100644 mcp-diagrams/ghl-mcp-apps-only/src/clients/ghl-api-client.ts create mode 100644 mcp-diagrams/ghl-mcp-apps-only/src/server.ts create mode 100644 mcp-diagrams/ghl-mcp-apps-only/src/types/ghl-types.ts create mode 100644 mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/calendar-widget-CUbShwNj.js create mode 100644 mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/contact-card-CFJe96SR.js create mode 100644 mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/contact-grid-C_Uxn-WJ.js create mode 100644 mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/conversation-thread-DBGTC45D.js create mode 100644 mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/invoice-preview-DphRvjHB.js create mode 100644 mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/opportunity-kanban-CSzRcmdW.js create mode 100644 mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/style-BaFxk78P.css create mode 100644 mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/styles-CphAgR3l.js create mode 100644 mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/calendar-widget.html create mode 100644 mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/contact-card.html create mode 100644 mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/contact-grid.html create mode 100644 mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/conversation-thread.html create mode 100644 mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/invoice-preview.html create mode 100644 mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/opportunity-kanban.html create mode 100644 mcp-diagrams/ghl-mcp-apps-only/tsconfig.json create mode 100644 memory/2026-01-30.md diff --git a/HEARTBEAT.md b/HEARTBEAT.md index aa6a3c4..14170bc 100644 --- a/HEARTBEAT.md +++ b/HEARTBEAT.md @@ -15,4 +15,4 @@ Building bulk animation generator for MCP marketing. Using Remotion with canvas viewport technique (dolly camera). Camera should zoom into typing area, follow text as typed, zoom out when done. Category-specific questions per software type. --- -*Last updated: 2026-01-28 23:00 EST* +*Last updated: 2026-01-30 23:00 EST* diff --git a/config/mcporter.json b/config/mcporter.json index d48a6b9..21079f5 100644 --- a/config/mcporter.json +++ b/config/mcporter.json @@ -5,7 +5,7 @@ "command": "node", "args": ["/Users/jakeshore/.clawdbot/workspace/mcp-diagrams/GoHighLevel-MCP/dist/server.js"], "env": { - "GHL_API_KEY": "pit-0aebc49f-07f7-47dc-a494-181b72a1df54", + "GHL_API_KEY": "pit-0480982f-750b-4baa-bc10-1340a9b2102b", "GHL_BASE_URL": "https://services.leadconnectorhq.com", "GHL_LOCATION_ID": "DZEpRd43MxUJKdtrev9t", "NODE_ENV": "production" diff --git a/mcp-diagrams/GoHighLevel-MCP b/mcp-diagrams/GoHighLevel-MCP index 69db02d..19b03fe 160000 --- a/mcp-diagrams/GoHighLevel-MCP +++ b/mcp-diagrams/GoHighLevel-MCP @@ -1 +1 @@ -Subproject commit 69db02d7cf9aca4fc39ae34b6f20f26617e57316 +Subproject commit 19b03fe777b18657c9e31ec79e740fac568cfa03 diff --git a/mcp-diagrams/ghl-mcp-apps-only/.env.example b/mcp-diagrams/ghl-mcp-apps-only/.env.example new file mode 100644 index 0000000..08cb1a8 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/.env.example @@ -0,0 +1,11 @@ +# 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 diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/agent-stats.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/agent-stats.html new file mode 100644 index 0000000..ffbf138 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/agent-stats.html @@ -0,0 +1,481 @@ + + + + + + GHL Agent Leaderboard + + + + +
+
+
+ Loading agent leaderboard... +
+
+ + diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/calendar-view.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/calendar-view.html new file mode 100644 index 0000000..13d2c93 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/calendar-view.html @@ -0,0 +1,544 @@ + + + + + + GHL Calendar View + + + + +
+
Loading calendar...
+
+ + diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/campaign-stats.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/campaign-stats.html new file mode 100644 index 0000000..b3ddb43 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/campaign-stats.html @@ -0,0 +1,442 @@ + + + + + + Campaign Performance Dashboard + + + + +
+
Loading campaign performance data...
+
+ + diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/contact-grid.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/contact-grid.html new file mode 100644 index 0000000..c1cbee7 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/contact-grid.html @@ -0,0 +1,501 @@ + + + + + + GHL Contact Grid + + + + +
+
Loading contacts...
+
+ + diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/contact-timeline.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/contact-timeline.html new file mode 100644 index 0000000..dbfba44 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/contact-timeline.html @@ -0,0 +1,355 @@ + + + + + + Contact Activity Timeline + + + + +
+
Loading timeline...
+
+ + diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/invoice-preview.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/invoice-preview.html new file mode 100644 index 0000000..1b024ca --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/invoice-preview.html @@ -0,0 +1,474 @@ + + + + + + Invoice Preview + + + + +
+
Loading invoice...
+
+ + diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/mcp-app.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/mcp-app.html new file mode 100644 index 0000000..72e960d --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/mcp-app.html @@ -0,0 +1,168 @@ + + + + + + GHL Conversation + + + + +
+
Loading conversation...
+
+ + diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/opportunity-card.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/opportunity-card.html new file mode 100644 index 0000000..f3c7161 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/opportunity-card.html @@ -0,0 +1,520 @@ + + + + + + GHL Opportunity Card + + + + +
+
Loading opportunity...
+
+ + diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/pipeline-board.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/pipeline-board.html new file mode 100644 index 0000000..88cbec5 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/pipeline-board.html @@ -0,0 +1,533 @@ + + + + + + GHL Pipeline Board + + + + +
+
Loading pipeline board...
+
+ + + + + + + + + diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/quick-book.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/quick-book.html new file mode 100644 index 0000000..b25ad6a --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/quick-book.html @@ -0,0 +1,453 @@ + + + + + + GHL Quick Booking + + + + +
+
Loading available slots...
+
+ + diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/workflow-status.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/workflow-status.html new file mode 100644 index 0000000..6e4be47 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/workflow-status.html @@ -0,0 +1,352 @@ + + + + + + GHL Workflow Status Monitor + + + + +
+
+
+ Loading workflow status... +
+
+ + diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/apps/index.js b/mcp-diagrams/ghl-mcp-apps-only/dist/apps/index.js new file mode 100644 index 0000000..9117063 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/dist/apps/index.js @@ -0,0 +1,654 @@ +/** + * 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 ` + + + + + + GHL ${componentName} + + + +
+

UI component "${componentName}" is loading...

+

Run npm run build:ui to build UI components.

+
+ + + + `.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 = ``; + // Insert before or at the beginning of + if (html.includes('')) { + return html.replace('', `${dataScript}`); + } + else if (html.includes('')) { + return html.replace('', `${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()); + } +} diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/clients/ghl-api-client.js b/mcp-diagrams/ghl-mcp-apps-only/dist/clients/ghl-api-client.js new file mode 100644 index 0000000..4e5b0de --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/dist/clients/ghl-api-client.js @@ -0,0 +1,5107 @@ +/** + * GoHighLevel API Client + * Implements exact API endpoints from OpenAPI specifications v2021-07-28 (Contacts) and v2021-04-15 (Conversations) + */ +import axios from 'axios'; +/** + * GoHighLevel API Client + * Handles all API communication with GHL services + */ +export class GHLApiClient { + axiosInstance; + config; + constructor(config) { + this.config = config; + // Create axios instance with base configuration + this.axiosInstance = axios.create({ + baseURL: config.baseUrl, + headers: { + 'Authorization': `Bearer ${config.accessToken}`, + 'Version': config.version, + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + timeout: 30000 // 30 second timeout + }); + // Add request interceptor for logging + this.axiosInstance.interceptors.request.use((config) => { + process.stderr.write(`[GHL API] ${config.method?.toUpperCase()} ${config.url}\n`); + return config; + }, (error) => { + console.error('[GHL API] Request error:', error); + return Promise.reject(error); + }); + // Add response interceptor for error handling + this.axiosInstance.interceptors.response.use((response) => { + process.stderr.write(`[GHL API] Response ${response.status}: ${response.config.url}\n`); + return response; + }, (error) => { + console.error('[GHL API] Response error:', { + status: error.response?.status, + message: error.response?.data?.message, + url: error.config?.url + }); + return Promise.reject(this.handleApiError(error)); + }); + } + /** + * Handle API errors and convert to standardized format + */ + handleApiError(error) { + const status = error.response?.status || 500; + const message = error.response?.data?.message || error.message || 'Unknown error'; + const errorMessage = Array.isArray(message) ? message.join(', ') : message; + return new Error(`GHL API Error (${status}): ${errorMessage}`); + } + /** + * Wrap API responses in standardized format + */ + wrapResponse(data) { + return { + success: true, + data + }; + } + /** + * Create custom headers for different API versions + */ + getConversationHeaders() { + return { + 'Authorization': `Bearer ${this.config.accessToken}`, + 'Version': '2021-04-15', // Conversations API uses different version + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }; + } + /** + * CONTACTS API METHODS + */ + /** + * Create a new contact + * POST /contacts/ + */ + async createContact(contactData) { + try { + // Ensure locationId is set + const payload = { + ...contactData, + locationId: contactData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.post('/contacts/', payload); + return this.wrapResponse(response.data.contact); + } + catch (error) { + throw error; + } + } + /** + * Get contact by ID + * GET /contacts/{contactId} + */ + async getContact(contactId) { + try { + const response = await this.axiosInstance.get(`/contacts/${contactId}`); + return this.wrapResponse(response.data.contact); + } + catch (error) { + throw error; + } + } + /** + * Update existing contact + * PUT /contacts/{contactId} + */ + async updateContact(contactId, updates) { + try { + const response = await this.axiosInstance.put(`/contacts/${contactId}`, updates); + return this.wrapResponse(response.data.contact); + } + catch (error) { + throw error; + } + } + /** + * Delete contact + * DELETE /contacts/{contactId} + */ + async deleteContact(contactId) { + try { + const response = await this.axiosInstance.delete(`/contacts/${contactId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Search contacts with advanced filters + * POST /contacts/search + */ + async searchContacts(searchParams) { + try { + // Build minimal request body with only required/supported parameters + // Start with just locationId and pageLimit as per API requirements + const payload = { + locationId: searchParams.locationId || this.config.locationId, + pageLimit: searchParams.limit || 25 + }; + // Only add optional parameters if they have valid values + if (searchParams.query && searchParams.query.trim()) { + payload.query = searchParams.query.trim(); + } + if (searchParams.startAfterId && searchParams.startAfterId.trim()) { + payload.startAfterId = searchParams.startAfterId.trim(); + } + if (searchParams.startAfter && typeof searchParams.startAfter === 'number') { + payload.startAfter = searchParams.startAfter; + } + // Only add filters if we have valid filter values + if (searchParams.filters) { + const filters = {}; + let hasFilters = false; + if (searchParams.filters.email && typeof searchParams.filters.email === 'string' && searchParams.filters.email.trim()) { + filters.email = searchParams.filters.email.trim(); + hasFilters = true; + } + if (searchParams.filters.phone && typeof searchParams.filters.phone === 'string' && searchParams.filters.phone.trim()) { + filters.phone = searchParams.filters.phone.trim(); + hasFilters = true; + } + if (searchParams.filters.tags && Array.isArray(searchParams.filters.tags) && searchParams.filters.tags.length > 0) { + filters.tags = searchParams.filters.tags; + hasFilters = true; + } + if (searchParams.filters.dateAdded && typeof searchParams.filters.dateAdded === 'object') { + filters.dateAdded = searchParams.filters.dateAdded; + hasFilters = true; + } + // Only add filters object if we have actual filters + if (hasFilters) { + payload.filters = filters; + } + } + process.stderr.write(`[GHL API] Search contacts payload: ${JSON.stringify(payload, null, 2)}\n`); + const response = await this.axiosInstance.post('/contacts/search', payload); + return this.wrapResponse(response.data); + } + catch (error) { + const axiosError = error; + process.stderr.write(`[GHL API] Search contacts error: ${JSON.stringify({ + status: axiosError.response?.status, + statusText: axiosError.response?.statusText, + data: axiosError.response?.data, + message: axiosError.message + }, null, 2)}\n`); + const handledError = this.handleApiError(axiosError); + return { + success: false, + error: { + message: handledError.message, + statusCode: axiosError.response?.status || 500, + details: axiosError.response?.data + } + }; + } + } + /** + * Get duplicate contact by email or phone + * GET /contacts/search/duplicate + */ + async getDuplicateContact(email, phone) { + try { + const params = { + locationId: this.config.locationId + }; + if (email) + params.email = encodeURIComponent(email); + if (phone) + params.number = encodeURIComponent(phone); + const response = await this.axiosInstance.get('/contacts/search/duplicate', { params }); + return this.wrapResponse(response.data.contact || null); + } + catch (error) { + throw error; + } + } + /** + * Add tags to contact + * POST /contacts/{contactId}/tags + */ + async addContactTags(contactId, tags) { + try { + const payload = { tags }; + const response = await this.axiosInstance.post(`/contacts/${contactId}/tags`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Remove tags from contact + * DELETE /contacts/{contactId}/tags + */ + async removeContactTags(contactId, tags) { + try { + const payload = { tags }; + const response = await this.axiosInstance.delete(`/contacts/${contactId}/tags`, { data: payload }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * CONVERSATIONS API METHODS + */ + /** + * Search conversations with filters + * GET /conversations/search + */ + async searchConversations(searchParams) { + try { + // Ensure locationId is set + const params = { + ...searchParams, + locationId: searchParams.locationId || this.config.locationId + }; + const response = await this.axiosInstance.get('/conversations/search', { + params, + headers: this.getConversationHeaders() + }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get conversation by ID + * GET /conversations/{conversationId} + */ + async getConversation(conversationId) { + try { + const response = await this.axiosInstance.get(`/conversations/${conversationId}`, { headers: this.getConversationHeaders() }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Create a new conversation + * POST /conversations/ + */ + async createConversation(conversationData) { + try { + // Ensure locationId is set + const payload = { + ...conversationData, + locationId: conversationData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.post('/conversations/', payload, { headers: this.getConversationHeaders() }); + return this.wrapResponse(response.data.conversation); + } + catch (error) { + throw error; + } + } + /** + * Update conversation + * PUT /conversations/{conversationId} + */ + async updateConversation(conversationId, updates) { + try { + // Ensure locationId is set + const payload = { + ...updates, + locationId: updates.locationId || this.config.locationId + }; + const response = await this.axiosInstance.put(`/conversations/${conversationId}`, payload, { headers: this.getConversationHeaders() }); + return this.wrapResponse(response.data.conversation); + } + catch (error) { + throw error; + } + } + /** + * Delete conversation + * DELETE /conversations/{conversationId} + */ + async deleteConversation(conversationId) { + try { + const response = await this.axiosInstance.delete(`/conversations/${conversationId}`, { headers: this.getConversationHeaders() }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get messages from a conversation + * GET /conversations/{conversationId}/messages + */ + async getConversationMessages(conversationId, options) { + try { + const params = {}; + if (options?.lastMessageId) + params.lastMessageId = options.lastMessageId; + if (options?.limit) + params.limit = options.limit; + if (options?.type) + params.type = options.type; + const response = await this.axiosInstance.get(`/conversations/${conversationId}/messages`, { + params, + headers: this.getConversationHeaders() + }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get message by ID + * GET /conversations/messages/{id} + */ + async getMessage(messageId) { + try { + const response = await this.axiosInstance.get(`/conversations/messages/${messageId}`, { headers: this.getConversationHeaders() }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Send a new message (SMS, Email, etc.) + * POST /conversations/messages + */ + async sendMessage(messageData) { + try { + const response = await this.axiosInstance.post('/conversations/messages', messageData, { headers: this.getConversationHeaders() }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Send SMS message to a contact + * Convenience method for sending SMS + */ + async sendSMS(contactId, message, fromNumber) { + try { + const messageData = { + type: 'SMS', + contactId, + message, + fromNumber + }; + return await this.sendMessage(messageData); + } + catch (error) { + throw error; + } + } + /** + * Send Email message to a contact + * Convenience method for sending Email + */ + async sendEmail(contactId, subject, message, html, options) { + try { + const messageData = { + type: 'Email', + contactId, + subject, + message, + html, + ...options + }; + return await this.sendMessage(messageData); + } + catch (error) { + throw error; + } + } + /** + * BLOG API METHODS + */ + /** + * Get all blog sites for a location + * GET /blogs/site/all + */ + async getBlogSites(params) { + try { + // Ensure locationId is set + const queryParams = { + locationId: params.locationId || this.config.locationId, + skip: params.skip, + limit: params.limit, + ...(params.searchTerm && { searchTerm: params.searchTerm }) + }; + const response = await this.axiosInstance.get('/blogs/site/all', { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get blog posts for a specific blog + * GET /blogs/posts/all + */ + async getBlogPosts(params) { + try { + // Ensure locationId is set + const queryParams = { + locationId: params.locationId || this.config.locationId, + blogId: params.blogId, + limit: params.limit, + offset: params.offset, + ...(params.searchTerm && { searchTerm: params.searchTerm }), + ...(params.status && { status: params.status }) + }; + const response = await this.axiosInstance.get('/blogs/posts/all', { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Create a new blog post + * POST /blogs/posts + */ + async createBlogPost(postData) { + try { + // Ensure locationId is set + const payload = { + ...postData, + locationId: postData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.post('/blogs/posts', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update an existing blog post + * PUT /blogs/posts/{postId} + */ + async updateBlogPost(postId, postData) { + try { + // Ensure locationId is set + const payload = { + ...postData, + locationId: postData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.put(`/blogs/posts/${postId}`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get all blog authors for a location + * GET /blogs/authors + */ + async getBlogAuthors(params) { + try { + // Ensure locationId is set + const queryParams = { + locationId: params.locationId || this.config.locationId, + limit: params.limit, + offset: params.offset + }; + const response = await this.axiosInstance.get('/blogs/authors', { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get all blog categories for a location + * GET /blogs/categories + */ + async getBlogCategories(params) { + try { + // Ensure locationId is set + const queryParams = { + locationId: params.locationId || this.config.locationId, + limit: params.limit, + offset: params.offset + }; + const response = await this.axiosInstance.get('/blogs/categories', { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Check if a URL slug exists (for validation before creating/updating posts) + * GET /blogs/posts/url-slug-exists + */ + async checkUrlSlugExists(params) { + try { + // Ensure locationId is set + const queryParams = { + locationId: params.locationId || this.config.locationId, + urlSlug: params.urlSlug, + ...(params.postId && { postId: params.postId }) + }; + const response = await this.axiosInstance.get('/blogs/posts/url-slug-exists', { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * TASKS API METHODS + */ + /** + * Get all tasks for a contact + * GET /contacts/{contactId}/tasks + */ + async getContactTasks(contactId) { + try { + const response = await this.axiosInstance.get(`/contacts/${contactId}/tasks`); + return this.wrapResponse(response.data.tasks); + } + catch (error) { + throw error; + } + } + /** + * Create task for contact + * POST /contacts/{contactId}/tasks + */ + async createContactTask(contactId, taskData) { + try { + const response = await this.axiosInstance.post(`/contacts/${contactId}/tasks`, taskData); + return this.wrapResponse(response.data.task); + } + catch (error) { + throw error; + } + } + /** + * NOTES API METHODS + */ + /** + * Get all notes for a contact + * GET /contacts/{contactId}/notes + */ + async getContactNotes(contactId) { + try { + const response = await this.axiosInstance.get(`/contacts/${contactId}/notes`); + return this.wrapResponse(response.data.notes); + } + catch (error) { + throw error; + } + } + /** + * Create note for contact + * POST /contacts/{contactId}/notes + */ + async createContactNote(contactId, noteData) { + try { + const response = await this.axiosInstance.post(`/contacts/${contactId}/notes`, noteData); + return this.wrapResponse(response.data.note); + } + catch (error) { + throw error; + } + } + /** + * ADDITIONAL CONTACT API METHODS + */ + /** + * Get a specific task for a contact + * GET /contacts/{contactId}/tasks/{taskId} + */ + async getContactTask(contactId, taskId) { + try { + const response = await this.axiosInstance.get(`/contacts/${contactId}/tasks/${taskId}`); + return this.wrapResponse(response.data.task); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update a task for a contact + * PUT /contacts/{contactId}/tasks/{taskId} + */ + async updateContactTask(contactId, taskId, updates) { + try { + const response = await this.axiosInstance.put(`/contacts/${contactId}/tasks/${taskId}`, updates); + return this.wrapResponse(response.data.task); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Delete a task for a contact + * DELETE /contacts/{contactId}/tasks/{taskId} + */ + async deleteContactTask(contactId, taskId) { + try { + const response = await this.axiosInstance.delete(`/contacts/${contactId}/tasks/${taskId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update task completion status + * PUT /contacts/{contactId}/tasks/{taskId}/completed + */ + async updateTaskCompletion(contactId, taskId, completed) { + try { + const response = await this.axiosInstance.put(`/contacts/${contactId}/tasks/${taskId}/completed`, { completed }); + return this.wrapResponse(response.data.task); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get a specific note for a contact + * GET /contacts/{contactId}/notes/{noteId} + */ + async getContactNote(contactId, noteId) { + try { + const response = await this.axiosInstance.get(`/contacts/${contactId}/notes/${noteId}`); + return this.wrapResponse(response.data.note); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update a note for a contact + * PUT /contacts/{contactId}/notes/{noteId} + */ + async updateContactNote(contactId, noteId, updates) { + try { + const response = await this.axiosInstance.put(`/contacts/${contactId}/notes/${noteId}`, updates); + return this.wrapResponse(response.data.note); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Delete a note for a contact + * DELETE /contacts/{contactId}/notes/{noteId} + */ + async deleteContactNote(contactId, noteId) { + try { + const response = await this.axiosInstance.delete(`/contacts/${contactId}/notes/${noteId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Upsert contact (create or update based on email/phone) + * POST /contacts/upsert + */ + async upsertContact(contactData) { + try { + const payload = { + ...contactData, + locationId: contactData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.post('/contacts/upsert', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get contacts by business ID + * GET /contacts/business/{businessId} + */ + async getContactsByBusiness(businessId, params = {}) { + try { + const queryParams = { + limit: params.limit || 25, + skip: params.skip || 0, + ...(params.query && { query: params.query }) + }; + const response = await this.axiosInstance.get(`/contacts/business/${businessId}`, { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get contact appointments + * GET /contacts/{contactId}/appointments + */ + async getContactAppointments(contactId) { + try { + const response = await this.axiosInstance.get(`/contacts/${contactId}/appointments`); + return this.wrapResponse(response.data.events); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Bulk update contact tags + * POST /contacts/tags/bulk + */ + async bulkUpdateContactTags(contactIds, tags, operation, removeAllTags) { + try { + const payload = { + ids: contactIds, + tags, + operation, + ...(removeAllTags !== undefined && { removeAllTags }) + }; + const response = await this.axiosInstance.post('/contacts/tags/bulk', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Bulk update contact business + * POST /contacts/business/bulk + */ + async bulkUpdateContactBusiness(contactIds, businessId) { + try { + const payload = { + ids: contactIds, + businessId: businessId || null + }; + const response = await this.axiosInstance.post('/contacts/business/bulk', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Add contact followers + * POST /contacts/{contactId}/followers + */ + async addContactFollowers(contactId, followers) { + try { + const payload = { followers }; + const response = await this.axiosInstance.post(`/contacts/${contactId}/followers`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Remove contact followers + * DELETE /contacts/{contactId}/followers + */ + async removeContactFollowers(contactId, followers) { + try { + const payload = { followers }; + const response = await this.axiosInstance.delete(`/contacts/${contactId}/followers`, { data: payload }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Add contact to campaign + * POST /contacts/{contactId}/campaigns/{campaignId} + */ + async addContactToCampaign(contactId, campaignId) { + try { + const response = await this.axiosInstance.post(`/contacts/${contactId}/campaigns/${campaignId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Remove contact from campaign + * DELETE /contacts/{contactId}/campaigns/{campaignId} + */ + async removeContactFromCampaign(contactId, campaignId) { + try { + const response = await this.axiosInstance.delete(`/contacts/${contactId}/campaigns/${campaignId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Remove contact from all campaigns + * DELETE /contacts/{contactId}/campaigns + */ + async removeContactFromAllCampaigns(contactId) { + try { + const response = await this.axiosInstance.delete(`/contacts/${contactId}/campaigns`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Add contact to workflow + * POST /contacts/{contactId}/workflow/{workflowId} + */ + async addContactToWorkflow(contactId, workflowId, eventStartTime) { + try { + const payload = eventStartTime ? { eventStartTime } : {}; + const response = await this.axiosInstance.post(`/contacts/${contactId}/workflow/${workflowId}`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Remove contact from workflow + * DELETE /contacts/{contactId}/workflow/{workflowId} + */ + async removeContactFromWorkflow(contactId, workflowId, eventStartTime) { + try { + const payload = eventStartTime ? { eventStartTime } : {}; + const response = await this.axiosInstance.delete(`/contacts/${contactId}/workflow/${workflowId}`, { data: payload }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * UTILITY METHODS + */ + /** + * Test API connection and authentication + */ + async testConnection() { + try { + // Test with a simple GET request to check API connectivity + const response = await this.axiosInstance.get('/locations/' + this.config.locationId); + return this.wrapResponse({ + status: 'connected', + locationId: this.config.locationId + }); + } + catch (error) { + throw new Error(`GHL API connection test failed: ${error}`); + } + } + /** + * Update access token + */ + updateAccessToken(newToken) { + this.config.accessToken = newToken; + this.axiosInstance.defaults.headers['Authorization'] = `Bearer ${newToken}`; + process.stderr.write('[GHL API] Access token updated\n'); + } + /** + * Get current configuration + */ + getConfig() { + return { ...this.config }; + } + /** + * Generic request method for new endpoints + * Used by new tool modules that don't have specific client methods yet + */ + async makeRequest(method, path, body) { + try { + let response; + switch (method) { + case 'GET': + response = await this.axiosInstance.get(path); + break; + case 'POST': + response = await this.axiosInstance.post(path, body); + break; + case 'PUT': + response = await this.axiosInstance.put(path, body); + break; + case 'PATCH': + response = await this.axiosInstance.patch(path, body); + break; + case 'DELETE': + response = await this.axiosInstance.delete(path); + break; + } + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * OPPORTUNITIES API METHODS + */ + /** + * Search opportunities with advanced filters + * GET /opportunities/search + */ + async searchOpportunities(searchParams) { + try { + // Build query parameters with exact API naming (underscores) + const params = { + location_id: searchParams.location_id || this.config.locationId + }; + // Add optional search parameters only if they have values + if (searchParams.q && searchParams.q.trim()) { + params.q = searchParams.q.trim(); + } + if (searchParams.pipeline_id) { + params.pipeline_id = searchParams.pipeline_id; + } + if (searchParams.pipeline_stage_id) { + params.pipeline_stage_id = searchParams.pipeline_stage_id; + } + if (searchParams.contact_id) { + params.contact_id = searchParams.contact_id; + } + if (searchParams.status) { + params.status = searchParams.status; + } + if (searchParams.assigned_to) { + params.assigned_to = searchParams.assigned_to; + } + if (searchParams.campaignId) { + params.campaignId = searchParams.campaignId; + } + if (searchParams.id) { + params.id = searchParams.id; + } + if (searchParams.order) { + params.order = searchParams.order; + } + if (searchParams.endDate) { + params.endDate = searchParams.endDate; + } + if (searchParams.startAfter) { + params.startAfter = searchParams.startAfter; + } + if (searchParams.startAfterId) { + params.startAfterId = searchParams.startAfterId; + } + if (searchParams.date) { + params.date = searchParams.date; + } + if (searchParams.country) { + params.country = searchParams.country; + } + if (searchParams.page) { + params.page = searchParams.page; + } + if (searchParams.limit) { + params.limit = searchParams.limit; + } + if (searchParams.getTasks !== undefined) { + params.getTasks = searchParams.getTasks; + } + if (searchParams.getNotes !== undefined) { + params.getNotes = searchParams.getNotes; + } + if (searchParams.getCalendarEvents !== undefined) { + params.getCalendarEvents = searchParams.getCalendarEvents; + } + process.stderr.write(`[GHL API] Search opportunities params: ${JSON.stringify(params, null, 2)}\n`); + const response = await this.axiosInstance.get('/opportunities/search', { params }); + return this.wrapResponse(response.data); + } + catch (error) { + const axiosError = error; + process.stderr.write(`[GHL API] Search opportunities error: ${JSON.stringify({ + status: axiosError.response?.status, + statusText: axiosError.response?.statusText, + data: axiosError.response?.data, + message: axiosError.message + }, null, 2)}\n`); + throw this.handleApiError(axiosError); + } + } + /** + * Get all pipelines for a location + * GET /opportunities/pipelines + */ + async getPipelines(locationId) { + try { + const params = { + locationId: locationId || this.config.locationId + }; + const response = await this.axiosInstance.get('/opportunities/pipelines', { params }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get opportunity by ID + * GET /opportunities/{id} + */ + async getOpportunity(opportunityId) { + try { + const response = await this.axiosInstance.get(`/opportunities/${opportunityId}`); + return this.wrapResponse(response.data.opportunity); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Create a new opportunity + * POST /opportunities/ + */ + async createOpportunity(opportunityData) { + try { + // Ensure locationId is set + const payload = { + ...opportunityData, + locationId: opportunityData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.post('/opportunities/', payload); + return this.wrapResponse(response.data.opportunity); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update existing opportunity + * PUT /opportunities/{id} + */ + async updateOpportunity(opportunityId, updates) { + try { + const response = await this.axiosInstance.put(`/opportunities/${opportunityId}`, updates); + return this.wrapResponse(response.data.opportunity); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update opportunity status + * PUT /opportunities/{id}/status + */ + async updateOpportunityStatus(opportunityId, status) { + try { + const payload = { status }; + const response = await this.axiosInstance.put(`/opportunities/${opportunityId}/status`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Upsert opportunity (create or update) + * POST /opportunities/upsert + */ + async upsertOpportunity(opportunityData) { + try { + // Ensure locationId is set + const payload = { + ...opportunityData, + locationId: opportunityData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.post('/opportunities/upsert', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Delete opportunity + * DELETE /opportunities/{id} + */ + async deleteOpportunity(opportunityId) { + try { + const response = await this.axiosInstance.delete(`/opportunities/${opportunityId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Add followers to opportunity + * POST /opportunities/{id}/followers + */ + async addOpportunityFollowers(opportunityId, followers) { + try { + const payload = { followers }; + const response = await this.axiosInstance.post(`/opportunities/${opportunityId}/followers`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Remove followers from opportunity + * DELETE /opportunities/{id}/followers + */ + async removeOpportunityFollowers(opportunityId, followers) { + try { + const payload = { followers }; + const response = await this.axiosInstance.delete(`/opportunities/${opportunityId}/followers`, { data: payload }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * CALENDAR & APPOINTMENTS API METHODS + */ + /** + * Get all calendar groups in a location + * GET /calendars/groups + */ + async getCalendarGroups(locationId) { + try { + const params = { + locationId: locationId || this.config.locationId + }; + const response = await this.axiosInstance.get('/calendars/groups', { params }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Create a new calendar group + * POST /calendars/groups + */ + async createCalendarGroup(groupData) { + try { + const payload = { + ...groupData, + locationId: groupData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.post('/calendars/groups', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get all calendars in a location + * GET /calendars/ + */ + async getCalendars(params) { + try { + const queryParams = { + locationId: params?.locationId || this.config.locationId, + ...(params?.groupId && { groupId: params.groupId }), + ...(params?.showDrafted !== undefined && { showDrafted: params.showDrafted }) + }; + const response = await this.axiosInstance.get('/calendars/', { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Create a new calendar + * POST /calendars/ + */ + async createCalendar(calendarData) { + try { + const payload = { + ...calendarData, + locationId: calendarData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.post('/calendars/', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get calendar by ID + * GET /calendars/{calendarId} + */ + async getCalendar(calendarId) { + try { + const response = await this.axiosInstance.get(`/calendars/${calendarId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update calendar by ID + * PUT /calendars/{calendarId} + */ + async updateCalendar(calendarId, updates) { + try { + const response = await this.axiosInstance.put(`/calendars/${calendarId}`, updates); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Delete calendar by ID + * DELETE /calendars/{calendarId} + */ + async deleteCalendar(calendarId) { + try { + const response = await this.axiosInstance.delete(`/calendars/${calendarId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get calendar events/appointments + * GET /calendars/events + */ + async getCalendarEvents(eventParams) { + try { + const params = { + locationId: eventParams.locationId || this.config.locationId, + startTime: eventParams.startTime, + endTime: eventParams.endTime, + ...(eventParams.userId && { userId: eventParams.userId }), + ...(eventParams.calendarId && { calendarId: eventParams.calendarId }), + ...(eventParams.groupId && { groupId: eventParams.groupId }) + }; + const response = await this.axiosInstance.get('/calendars/events', { params }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get blocked slots + * GET /calendars/blocked-slots + */ + async getBlockedSlots(eventParams) { + try { + const params = { + locationId: eventParams.locationId || this.config.locationId, + startTime: eventParams.startTime, + endTime: eventParams.endTime, + ...(eventParams.userId && { userId: eventParams.userId }), + ...(eventParams.calendarId && { calendarId: eventParams.calendarId }), + ...(eventParams.groupId && { groupId: eventParams.groupId }) + }; + const response = await this.axiosInstance.get('/calendars/blocked-slots', { params }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get free slots for a calendar + * GET /calendars/{calendarId}/free-slots + */ + async getFreeSlots(slotParams) { + try { + const params = { + startDate: slotParams.startDate, + endDate: slotParams.endDate, + ...(slotParams.timezone && { timezone: slotParams.timezone }), + ...(slotParams.userId && { userId: slotParams.userId }), + ...(slotParams.userIds && { userIds: slotParams.userIds }), + ...(slotParams.enableLookBusy !== undefined && { enableLookBusy: slotParams.enableLookBusy }) + }; + const response = await this.axiosInstance.get(`/calendars/${slotParams.calendarId}/free-slots`, { params }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Create a new appointment + * POST /calendars/events/appointments + */ + async createAppointment(appointmentData) { + try { + const payload = { + ...appointmentData, + locationId: appointmentData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.post('/calendars/events/appointments', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get appointment by ID + * GET /calendars/events/appointments/{eventId} + */ + async getAppointment(appointmentId) { + try { + const response = await this.axiosInstance.get(`/calendars/events/appointments/${appointmentId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update appointment by ID + * PUT /calendars/events/appointments/{eventId} + */ + async updateAppointment(appointmentId, updates) { + try { + const response = await this.axiosInstance.put(`/calendars/events/appointments/${appointmentId}`, updates); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Delete appointment by ID + * DELETE /calendars/events/appointments/{eventId} + */ + async deleteAppointment(appointmentId) { + try { + const response = await this.axiosInstance.delete(`/calendars/events/appointments/${appointmentId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update block slot by ID + * PUT /calendars/events/block-slots/{eventId} + */ + async updateBlockSlot(blockSlotId, updates) { + try { + const response = await this.axiosInstance.put(`/calendars/events/block-slots/${blockSlotId}`, updates); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * EMAIL API METHODS + */ + async getEmailCampaigns(params) { + try { + const response = await this.axiosInstance.get('/emails/schedule', { + params: { + locationId: this.config.locationId, + ...params + } + }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + async createEmailTemplate(params) { + try { + const response = await this.axiosInstance.post('/emails/builder', { + locationId: this.config.locationId, + type: 'html', + ...params + }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + async getEmailTemplates(params) { + try { + const response = await this.axiosInstance.get('/emails/builder', { + params: { + locationId: this.config.locationId, + ...params + } + }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + async updateEmailTemplate(params) { + try { + const { templateId, ...data } = params; + const response = await this.axiosInstance.post('/emails/builder/data', { + locationId: this.config.locationId, + templateId, + ...data, + editorType: 'html' + }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + async deleteEmailTemplate(params) { + try { + const { templateId } = params; + const response = await this.axiosInstance.delete(`/emails/builder/${this.config.locationId}/${templateId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * LOCATION API METHODS + */ + /** + * Search locations/sub-accounts + * GET /locations/search + */ + async searchLocations(params = {}) { + try { + const queryParams = { + skip: params.skip || 0, + limit: params.limit || 10, + order: params.order || 'asc', + ...(params.companyId && { companyId: params.companyId }), + ...(params.email && { email: params.email }) + }; + const response = await this.axiosInstance.get('/locations/search', { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get location by ID + * GET /locations/{locationId} + */ + async getLocationById(locationId) { + try { + const response = await this.axiosInstance.get(`/locations/${locationId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Create a new location/sub-account + * POST /locations/ + */ + async createLocation(locationData) { + try { + const response = await this.axiosInstance.post('/locations/', locationData); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update location/sub-account + * PUT /locations/{locationId} + */ + async updateLocation(locationId, updates) { + try { + const response = await this.axiosInstance.put(`/locations/${locationId}`, updates); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Delete location/sub-account + * DELETE /locations/{locationId} + */ + async deleteLocation(locationId, deleteTwilioAccount) { + try { + const response = await this.axiosInstance.delete(`/locations/${locationId}`, { + params: { deleteTwilioAccount } + }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * LOCATION TAGS API METHODS + */ + /** + * Get location tags + * GET /locations/{locationId}/tags + */ + async getLocationTags(locationId) { + try { + const response = await this.axiosInstance.get(`/locations/${locationId}/tags`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Create location tag + * POST /locations/{locationId}/tags + */ + async createLocationTag(locationId, tagData) { + try { + const response = await this.axiosInstance.post(`/locations/${locationId}/tags`, tagData); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get location tag by ID + * GET /locations/{locationId}/tags/{tagId} + */ + async getLocationTag(locationId, tagId) { + try { + const response = await this.axiosInstance.get(`/locations/${locationId}/tags/${tagId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update location tag + * PUT /locations/{locationId}/tags/{tagId} + */ + async updateLocationTag(locationId, tagId, tagData) { + try { + const response = await this.axiosInstance.put(`/locations/${locationId}/tags/${tagId}`, tagData); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Delete location tag + * DELETE /locations/{locationId}/tags/{tagId} + */ + async deleteLocationTag(locationId, tagId) { + try { + const response = await this.axiosInstance.delete(`/locations/${locationId}/tags/${tagId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * LOCATION TASKS API METHODS + */ + /** + * Search location tasks + * POST /locations/{locationId}/tasks/search + */ + async searchLocationTasks(locationId, searchParams) { + try { + const response = await this.axiosInstance.post(`/locations/${locationId}/tasks/search`, searchParams); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * CUSTOM FIELDS API METHODS + */ + /** + * Get custom fields for location + * GET /locations/{locationId}/customFields + */ + async getLocationCustomFields(locationId, model) { + try { + const params = {}; + if (model) + params.model = model; + const response = await this.axiosInstance.get(`/locations/${locationId}/customFields`, { params }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Create custom field for location + * POST /locations/{locationId}/customFields + */ + async createLocationCustomField(locationId, fieldData) { + try { + const response = await this.axiosInstance.post(`/locations/${locationId}/customFields`, fieldData); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get custom field by ID + * GET /locations/{locationId}/customFields/{id} + */ + async getLocationCustomField(locationId, customFieldId) { + try { + const response = await this.axiosInstance.get(`/locations/${locationId}/customFields/${customFieldId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update custom field + * PUT /locations/{locationId}/customFields/{id} + */ + async updateLocationCustomField(locationId, customFieldId, fieldData) { + try { + const response = await this.axiosInstance.put(`/locations/${locationId}/customFields/${customFieldId}`, fieldData); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Delete custom field + * DELETE /locations/{locationId}/customFields/{id} + */ + async deleteLocationCustomField(locationId, customFieldId) { + try { + const response = await this.axiosInstance.delete(`/locations/${locationId}/customFields/${customFieldId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Upload file to custom fields + * POST /locations/{locationId}/customFields/upload + */ + async uploadLocationCustomFieldFile(locationId, uploadData) { + try { + // Note: This endpoint expects multipart/form-data but we'll handle it as JSON for now + // In a real implementation, you'd use FormData for file uploads + const response = await this.axiosInstance.post(`/locations/${locationId}/customFields/upload`, uploadData, { headers: { 'Content-Type': 'multipart/form-data' } }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * CUSTOM VALUES API METHODS + */ + /** + * Get custom values for location + * GET /locations/{locationId}/customValues + */ + async getLocationCustomValues(locationId) { + try { + const response = await this.axiosInstance.get(`/locations/${locationId}/customValues`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Create custom value for location + * POST /locations/{locationId}/customValues + */ + async createLocationCustomValue(locationId, valueData) { + try { + const response = await this.axiosInstance.post(`/locations/${locationId}/customValues`, valueData); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get custom value by ID + * GET /locations/{locationId}/customValues/{id} + */ + async getLocationCustomValue(locationId, customValueId) { + try { + const response = await this.axiosInstance.get(`/locations/${locationId}/customValues/${customValueId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update custom value + * PUT /locations/{locationId}/customValues/{id} + */ + async updateLocationCustomValue(locationId, customValueId, valueData) { + try { + const response = await this.axiosInstance.put(`/locations/${locationId}/customValues/${customValueId}`, valueData); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Delete custom value + * DELETE /locations/{locationId}/customValues/{id} + */ + async deleteLocationCustomValue(locationId, customValueId) { + try { + const response = await this.axiosInstance.delete(`/locations/${locationId}/customValues/${customValueId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * TEMPLATES API METHODS + */ + /** + * Get location templates (SMS/Email) + * GET /locations/{locationId}/templates + */ + async getLocationTemplates(locationId, params) { + try { + const queryParams = { + originId: params.originId, + deleted: params.deleted || false, + skip: params.skip || 0, + limit: params.limit || 25, + ...(params.type && { type: params.type }) + }; + const response = await this.axiosInstance.get(`/locations/${locationId}/templates`, { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Delete location template + * DELETE /locations/{locationId}/templates/{id} + */ + async deleteLocationTemplate(locationId, templateId) { + try { + const response = await this.axiosInstance.delete(`/locations/${locationId}/templates/${templateId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * TIMEZONES API METHODS + */ + /** + * Get available timezones + * GET /locations/{locationId}/timezones + */ + async getTimezones(locationId) { + try { + const endpoint = locationId ? `/locations/${locationId}/timezones` : '/locations/timezones'; + const response = await this.axiosInstance.get(endpoint); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * EMAIL ISV (VERIFICATION) API METHODS + */ + /** + * Verify email address or contact + * POST /email/verify + */ + async verifyEmail(locationId, verificationData) { + try { + const params = { + locationId: locationId + }; + const response = await this.axiosInstance.post('/email/verify', verificationData, { params }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * ADDITIONAL CONVERSATION/MESSAGE API METHODS + */ + /** + * Get email message by ID + * GET /conversations/messages/email/{id} + */ + async getEmailMessage(emailMessageId) { + try { + const response = await this.axiosInstance.get(`/conversations/messages/email/${emailMessageId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Cancel scheduled email message + * DELETE /conversations/messages/email/{emailMessageId}/schedule + */ + async cancelScheduledEmail(emailMessageId) { + try { + const response = await this.axiosInstance.delete(`/conversations/messages/email/${emailMessageId}/schedule`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Add inbound message manually + * POST /conversations/messages/inbound + */ + async addInboundMessage(messageData) { + try { + const response = await this.axiosInstance.post('/conversations/messages/inbound', messageData, { headers: this.getConversationHeaders() }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Add outbound call manually + * POST /conversations/messages/outbound + */ + async addOutboundCall(messageData) { + try { + const response = await this.axiosInstance.post('/conversations/messages/outbound', messageData, { headers: this.getConversationHeaders() }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Cancel scheduled message + * DELETE /conversations/messages/{messageId}/schedule + */ + async cancelScheduledMessage(messageId) { + try { + const response = await this.axiosInstance.delete(`/conversations/messages/${messageId}/schedule`, { headers: this.getConversationHeaders() }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Upload file attachments for messages + * POST /conversations/messages/upload + */ + async uploadMessageAttachments(uploadData) { + try { + const response = await this.axiosInstance.post('/conversations/messages/upload', uploadData, { + headers: { + ...this.getConversationHeaders(), + 'Content-Type': 'multipart/form-data' + } + }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update message status + * PUT /conversations/messages/{messageId}/status + */ + async updateMessageStatus(messageId, statusData) { + try { + const response = await this.axiosInstance.put(`/conversations/messages/${messageId}/status`, statusData, { headers: this.getConversationHeaders() }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get message recording + * GET /conversations/messages/{messageId}/locations/{locationId}/recording + */ + async getMessageRecording(messageId, locationId) { + try { + const locId = locationId || this.config.locationId; + const response = await this.axiosInstance.get(`/conversations/messages/${messageId}/locations/${locId}/recording`, { + headers: this.getConversationHeaders(), + responseType: 'arraybuffer' + }); + const recordingResponse = { + audioData: response.data, + contentType: response.headers['content-type'] || 'audio/x-wav', + contentDisposition: response.headers['content-disposition'] || 'attachment; filename=audio.wav' + }; + return this.wrapResponse(recordingResponse); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get message transcription + * GET /conversations/locations/{locationId}/messages/{messageId}/transcription + */ + async getMessageTranscription(messageId, locationId) { + try { + const locId = locationId || this.config.locationId; + const response = await this.axiosInstance.get(`/conversations/locations/${locId}/messages/${messageId}/transcription`, { headers: this.getConversationHeaders() }); + return this.wrapResponse({ transcriptions: response.data }); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Download message transcription + * GET /conversations/locations/{locationId}/messages/{messageId}/transcription/download + */ + async downloadMessageTranscription(messageId, locationId) { + try { + const locId = locationId || this.config.locationId; + const response = await this.axiosInstance.get(`/conversations/locations/${locId}/messages/${messageId}/transcription/download`, { + headers: this.getConversationHeaders(), + responseType: 'text' + }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Live chat typing indicator + * POST /conversations/providers/live-chat/typing + */ + async liveChatTyping(typingData) { + try { + const response = await this.axiosInstance.post('/conversations/providers/live-chat/typing', typingData, { headers: this.getConversationHeaders() }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + // ============================================================================ + // SOCIAL MEDIA POSTING API METHODS + // ============================================================================ + // ===== POST MANAGEMENT ===== + /** + * Search/List Social Media Posts + */ + async searchSocialPosts(searchData) { + try { + const locationId = this.config.locationId; + const response = await this.axiosInstance.post(`/social-media-posting/${locationId}/posts/list`, searchData); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Create Social Media Post + */ + async createSocialPost(postData) { + try { + const locationId = this.config.locationId; + const response = await this.axiosInstance.post(`/social-media-posting/${locationId}/posts`, postData); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get Social Media Post by ID + */ + async getSocialPost(postId) { + try { + const locationId = this.config.locationId; + const response = await this.axiosInstance.get(`/social-media-posting/${locationId}/posts/${postId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update Social Media Post + */ + async updateSocialPost(postId, updateData) { + try { + const locationId = this.config.locationId; + const response = await this.axiosInstance.put(`/social-media-posting/${locationId}/posts/${postId}`, updateData); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Delete Social Media Post + */ + async deleteSocialPost(postId) { + try { + const locationId = this.config.locationId; + const response = await this.axiosInstance.delete(`/social-media-posting/${locationId}/posts/${postId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Bulk Delete Social Media Posts + */ + async bulkDeleteSocialPosts(deleteData) { + try { + const locationId = this.config.locationId; + const response = await this.axiosInstance.post(`/social-media-posting/${locationId}/posts/bulk-delete`, deleteData); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + // ===== ACCOUNT MANAGEMENT ===== + /** + * Get Social Media Accounts and Groups + */ + async getSocialAccounts() { + try { + const locationId = this.config.locationId; + const response = await this.axiosInstance.get(`/social-media-posting/${locationId}/accounts`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Delete Social Media Account + */ + async deleteSocialAccount(accountId, companyId, userId) { + try { + const locationId = this.config.locationId; + const params = {}; + if (companyId) + params.companyId = companyId; + if (userId) + params.userId = userId; + const response = await this.axiosInstance.delete(`/social-media-posting/${locationId}/accounts/${accountId}`, { params }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + // ===== CSV OPERATIONS ===== + /** + * Upload CSV for Social Media Posts + */ + async uploadSocialCSV(csvData) { + try { + const locationId = this.config.locationId; + // Note: This would typically use FormData for file upload + const response = await this.axiosInstance.post(`/social-media-posting/${locationId}/csv`, csvData); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get CSV Upload Status + */ + async getSocialCSVUploadStatus(skip, limit, includeUsers, userId) { + try { + const locationId = this.config.locationId; + const params = {}; + if (skip !== undefined) + params.skip = skip.toString(); + if (limit !== undefined) + params.limit = limit.toString(); + if (includeUsers !== undefined) + params.includeUsers = includeUsers.toString(); + if (userId) + params.userId = userId; + const response = await this.axiosInstance.get(`/social-media-posting/${locationId}/csv`, { params }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Set Accounts for CSV Import + */ + async setSocialCSVAccounts(accountsData) { + try { + const locationId = this.config.locationId; + const response = await this.axiosInstance.post(`/social-media-posting/${locationId}/set-accounts`, accountsData); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get CSV Posts + */ + async getSocialCSVPosts(csvId, skip, limit) { + try { + const locationId = this.config.locationId; + const params = {}; + if (skip !== undefined) + params.skip = skip.toString(); + if (limit !== undefined) + params.limit = limit.toString(); + const response = await this.axiosInstance.get(`/social-media-posting/${locationId}/csv/${csvId}`, { params }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Start CSV Finalization + */ + async finalizeSocialCSV(csvId, finalizeData) { + try { + const locationId = this.config.locationId; + const response = await this.axiosInstance.patch(`/social-media-posting/${locationId}/csv/${csvId}`, finalizeData); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Delete CSV Import + */ + async deleteSocialCSV(csvId) { + try { + const locationId = this.config.locationId; + const response = await this.axiosInstance.delete(`/social-media-posting/${locationId}/csv/${csvId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Delete CSV Post + */ + async deleteSocialCSVPost(csvId, postId) { + try { + const locationId = this.config.locationId; + const response = await this.axiosInstance.delete(`/social-media-posting/${locationId}/csv/${csvId}/post/${postId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + // ===== CATEGORIES & TAGS ===== + /** + * Get Social Media Categories + */ + async getSocialCategories(searchText, limit, skip) { + // TODO: Implement this method properly + throw new Error('Method not yet implemented'); + } + // TODO: Implement remaining social media API methods + async getSocialCategory(categoryId) { + throw new Error('Method not yet implemented'); + } + async getSocialTags(searchText, limit, skip) { + throw new Error('Method not yet implemented'); + } + async getSocialTagsByIds(tagData) { + throw new Error('Method not yet implemented'); + } + async startSocialOAuth(platform, userId, page, reconnect) { + throw new Error('Method not yet implemented'); + } + async getGoogleBusinessLocations(accountId) { + throw new Error('Method not yet implemented'); + } + async setGoogleBusinessLocations(accountId, locationData) { + throw new Error('Method not yet implemented'); + } + async getFacebookPages(accountId) { + throw new Error('Method not yet implemented'); + } + async attachFacebookPages(accountId, pageData) { + throw new Error('Method not yet implemented'); + } + async getInstagramAccounts(accountId) { + throw new Error('Method not yet implemented'); + } + async attachInstagramAccounts(accountId, accountData) { + throw new Error('Method not yet implemented'); + } + async getLinkedInAccounts(accountId) { + throw new Error('Method not yet implemented'); + } + async attachLinkedInAccounts(accountId, accountData) { + throw new Error('Method not yet implemented'); + } + async getTwitterProfile(accountId) { + throw new Error('Method not yet implemented'); + } + async attachTwitterProfile(accountId, profileData) { + throw new Error('Method not yet implemented'); + } + async getTikTokProfile(accountId) { + throw new Error('Method not yet implemented'); + } + async attachTikTokProfile(accountId, profileData) { + throw new Error('Method not yet implemented'); + } + async getTikTokBusinessProfile(accountId) { + throw new Error('Method not yet implemented'); + } + // ===== MISSING CALENDAR GROUPS MANAGEMENT METHODS ===== + /** + * Validate calendar group slug + * GET /calendars/groups/slug/validate + */ + async validateCalendarGroupSlug(slug, locationId) { + try { + const params = { + locationId: locationId || this.config.locationId, + slug + }; + const response = await this.axiosInstance.get('/calendars/groups/slug/validate', { params }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update calendar group by ID + * PUT /calendars/groups/{groupId} + */ + async updateCalendarGroup(groupId, updateData) { + try { + const response = await this.axiosInstance.put(`/calendars/groups/${groupId}`, updateData); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Delete calendar group by ID + * DELETE /calendars/groups/{groupId} + */ + async deleteCalendarGroup(groupId) { + try { + const response = await this.axiosInstance.delete(`/calendars/groups/${groupId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Disable calendar group + * POST /calendars/groups/{groupId}/status + */ + async disableCalendarGroup(groupId, isActive) { + try { + const payload = { isActive }; + const response = await this.axiosInstance.post(`/calendars/groups/${groupId}/status`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + // ===== APPOINTMENT NOTES METHODS ===== + /** + * Get appointment notes + * GET /calendars/events/appointments/{appointmentId}/notes + */ + async getAppointmentNotes(appointmentId, limit = 10, offset = 0) { + try { + const params = { limit, offset }; + const response = await this.axiosInstance.get(`/calendars/events/appointments/${appointmentId}/notes`, { params }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Create appointment note + * POST /calendars/events/appointments/{appointmentId}/notes + */ + async createAppointmentNote(appointmentId, noteData) { + try { + const response = await this.axiosInstance.post(`/calendars/events/appointments/${appointmentId}/notes`, noteData); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update appointment note + * PUT /calendars/events/appointments/{appointmentId}/notes/{noteId} + */ + async updateAppointmentNote(appointmentId, noteId, updateData) { + try { + const response = await this.axiosInstance.put(`/calendars/events/appointments/${appointmentId}/notes/${noteId}`, updateData); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Delete appointment note + * DELETE /calendars/events/appointments/{appointmentId}/notes/{noteId} + */ + async deleteAppointmentNote(appointmentId, noteId) { + try { + const response = await this.axiosInstance.delete(`/calendars/events/appointments/${appointmentId}/notes/${noteId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + // ===== CALENDAR RESOURCES METHODS ===== + /** + * Get calendar resources + * GET /calendars/resources/{resourceType} + */ + async getCalendarResources(resourceType, limit = 20, skip = 0, locationId) { + try { + const params = { + locationId: locationId || this.config.locationId, + limit, + skip + }; + const response = await this.axiosInstance.get(`/calendars/resources/${resourceType}`, { params }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Create calendar resource + * POST /calendars/resources/{resourceType} + */ + async createCalendarResource(resourceType, resourceData) { + try { + const payload = { + ...resourceData, + locationId: resourceData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.post(`/calendars/resources/${resourceType}`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get calendar resource by ID + * GET /calendars/resources/{resourceType}/{resourceId} + */ + async getCalendarResource(resourceType, resourceId) { + try { + const response = await this.axiosInstance.get(`/calendars/resources/${resourceType}/${resourceId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update calendar resource + * PUT /calendars/resources/{resourceType}/{resourceId} + */ + async updateCalendarResource(resourceType, resourceId, updateData) { + try { + const response = await this.axiosInstance.put(`/calendars/resources/${resourceType}/${resourceId}`, updateData); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Delete calendar resource + * DELETE /calendars/resources/{resourceType}/{resourceId} + */ + async deleteCalendarResource(resourceType, resourceId) { + try { + const response = await this.axiosInstance.delete(`/calendars/resources/${resourceType}/${resourceId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + // ===== CALENDAR NOTIFICATIONS METHODS ===== + /** + * Get calendar notifications + * GET /calendars/{calendarId}/notifications + */ + async getCalendarNotifications(calendarId, queryParams) { + try { + const params = { + ...queryParams + }; + const response = await this.axiosInstance.get(`/calendars/${calendarId}/notifications`, { params }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Create calendar notifications + * POST /calendars/{calendarId}/notifications + */ + async createCalendarNotifications(calendarId, notifications) { + try { + const payload = { notifications }; + const response = await this.axiosInstance.post(`/calendars/${calendarId}/notifications`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get calendar notification by ID + * GET /calendars/{calendarId}/notifications/{notificationId} + */ + async getCalendarNotification(calendarId, notificationId) { + try { + const response = await this.axiosInstance.get(`/calendars/${calendarId}/notifications/${notificationId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update calendar notification + * PUT /calendars/{calendarId}/notifications/{notificationId} + */ + async updateCalendarNotification(calendarId, notificationId, updateData) { + try { + const response = await this.axiosInstance.put(`/calendars/${calendarId}/notifications/${notificationId}`, updateData); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Delete calendar notification + * DELETE /calendars/{calendarId}/notifications/{notificationId} + */ + async deleteCalendarNotification(calendarId, notificationId) { + try { + const response = await this.axiosInstance.delete(`/calendars/${calendarId}/notifications/${notificationId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get blocked slots by location + * GET /calendars/blocked-slots + */ + async getBlockedSlotsByLocation(slotParams) { + try { + const params = new URLSearchParams({ + locationId: slotParams.locationId, + startTime: slotParams.startTime, + endTime: slotParams.endTime, + ...(slotParams.userId && { userId: slotParams.userId }), + ...(slotParams.calendarId && { calendarId: slotParams.calendarId }), + ...(slotParams.groupId && { groupId: slotParams.groupId }) + }); + const response = await this.axiosInstance.get(`/calendars/blocked-slots?${params}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Create a new block slot + * POST /calendars/blocked-slots + */ + async createBlockSlot(blockSlotData) { + try { + const payload = { + ...blockSlotData, + locationId: blockSlotData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.post('/calendars/blocked-slots', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + // ===== MEDIA LIBRARY API METHODS ===== + /** + * Get list of files and folders from media library + * GET /medias/files + */ + async getMediaFiles(params) { + try { + const queryParams = new URLSearchParams({ + sortBy: params.sortBy, + sortOrder: params.sortOrder, + altType: params.altType, + altId: params.altId, + ...(params.offset !== undefined && { offset: params.offset.toString() }), + ...(params.limit !== undefined && { limit: params.limit.toString() }), + ...(params.type && { type: params.type }), + ...(params.query && { query: params.query }), + ...(params.parentId && { parentId: params.parentId }) + }); + const response = await this.axiosInstance.get(`/medias/files?${queryParams}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Upload file to media library + * POST /medias/upload-file + */ + async uploadMediaFile(uploadData) { + try { + const formData = new FormData(); + // Handle file upload (either direct file or hosted file URL) + if (uploadData.hosted && uploadData.fileUrl) { + formData.append('hosted', 'true'); + formData.append('fileUrl', uploadData.fileUrl); + } + else if (uploadData.file) { + formData.append('hosted', 'false'); + formData.append('file', uploadData.file); + } + else { + throw new Error('Either file or fileUrl (with hosted=true) must be provided'); + } + // Add optional fields + if (uploadData.name) { + formData.append('name', uploadData.name); + } + if (uploadData.parentId) { + formData.append('parentId', uploadData.parentId); + } + const response = await this.axiosInstance.post('/medias/upload-file', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + } + }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Delete file or folder from media library + * DELETE /medias/{id} + */ + async deleteMediaFile(deleteParams) { + try { + const queryParams = new URLSearchParams({ + altType: deleteParams.altType, + altId: deleteParams.altId + }); + const response = await this.axiosInstance.delete(`/medias/${deleteParams.id}?${queryParams}`); + return this.wrapResponse({ success: true, message: 'Media file deleted successfully' }); + } + catch (error) { + throw this.handleApiError(error); + } + } + // ===== CUSTOM OBJECTS API METHODS ===== + /** + * Get all objects for a location + * GET /objects/ + */ + async getObjectsByLocation(locationId) { + try { + const params = { + locationId: locationId || this.config.locationId + }; + const response = await this.axiosInstance.get('/objects/', { params }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Create custom object schema + * POST /objects/ + */ + async createObjectSchema(schemaData) { + try { + const payload = { + ...schemaData, + locationId: schemaData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.post('/objects/', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get object schema by key/id + * GET /objects/{key} + */ + async getObjectSchema(params) { + try { + const queryParams = { + locationId: params.locationId || this.config.locationId, + ...(params.fetchProperties !== undefined && { fetchProperties: params.fetchProperties.toString() }) + }; + const response = await this.axiosInstance.get(`/objects/${params.key}`, { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update object schema by key/id + * PUT /objects/{key} + */ + async updateObjectSchema(key, updateData) { + try { + const payload = { + ...updateData, + locationId: updateData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.put(`/objects/${key}`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Create object record + * POST /objects/{schemaKey}/records + */ + async createObjectRecord(schemaKey, recordData) { + try { + const payload = { + ...recordData, + locationId: recordData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.post(`/objects/${schemaKey}/records`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get object record by id + * GET /objects/{schemaKey}/records/{id} + */ + async getObjectRecord(schemaKey, recordId) { + try { + const response = await this.axiosInstance.get(`/objects/${schemaKey}/records/${recordId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update object record + * PUT /objects/{schemaKey}/records/{id} + */ + async updateObjectRecord(schemaKey, recordId, updateData) { + try { + const queryParams = { + locationId: updateData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.put(`/objects/${schemaKey}/records/${recordId}`, updateData, { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Delete object record + * DELETE /objects/{schemaKey}/records/{id} + */ + async deleteObjectRecord(schemaKey, recordId) { + try { + const response = await this.axiosInstance.delete(`/objects/${schemaKey}/records/${recordId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Search object records + * POST /objects/{schemaKey}/records/search + */ + async searchObjectRecords(schemaKey, searchData) { + try { + const payload = { + ...searchData, + locationId: searchData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.post(`/objects/${schemaKey}/records/search`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + // ===== ASSOCIATIONS API METHODS ===== + /** + * Get all associations for a location + * GET /associations/ + */ + async getAssociations(params) { + try { + const queryParams = { + locationId: params.locationId || this.config.locationId, + skip: params.skip.toString(), + limit: params.limit.toString() + }; + const response = await this.axiosInstance.get('/associations/', { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Create association + * POST /associations/ + */ + async createAssociation(associationData) { + try { + const payload = { + ...associationData, + locationId: associationData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.post('/associations/', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get association by ID + * GET /associations/{associationId} + */ + async getAssociationById(associationId) { + try { + const response = await this.axiosInstance.get(`/associations/${associationId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update association + * PUT /associations/{associationId} + */ + async updateAssociation(associationId, updateData) { + try { + const response = await this.axiosInstance.put(`/associations/${associationId}`, updateData); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Delete association + * DELETE /associations/{associationId} + */ + async deleteAssociation(associationId) { + try { + const response = await this.axiosInstance.delete(`/associations/${associationId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get association by key name + * GET /associations/key/{key_name} + */ + async getAssociationByKey(params) { + try { + const queryParams = { + locationId: params.locationId || this.config.locationId + }; + const response = await this.axiosInstance.get(`/associations/key/${params.keyName}`, { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get association by object key + * GET /associations/objectKey/{objectKey} + */ + async getAssociationByObjectKey(params) { + try { + const queryParams = params.locationId ? { + locationId: params.locationId + } : {}; + const response = await this.axiosInstance.get(`/associations/objectKey/${params.objectKey}`, { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Create relation between entities + * POST /associations/relations + */ + async createRelation(relationData) { + try { + const payload = { + ...relationData, + locationId: relationData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.post('/associations/relations', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get relations by record ID + * GET /associations/relations/{recordId} + */ + async getRelationsByRecord(params) { + try { + const queryParams = { + locationId: params.locationId || this.config.locationId, + skip: params.skip.toString(), + limit: params.limit.toString(), + ...(params.associationIds && { associationIds: params.associationIds }) + }; + const response = await this.axiosInstance.get(`/associations/relations/${params.recordId}`, { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Delete relation + * DELETE /associations/relations/{relationId} + */ + async deleteRelation(params) { + try { + const queryParams = { + locationId: params.locationId || this.config.locationId + }; + const response = await this.axiosInstance.delete(`/associations/relations/${params.relationId}`, { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + // ===== CUSTOM FIELDS V2 API METHODS ===== + /** + * Get custom field or folder by ID + * GET /custom-fields/{id} + */ + async getCustomFieldV2ById(id) { + try { + const response = await this.axiosInstance.get(`/custom-fields/${id}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Create custom field + * POST /custom-fields/ + */ + async createCustomFieldV2(fieldData) { + try { + const payload = { + ...fieldData, + locationId: fieldData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.post('/custom-fields/', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update custom field by ID + * PUT /custom-fields/{id} + */ + async updateCustomFieldV2(id, fieldData) { + try { + const payload = { + ...fieldData, + locationId: fieldData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.put(`/custom-fields/${id}`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Delete custom field by ID + * DELETE /custom-fields/{id} + */ + async deleteCustomFieldV2(id) { + try { + const response = await this.axiosInstance.delete(`/custom-fields/${id}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get custom fields by object key + * GET /custom-fields/object-key/{objectKey} + */ + async getCustomFieldsV2ByObjectKey(params) { + try { + const queryParams = { + locationId: params.locationId || this.config.locationId + }; + const response = await this.axiosInstance.get(`/custom-fields/object-key/${params.objectKey}`, { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Create custom field folder + * POST /custom-fields/folder + */ + async createCustomFieldV2Folder(folderData) { + try { + const payload = { + ...folderData, + locationId: folderData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.post('/custom-fields/folder', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Update custom field folder name + * PUT /custom-fields/folder/{id} + */ + async updateCustomFieldV2Folder(id, folderData) { + try { + const payload = { + ...folderData, + locationId: folderData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.put(`/custom-fields/folder/${id}`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Delete custom field folder + * DELETE /custom-fields/folder/{id} + */ + async deleteCustomFieldV2Folder(params) { + try { + const queryParams = { + locationId: params.locationId || this.config.locationId + }; + const response = await this.axiosInstance.delete(`/custom-fields/folder/${params.id}`, { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + // ===== WORKFLOWS API METHODS ===== + /** + * Get all workflows for a location + * GET /workflows/ + */ + async getWorkflows(request) { + try { + const queryParams = { + locationId: request.locationId || this.config.locationId + }; + const response = await this.axiosInstance.get('/workflows/', { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + // ===== SURVEYS API METHODS ===== + /** + * Get all surveys for a location + * GET /surveys/ + */ + async getSurveys(request) { + try { + const queryParams = { + locationId: request.locationId || this.config.locationId + }; + if (request.skip !== undefined) { + queryParams.skip = request.skip.toString(); + } + if (request.limit !== undefined) { + queryParams.limit = request.limit.toString(); + } + if (request.type) { + queryParams.type = request.type; + } + const response = await this.axiosInstance.get('/surveys/', { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw this.handleApiError(error); + } + } + /** + * Get survey submissions with filtering and pagination + * GET /surveys/submissions + */ + async getSurveySubmissions(request) { + try { + const locationId = request.locationId || this.config.locationId; + const params = new URLSearchParams(); + if (request.page) + params.append('page', request.page.toString()); + if (request.limit) + params.append('limit', request.limit.toString()); + if (request.surveyId) + params.append('surveyId', request.surveyId); + if (request.q) + params.append('q', request.q); + if (request.startAt) + params.append('startAt', request.startAt); + if (request.endAt) + params.append('endAt', request.endAt); + const response = await this.axiosInstance.get(`/locations/${locationId}/surveys/submissions?${params.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + // ===== STORE API METHODS ===== + /** + * SHIPPING ZONES API METHODS + */ + /** + * Create a new shipping zone + * POST /store/shipping-zone + */ + async createShippingZone(zoneData) { + try { + const payload = { + ...zoneData, + altId: zoneData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post('/store/shipping-zone', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * List all shipping zones + * GET /store/shipping-zone + */ + async listShippingZones(params) { + try { + const altId = params.altId || this.config.locationId; + const queryParams = new URLSearchParams({ + altId, + altType: 'location' + }); + if (params.limit) + queryParams.append('limit', params.limit.toString()); + if (params.offset) + queryParams.append('offset', params.offset.toString()); + if (params.withShippingRate !== undefined) + queryParams.append('withShippingRate', params.withShippingRate.toString()); + const response = await this.axiosInstance.get(`/store/shipping-zone?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get a specific shipping zone by ID + * GET /store/shipping-zone/{shippingZoneId} + */ + async getShippingZone(shippingZoneId, params) { + try { + const altId = params.altId || this.config.locationId; + const queryParams = new URLSearchParams({ + altId, + altType: 'location' + }); + if (params.withShippingRate !== undefined) + queryParams.append('withShippingRate', params.withShippingRate.toString()); + const response = await this.axiosInstance.get(`/store/shipping-zone/${shippingZoneId}?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update a shipping zone + * PUT /store/shipping-zone/{shippingZoneId} + */ + async updateShippingZone(shippingZoneId, updateData) { + try { + const payload = { + ...updateData, + altId: updateData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.put(`/store/shipping-zone/${shippingZoneId}`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Delete a shipping zone + * DELETE /store/shipping-zone/{shippingZoneId} + */ + async deleteShippingZone(shippingZoneId, params) { + try { + const altId = params.altId || this.config.locationId; + const queryParams = new URLSearchParams({ + altId, + altType: 'location' + }); + const response = await this.axiosInstance.delete(`/store/shipping-zone/${shippingZoneId}?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * SHIPPING RATES API METHODS + */ + /** + * Get available shipping rates for an order + * POST /store/shipping-zone/shipping-rates + */ + async getAvailableShippingRates(rateData) { + try { + const payload = { + ...rateData, + altId: rateData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post('/store/shipping-zone/shipping-rates', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Create a new shipping rate for a zone + * POST /store/shipping-zone/{shippingZoneId}/shipping-rate + */ + async createShippingRate(shippingZoneId, rateData) { + try { + const payload = { + ...rateData, + altId: rateData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post(`/store/shipping-zone/${shippingZoneId}/shipping-rate`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * List shipping rates for a zone + * GET /store/shipping-zone/{shippingZoneId}/shipping-rate + */ + async listShippingRates(shippingZoneId, params) { + try { + const altId = params.altId || this.config.locationId; + const queryParams = new URLSearchParams({ + altId, + altType: 'location' + }); + if (params.limit) + queryParams.append('limit', params.limit.toString()); + if (params.offset) + queryParams.append('offset', params.offset.toString()); + const response = await this.axiosInstance.get(`/store/shipping-zone/${shippingZoneId}/shipping-rate?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get a specific shipping rate + * GET /store/shipping-zone/{shippingZoneId}/shipping-rate/{shippingRateId} + */ + async getShippingRate(shippingZoneId, shippingRateId, params) { + try { + const altId = params.altId || this.config.locationId; + const queryParams = new URLSearchParams({ + altId, + altType: 'location' + }); + const response = await this.axiosInstance.get(`/store/shipping-zone/${shippingZoneId}/shipping-rate/${shippingRateId}?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update a shipping rate + * PUT /store/shipping-zone/{shippingZoneId}/shipping-rate/{shippingRateId} + */ + async updateShippingRate(shippingZoneId, shippingRateId, updateData) { + try { + const payload = { + ...updateData, + altId: updateData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.put(`/store/shipping-zone/${shippingZoneId}/shipping-rate/${shippingRateId}`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Delete a shipping rate + * DELETE /store/shipping-zone/{shippingZoneId}/shipping-rate/{shippingRateId} + */ + async deleteShippingRate(shippingZoneId, shippingRateId, params) { + try { + const altId = params.altId || this.config.locationId; + const queryParams = new URLSearchParams({ + altId, + altType: 'location' + }); + const response = await this.axiosInstance.delete(`/store/shipping-zone/${shippingZoneId}/shipping-rate/${shippingRateId}?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * SHIPPING CARRIERS API METHODS + */ + /** + * Create a new shipping carrier + * POST /store/shipping-carrier + */ + async createShippingCarrier(carrierData) { + try { + const payload = { + ...carrierData, + altId: carrierData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post('/store/shipping-carrier', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * List all shipping carriers + * GET /store/shipping-carrier + */ + async listShippingCarriers(params) { + try { + const altId = params.altId || this.config.locationId; + const queryParams = new URLSearchParams({ + altId, + altType: 'location' + }); + const response = await this.axiosInstance.get(`/store/shipping-carrier?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get a specific shipping carrier by ID + * GET /store/shipping-carrier/{shippingCarrierId} + */ + async getShippingCarrier(shippingCarrierId, params) { + try { + const altId = params.altId || this.config.locationId; + const queryParams = new URLSearchParams({ + altId, + altType: 'location' + }); + const response = await this.axiosInstance.get(`/store/shipping-carrier/${shippingCarrierId}?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update a shipping carrier + * PUT /store/shipping-carrier/{shippingCarrierId} + */ + async updateShippingCarrier(shippingCarrierId, updateData) { + try { + const payload = { + ...updateData, + altId: updateData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.put(`/store/shipping-carrier/${shippingCarrierId}`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Delete a shipping carrier + * DELETE /store/shipping-carrier/{shippingCarrierId} + */ + async deleteShippingCarrier(shippingCarrierId, params) { + try { + const altId = params.altId || this.config.locationId; + const queryParams = new URLSearchParams({ + altId, + altType: 'location' + }); + const response = await this.axiosInstance.delete(`/store/shipping-carrier/${shippingCarrierId}?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * STORE SETTINGS API METHODS + */ + /** + * Create or update store settings + * POST /store/store-setting + */ + async createStoreSetting(settingData) { + try { + const payload = { + ...settingData, + altId: settingData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post('/store/store-setting', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get store settings + * GET /store/store-setting + */ + async getStoreSetting(params) { + try { + const altId = params.altId || this.config.locationId; + const queryParams = new URLSearchParams({ + altId, + altType: 'location' + }); + const response = await this.axiosInstance.get(`/store/store-setting?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * PRODUCTS API METHODS + */ + /** + * Create a new product + * POST /products/ + */ + async createProduct(productData) { + try { + const response = await this.axiosInstance.post('/products/', productData); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update a product by ID + * PUT /products/{productId} + */ + async updateProduct(productId, updateData) { + try { + const response = await this.axiosInstance.put(`/products/${productId}`, updateData); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get a product by ID + * GET /products/{productId} + */ + async getProduct(productId, locationId) { + try { + const queryParams = new URLSearchParams({ + locationId: locationId || this.config.locationId + }); + const response = await this.axiosInstance.get(`/products/${productId}?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * List products + * GET /products/ + */ + async listProducts(params) { + try { + const queryParams = new URLSearchParams({ + locationId: params.locationId || this.config.locationId + }); + if (params.limit) + queryParams.append('limit', params.limit.toString()); + if (params.offset) + queryParams.append('offset', params.offset.toString()); + if (params.search) + queryParams.append('search', params.search); + if (params.collectionIds?.length) + queryParams.append('collectionIds', params.collectionIds.join(',')); + if (params.collectionSlug) + queryParams.append('collectionSlug', params.collectionSlug); + if (params.expand?.length) + params.expand.forEach(item => queryParams.append('expand', item)); + if (params.productIds?.length) + params.productIds.forEach(id => queryParams.append('productIds', id)); + if (params.storeId) + queryParams.append('storeId', params.storeId); + if (params.includedInStore !== undefined) + queryParams.append('includedInStore', params.includedInStore.toString()); + if (params.availableInStore !== undefined) + queryParams.append('availableInStore', params.availableInStore.toString()); + if (params.sortOrder) + queryParams.append('sortOrder', params.sortOrder); + const response = await this.axiosInstance.get(`/products/?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Delete a product by ID + * DELETE /products/{productId} + */ + async deleteProduct(productId, locationId) { + try { + const queryParams = new URLSearchParams({ + locationId: locationId || this.config.locationId + }); + const response = await this.axiosInstance.delete(`/products/${productId}?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Bulk update products + * POST /products/bulk-update + */ + async bulkUpdateProducts(updateData) { + try { + const payload = { + ...updateData, + altId: updateData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post('/products/bulk-update', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Create a price for a product + * POST /products/{productId}/price + */ + async createPrice(productId, priceData) { + try { + const payload = { + ...priceData, + locationId: priceData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.post(`/products/${productId}/price`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update a price by ID + * PUT /products/{productId}/price/{priceId} + */ + async updatePrice(productId, priceId, updateData) { + try { + const payload = { + ...updateData, + locationId: updateData.locationId || this.config.locationId + }; + const response = await this.axiosInstance.put(`/products/${productId}/price/${priceId}`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get a price by ID + * GET /products/{productId}/price/{priceId} + */ + async getPrice(productId, priceId, locationId) { + try { + const queryParams = new URLSearchParams({ + locationId: locationId || this.config.locationId + }); + const response = await this.axiosInstance.get(`/products/${productId}/price/${priceId}?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * List prices for a product + * GET /products/{productId}/price + */ + async listPrices(productId, params) { + try { + const queryParams = new URLSearchParams({ + locationId: params.locationId || this.config.locationId + }); + if (params.limit) + queryParams.append('limit', params.limit.toString()); + if (params.offset) + queryParams.append('offset', params.offset.toString()); + if (params.ids) + queryParams.append('ids', params.ids); + const response = await this.axiosInstance.get(`/products/${productId}/price?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Delete a price by ID + * DELETE /products/{productId}/price/{priceId} + */ + async deletePrice(productId, priceId, locationId) { + try { + const queryParams = new URLSearchParams({ + locationId: locationId || this.config.locationId + }); + const response = await this.axiosInstance.delete(`/products/${productId}/price/${priceId}?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * List inventory + * GET /products/inventory + */ + async listInventory(params) { + try { + const queryParams = new URLSearchParams({ + altId: params.altId || this.config.locationId, + altType: 'location' + }); + if (params.limit) + queryParams.append('limit', params.limit.toString()); + if (params.offset) + queryParams.append('offset', params.offset.toString()); + if (params.search) + queryParams.append('search', params.search); + const response = await this.axiosInstance.get(`/products/inventory?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update inventory + * POST /products/inventory + */ + async updateInventory(updateData) { + try { + const payload = { + ...updateData, + altId: updateData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post('/products/inventory', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get product store stats + * GET /products/store/{storeId}/stats + */ + async getProductStoreStats(storeId, params) { + try { + const queryParams = new URLSearchParams({ + altId: params.altId || this.config.locationId, + altType: 'location' + }); + if (params.search) + queryParams.append('search', params.search); + if (params.collectionIds) + queryParams.append('collectionIds', params.collectionIds); + const response = await this.axiosInstance.get(`/products/store/${storeId}/stats?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update product store status + * POST /products/store/{storeId} + */ + async updateProductStore(storeId, updateData) { + try { + const response = await this.axiosInstance.post(`/products/store/${storeId}`, updateData); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Create a product collection + * POST /products/collections + */ + async createProductCollection(collectionData) { + try { + const payload = { + ...collectionData, + altId: collectionData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post('/products/collections', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update a product collection + * PUT /products/collections/{collectionId} + */ + async updateProductCollection(collectionId, updateData) { + try { + const payload = { + ...updateData, + altId: updateData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.put(`/products/collections/${collectionId}`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get a product collection by ID + * GET /products/collections/{collectionId} + */ + async getProductCollection(collectionId) { + try { + const response = await this.axiosInstance.get(`/products/collections/${collectionId}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * List product collections + * GET /products/collections + */ + async listProductCollections(params) { + try { + const queryParams = new URLSearchParams({ + altId: params.altId || this.config.locationId, + altType: 'location' + }); + if (params.limit) + queryParams.append('limit', params.limit.toString()); + if (params.offset) + queryParams.append('offset', params.offset.toString()); + if (params.collectionIds) + queryParams.append('collectionIds', params.collectionIds); + if (params.name) + queryParams.append('name', params.name); + const response = await this.axiosInstance.get(`/products/collections?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Delete a product collection + * DELETE /products/collections/{collectionId} + */ + async deleteProductCollection(collectionId, params) { + try { + const queryParams = new URLSearchParams({ + altId: params.altId || this.config.locationId, + altType: 'location' + }); + const response = await this.axiosInstance.delete(`/products/collections/${collectionId}?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * List product reviews + * GET /products/reviews + */ + async listProductReviews(params) { + try { + const queryParams = new URLSearchParams({ + altId: params.altId || this.config.locationId, + altType: 'location' + }); + if (params.limit) + queryParams.append('limit', params.limit.toString()); + if (params.offset) + queryParams.append('offset', params.offset.toString()); + if (params.sortField) + queryParams.append('sortField', params.sortField); + if (params.sortOrder) + queryParams.append('sortOrder', params.sortOrder); + if (params.rating) + queryParams.append('rating', params.rating.toString()); + if (params.startDate) + queryParams.append('startDate', params.startDate); + if (params.endDate) + queryParams.append('endDate', params.endDate); + if (params.productId) + queryParams.append('productId', params.productId); + if (params.storeId) + queryParams.append('storeId', params.storeId); + const response = await this.axiosInstance.get(`/products/reviews?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get reviews count + * GET /products/reviews/count + */ + async getReviewsCount(params) { + try { + const queryParams = new URLSearchParams({ + altId: params.altId || this.config.locationId, + altType: 'location' + }); + if (params.rating) + queryParams.append('rating', params.rating.toString()); + if (params.startDate) + queryParams.append('startDate', params.startDate); + if (params.endDate) + queryParams.append('endDate', params.endDate); + if (params.productId) + queryParams.append('productId', params.productId); + if (params.storeId) + queryParams.append('storeId', params.storeId); + const response = await this.axiosInstance.get(`/products/reviews/count?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update a product review + * PUT /products/reviews/{reviewId} + */ + async updateProductReview(reviewId, updateData) { + try { + const payload = { + ...updateData, + altId: updateData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.put(`/products/reviews/${reviewId}`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Delete a product review + * DELETE /products/reviews/{reviewId} + */ + async deleteProductReview(reviewId, params) { + try { + const queryParams = new URLSearchParams({ + altId: params.altId || this.config.locationId, + altType: 'location', + productId: params.productId + }); + const response = await this.axiosInstance.delete(`/products/reviews/${reviewId}?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Bulk update product reviews + * POST /products/reviews/bulk-update + */ + async bulkUpdateProductReviews(updateData) { + try { + const payload = { + ...updateData, + altId: updateData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post('/products/reviews/bulk-update', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * PAYMENTS API METHODS + */ + /** + * Create white-label integration provider + * POST /payments/integrations/provider/whitelabel + */ + async createWhiteLabelIntegrationProvider(data) { + try { + const response = await this.axiosInstance.post('/payments/integrations/provider/whitelabel', data); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * List white-label integration providers + * GET /payments/integrations/provider/whitelabel + */ + async listWhiteLabelIntegrationProviders(params) { + try { + const queryParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + queryParams.append(key, value.toString()); + } + }); + const response = await this.axiosInstance.get(`/payments/integrations/provider/whitelabel?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * List orders + * GET /payments/orders + */ + async listOrders(params) { + try { + const queryParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + queryParams.append(key, value.toString()); + } + }); + const response = await this.axiosInstance.get(`/payments/orders?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get order by ID + * GET /payments/orders/{orderId} + */ + async getOrderById(orderId, params) { + try { + const queryParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null && key !== 'orderId') { + queryParams.append(key, value.toString()); + } + }); + const response = await this.axiosInstance.get(`/payments/orders/${orderId}?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Create order fulfillment + * POST /payments/orders/{orderId}/fulfillments + */ + async createOrderFulfillment(orderId, data) { + try { + const response = await this.axiosInstance.post(`/payments/orders/${orderId}/fulfillments`, data); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * List order fulfillments + * GET /payments/orders/{orderId}/fulfillments + */ + async listOrderFulfillments(orderId, params) { + try { + const queryParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null && key !== 'orderId') { + queryParams.append(key, value.toString()); + } + }); + const response = await this.axiosInstance.get(`/payments/orders/${orderId}/fulfillments?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * List transactions + * GET /payments/transactions + */ + async listTransactions(params) { + try { + const queryParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + queryParams.append(key, value.toString()); + } + }); + const response = await this.axiosInstance.get(`/payments/transactions?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get transaction by ID + * GET /payments/transactions/{transactionId} + */ + async getTransactionById(transactionId, params) { + try { + const queryParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null && key !== 'transactionId') { + queryParams.append(key, value.toString()); + } + }); + const response = await this.axiosInstance.get(`/payments/transactions/${transactionId}?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * List subscriptions + * GET /payments/subscriptions + */ + async listSubscriptions(params) { + try { + const queryParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + queryParams.append(key, value.toString()); + } + }); + const response = await this.axiosInstance.get(`/payments/subscriptions?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get subscription by ID + * GET /payments/subscriptions/{subscriptionId} + */ + async getSubscriptionById(subscriptionId, params) { + try { + const queryParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null && key !== 'subscriptionId') { + queryParams.append(key, value.toString()); + } + }); + const response = await this.axiosInstance.get(`/payments/subscriptions/${subscriptionId}?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * List coupons + * GET /payments/coupon/list + */ + async listCoupons(params) { + try { + const queryParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + queryParams.append(key, value.toString()); + } + }); + const response = await this.axiosInstance.get(`/payments/coupon/list?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Create coupon + * POST /payments/coupon + */ + async createCoupon(data) { + try { + const response = await this.axiosInstance.post('/payments/coupon', data); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update coupon + * PUT /payments/coupon + */ + async updateCoupon(data) { + try { + const response = await this.axiosInstance.put('/payments/coupon', data); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Delete coupon + * DELETE /payments/coupon + */ + async deleteCoupon(data) { + try { + const response = await this.axiosInstance.delete('/payments/coupon', { data }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get coupon + * GET /payments/coupon + */ + async getCoupon(params) { + try { + const queryParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + queryParams.append(key, value.toString()); + } + }); + const response = await this.axiosInstance.get(`/payments/coupon?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Create custom provider integration + * POST /payments/custom-provider/provider + */ + async createCustomProviderIntegration(locationId, data) { + try { + const queryParams = new URLSearchParams({ locationId }); + const response = await this.axiosInstance.post(`/payments/custom-provider/provider?${queryParams.toString()}`, data); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Delete custom provider integration + * DELETE /payments/custom-provider/provider + */ + async deleteCustomProviderIntegration(locationId) { + try { + const queryParams = new URLSearchParams({ locationId }); + const response = await this.axiosInstance.delete(`/payments/custom-provider/provider?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get custom provider config + * GET /payments/custom-provider/connect + */ + async getCustomProviderConfig(locationId) { + try { + const queryParams = new URLSearchParams({ locationId }); + const response = await this.axiosInstance.get(`/payments/custom-provider/connect?${queryParams.toString()}`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Create custom provider config + * POST /payments/custom-provider/connect + */ + async createCustomProviderConfig(locationId, data) { + try { + const queryParams = new URLSearchParams({ locationId }); + const response = await this.axiosInstance.post(`/payments/custom-provider/connect?${queryParams.toString()}`, data); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Disconnect custom provider config + * POST /payments/custom-provider/disconnect + */ + async disconnectCustomProviderConfig(locationId, data) { + try { + const queryParams = new URLSearchParams({ locationId }); + const response = await this.axiosInstance.post(`/payments/custom-provider/disconnect?${queryParams.toString()}`, data); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + // ============================================================================= + // INVOICES API METHODS + // ============================================================================= + /** + * Create invoice template + * POST /invoices/template + */ + async createInvoiceTemplate(templateData) { + try { + const payload = { + ...templateData, + altId: templateData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post('/invoices/template', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * List invoice templates + * GET /invoices/template + */ + async listInvoiceTemplates(params) { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location', + limit: params?.limit || '10', + offset: params?.offset || '0', + ...(params?.status && { status: params.status }), + ...(params?.startAt && { startAt: params.startAt }), + ...(params?.endAt && { endAt: params.endAt }), + ...(params?.search && { search: params.search }), + ...(params?.paymentMode && { paymentMode: params.paymentMode }) + }; + const response = await this.axiosInstance.get('/invoices/template', { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get invoice template by ID + * GET /invoices/template/{templateId} + */ + async getInvoiceTemplate(templateId, params) { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.get(`/invoices/template/${templateId}`, { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update invoice template + * PUT /invoices/template/{templateId} + */ + async updateInvoiceTemplate(templateId, templateData) { + try { + const payload = { + ...templateData, + altId: templateData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.put(`/invoices/template/${templateId}`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Delete invoice template + * DELETE /invoices/template/{templateId} + */ + async deleteInvoiceTemplate(templateId, params) { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.delete(`/invoices/template/${templateId}`, { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update invoice template late fees configuration + * PATCH /invoices/template/{templateId}/late-fees-configuration + */ + async updateInvoiceTemplateLateFeesConfiguration(templateId, configData) { + try { + const payload = { + ...configData, + altId: configData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.patch(`/invoices/template/${templateId}/late-fees-configuration`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update invoice template payment methods configuration + * PATCH /invoices/template/{templateId}/payment-methods-configuration + */ + async updateInvoiceTemplatePaymentMethodsConfiguration(templateId, configData) { + try { + const payload = { + ...configData, + altId: configData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.patch(`/invoices/template/${templateId}/payment-methods-configuration`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Create invoice schedule + * POST /invoices/schedule + */ + async createInvoiceSchedule(scheduleData) { + try { + const payload = { + ...scheduleData, + altId: scheduleData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post('/invoices/schedule', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * List invoice schedules + * GET /invoices/schedule + */ + async listInvoiceSchedules(params) { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location', + limit: params?.limit || '10', + offset: params?.offset || '0', + ...(params?.status && { status: params.status }), + ...(params?.startAt && { startAt: params.startAt }), + ...(params?.endAt && { endAt: params.endAt }), + ...(params?.search && { search: params.search }), + ...(params?.paymentMode && { paymentMode: params.paymentMode }) + }; + const response = await this.axiosInstance.get('/invoices/schedule', { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get invoice schedule by ID + * GET /invoices/schedule/{scheduleId} + */ + async getInvoiceSchedule(scheduleId, params) { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.get(`/invoices/schedule/${scheduleId}`, { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update invoice schedule + * PUT /invoices/schedule/{scheduleId} + */ + async updateInvoiceSchedule(scheduleId, scheduleData) { + try { + const payload = { + ...scheduleData, + altId: scheduleData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.put(`/invoices/schedule/${scheduleId}`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Delete invoice schedule + * DELETE /invoices/schedule/{scheduleId} + */ + async deleteInvoiceSchedule(scheduleId, params) { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.delete(`/invoices/schedule/${scheduleId}`, { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update and schedule recurring invoice + * POST /invoices/schedule/{scheduleId}/updateAndSchedule + */ + async updateAndScheduleInvoiceSchedule(scheduleId) { + try { + const response = await this.axiosInstance.post(`/invoices/schedule/${scheduleId}/updateAndSchedule`); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Schedule an invoice schedule + * POST /invoices/schedule/{scheduleId}/schedule + */ + async scheduleInvoiceSchedule(scheduleId, scheduleData) { + try { + const payload = { + ...scheduleData, + altId: scheduleData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post(`/invoices/schedule/${scheduleId}/schedule`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Manage auto payment for schedule invoice + * POST /invoices/schedule/{scheduleId}/auto-payment + */ + async autoPaymentInvoiceSchedule(scheduleId, paymentData) { + try { + const payload = { + ...paymentData, + altId: paymentData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post(`/invoices/schedule/${scheduleId}/auto-payment`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Cancel scheduled invoice + * POST /invoices/schedule/{scheduleId}/cancel + */ + async cancelInvoiceSchedule(scheduleId, cancelData) { + try { + const payload = { + ...cancelData, + altId: cancelData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post(`/invoices/schedule/${scheduleId}/cancel`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Create or update text2pay invoice + * POST /invoices/text2pay + */ + async text2PayInvoice(invoiceData) { + try { + const payload = { + ...invoiceData, + altId: invoiceData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post('/invoices/text2pay', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Generate invoice number + * GET /invoices/generate-invoice-number + */ + async generateInvoiceNumber(params) { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.get('/invoices/generate-invoice-number', { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Get invoice by ID + * GET /invoices/{invoiceId} + */ + async getInvoice(invoiceId, params) { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.get(`/invoices/${invoiceId}`, { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update invoice + * PUT /invoices/{invoiceId} + */ + async updateInvoice(invoiceId, invoiceData) { + try { + const payload = { + ...invoiceData, + altId: invoiceData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.put(`/invoices/${invoiceId}`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Delete invoice + * DELETE /invoices/{invoiceId} + */ + async deleteInvoice(invoiceId, params) { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.delete(`/invoices/${invoiceId}`, { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update invoice late fees configuration + * PATCH /invoices/{invoiceId}/late-fees-configuration + */ + async updateInvoiceLateFeesConfiguration(invoiceId, configData) { + try { + const payload = { + ...configData, + altId: configData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.patch(`/invoices/${invoiceId}/late-fees-configuration`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Void invoice + * POST /invoices/{invoiceId}/void + */ + async voidInvoice(invoiceId, voidData) { + try { + const payload = { + ...voidData, + altId: voidData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post(`/invoices/${invoiceId}/void`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Send invoice + * POST /invoices/{invoiceId}/send + */ + async sendInvoice(invoiceId, sendData) { + try { + const payload = { + ...sendData, + altId: sendData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post(`/invoices/${invoiceId}/send`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Record manual payment for invoice + * POST /invoices/{invoiceId}/record-payment + */ + async recordInvoicePayment(invoiceId, paymentData) { + try { + const payload = { + ...paymentData, + altId: paymentData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post(`/invoices/${invoiceId}/record-payment`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update invoice last visited at + * PATCH /invoices/stats/last-visited-at + */ + async updateInvoiceLastVisitedAt(statsData) { + try { + const response = await this.axiosInstance.patch('/invoices/stats/last-visited-at', statsData); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Create new estimate + * POST /invoices/estimate + */ + async createEstimate(estimateData) { + try { + const payload = { + ...estimateData, + altId: estimateData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post('/invoices/estimate', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update estimate + * PUT /invoices/estimate/{estimateId} + */ + async updateEstimate(estimateId, estimateData) { + try { + const payload = { + ...estimateData, + altId: estimateData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.put(`/invoices/estimate/${estimateId}`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Delete estimate + * DELETE /invoices/estimate/{estimateId} + */ + async deleteEstimate(estimateId, deleteData) { + try { + const payload = { + ...deleteData, + altId: deleteData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.delete(`/invoices/estimate/${estimateId}`, { data: payload }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Generate estimate number + * GET /invoices/estimate/number/generate + */ + async generateEstimateNumber(params) { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.get('/invoices/estimate/number/generate', { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Send estimate + * POST /invoices/estimate/{estimateId}/send + */ + async sendEstimate(estimateId, sendData) { + try { + const payload = { + ...sendData, + altId: sendData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post(`/invoices/estimate/${estimateId}/send`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Create invoice from estimate + * POST /invoices/estimate/{estimateId}/invoice + */ + async createInvoiceFromEstimate(estimateId, invoiceData) { + try { + const payload = { + ...invoiceData, + altId: invoiceData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post(`/invoices/estimate/${estimateId}/invoice`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * List estimates + * GET /invoices/estimate/list + */ + async listEstimates(params) { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location', + limit: params?.limit || '10', + offset: params?.offset || '0', + ...(params?.startAt && { startAt: params.startAt }), + ...(params?.endAt && { endAt: params.endAt }), + ...(params?.search && { search: params.search }), + ...(params?.status && { status: params.status }), + ...(params?.contactId && { contactId: params.contactId }) + }; + const response = await this.axiosInstance.get('/invoices/estimate/list', { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update estimate last visited at + * PATCH /invoices/estimate/stats/last-visited-at + */ + async updateEstimateLastVisitedAt(statsData) { + try { + const response = await this.axiosInstance.patch('/invoices/estimate/stats/last-visited-at', statsData); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * List estimate templates + * GET /invoices/estimate/template + */ + async listEstimateTemplates(params) { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location', + limit: params?.limit || '10', + offset: params?.offset || '0', + ...(params?.search && { search: params.search }) + }; + const response = await this.axiosInstance.get('/invoices/estimate/template', { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Create estimate template + * POST /invoices/estimate/template + */ + async createEstimateTemplate(templateData) { + try { + const payload = { + ...templateData, + altId: templateData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post('/invoices/estimate/template', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Update estimate template + * PUT /invoices/estimate/template/{templateId} + */ + async updateEstimateTemplate(templateId, templateData) { + try { + const payload = { + ...templateData, + altId: templateData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.put(`/invoices/estimate/template/${templateId}`, payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Delete estimate template + * DELETE /invoices/estimate/template/{templateId} + */ + async deleteEstimateTemplate(templateId, deleteData) { + try { + const payload = { + ...deleteData, + altId: deleteData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.delete(`/invoices/estimate/template/${templateId}`, { data: payload }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Preview estimate template + * GET /invoices/estimate/template/preview + */ + async previewEstimateTemplate(params) { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location', + templateId: params?.templateId || '' + }; + const response = await this.axiosInstance.get('/invoices/estimate/template/preview', { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * Create invoice + * POST /invoices/ + */ + async createInvoice(invoiceData) { + try { + const payload = { + ...invoiceData, + altId: invoiceData.altId || this.config.locationId, + altType: 'location' + }; + const response = await this.axiosInstance.post('/invoices/', payload); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } + /** + * List invoices + * GET /invoices/ + */ + async listInvoices(params) { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location', + limit: params?.limit || '10', + offset: params?.offset || '0', + ...(params?.status && { status: params.status }), + ...(params?.startAt && { startAt: params.startAt }), + ...(params?.endAt && { endAt: params.endAt }), + ...(params?.search && { search: params.search }), + ...(params?.paymentMode && { paymentMode: params.paymentMode }), + ...(params?.contactId && { contactId: params.contactId }), + ...(params?.sortField && { sortField: params.sortField }), + ...(params?.sortOrder && { sortOrder: params.sortOrder }) + }; + const response = await this.axiosInstance.get('/invoices/', { params: queryParams }); + return this.wrapResponse(response.data); + } + catch (error) { + throw error; + } + } +} diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/server.js b/mcp-diagrams/ghl-mcp-apps-only/dist/server.js new file mode 100644 index 0000000..87611bd --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/dist/server.js @@ -0,0 +1,139 @@ +/** + * 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); +}); diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/types/ghl-types.js b/mcp-diagrams/ghl-mcp-apps-only/dist/types/ghl-types.js new file mode 100644 index 0000000..8fdf1f5 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/dist/types/ghl-types.js @@ -0,0 +1,5 @@ +/** + * TypeScript interfaces for GoHighLevel API integration + * Based on official OpenAPI specifications v2021-07-28 (Contacts) and v2021-04-15 (Conversations) + */ +export {}; diff --git a/mcp-diagrams/ghl-mcp-apps-only/package.json b/mcp-diagrams/ghl-mcp-apps-only/package.json new file mode 100644 index 0000000..67223fe --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/package.json @@ -0,0 +1,22 @@ +{ + "name": "ghl-mcp-apps-only", + "version": "1.0.0", + "description": "GoHighLevel MCP Apps Only - Slimmed down for testing", + "main": "dist/server.js", + "type": "module", + "scripts": { + "build": "tsc", + "start": "node dist/server.js", + "dev": "tsx src/server.ts" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.1", + "axios": "^1.9.0", + "dotenv": "^16.5.0" + }, + "devDependencies": { + "@types/node": "^22.15.29", + "tsx": "^4.7.0", + "typescript": "^5.8.3" + } +} diff --git a/mcp-diagrams/ghl-mcp-apps-only/src/apps/index.ts b/mcp-diagrams/ghl-mcp-apps-only/src/apps/index.ts new file mode 100644 index 0000000..7184538 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/src/apps/index.ts @@ -0,0 +1,824 @@ +/** + * MCP Apps Manager + * Manages rich UI components for GoHighLevel MCP Server + */ + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import { GHLApiClient } from '../clients/ghl-api-client.js'; +import * as fs from 'fs'; +import * as path from 'path'; +import { fileURLToPath } from 'url'; + +export interface AppToolResult { + content: Array<{ type: 'text'; text: string }>; + structuredContent?: Record; + [key: string]: unknown; +} + +export interface AppResourceHandler { + uri: string; + mimeType: string; + getContent: () => string; +} + +/** + * 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(): string { + // 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 { + private ghlClient: GHLApiClient; + private resourceHandlers: Map = new Map(); + private uiBuildPath: string; + + constructor(ghlClient: GHLApiClient) { + this.ghlClient = ghlClient; + this.uiBuildPath = getUIBuildPath(); + process.stderr.write(`[MCP Apps] UI build path: ${this.uiBuildPath}\n`); + this.registerResourceHandlers(); + } + + /** + * Register all UI resource handlers + */ + private registerResourceHandlers(): void { + const resources: Array<{ uri: string; file: string }> = [ + // 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 + */ + private loadUIResource(filename: string): string { + 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 + */ + private getFallbackHTML(filename: string): string { + const componentName = filename.replace('.html', ''); + return ` + + + + + + GHL ${componentName} + + + +
+

UI component "${componentName}" is loading...

+

Run npm run build:ui to build UI components.

+
+ + + + `.trim(); + } + + /** + * Get tool definitions for all app tools + */ + getToolDefinitions(): Tool[] { + 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(): string[] { + 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: string): boolean { + return this.getAppToolNames().includes(toolName); + } + + /** + * Execute an app tool + */ + async executeTool(toolName: string, args: Record): Promise { + 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 as { + opportunityId: string; + pipelineStageId?: string; + name?: string; + monetaryValue?: number; + status?: 'open' | 'won' | 'lost' | 'abandoned'; + }); + default: + throw new Error(`Unknown app tool: ${toolName}`); + } + } + + /** + * View contact grid (search results) + */ + private async viewContactGrid(query?: string, limit?: number): Promise { + 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) + */ + private async viewPipelineBoard(pipelineId: string): Promise { + 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: any) => 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: any) => ({ + 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 + */ + private async viewQuickBook(calendarId: string, contactId?: string): Promise { + 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 as any)?.name || calendarId}`, + resourceHandler.uri, + resourceHandler.mimeType, + resourceHandler.getContent(), + data + ); + } + + /** + * View opportunity card + */ + private async viewOpportunityCard(opportunityId: string): Promise { + 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 as any)?.name || opportunityId}`, + resourceHandler.uri, + resourceHandler.mimeType, + resourceHandler.getContent(), + opportunity + ); + } + + /** + * View calendar + */ + private async viewCalendar(calendarId: string, startDate?: string, endDate?: string): Promise { + 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 as any; + 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 + */ + private async viewCampaignStats(campaignId: string): Promise { + // Get email campaigns + const response = await this.ghlClient.getEmailCampaigns({}); + + const campaigns = response.data?.schedules || []; + const campaign = campaigns.find((c: any) => 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 as any)?.name || campaignId}`, + resourceHandler.uri, + resourceHandler.mimeType, + resourceHandler.getContent(), + data + ); + } + + /** + * View agent stats + */ + private async viewAgentStats(userId?: string, dateRange?: string): Promise { + // 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 + */ + private async viewContactTimeline(contactId: string): Promise { + 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 as any; + 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 + */ + private async viewWorkflowStatus(workflowId: string): Promise { + const response = await this.ghlClient.getWorkflows({ + locationId: this.ghlClient.getConfig().locationId + }); + + const workflows = response.data?.workflows || []; + const workflow = workflows.find((w: any) => 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 as any)?.name || workflowId}`, + resourceHandler.uri, + resourceHandler.mimeType, + resourceHandler.getContent(), + data + ); + } + + /** + * View main dashboard + */ + private async viewDashboard(): Promise { + 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 + */ + private async viewInvoice(invoiceId: string): Promise { + 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) + */ + private async updateOpportunity(args: { + opportunityId: string; + pipelineStageId?: string; + name?: string; + monetaryValue?: number; + status?: 'open' | 'won' | 'lost' | 'abandoned'; + }): Promise { + const { opportunityId, ...updates } = args; + + // Build the update payload + const updatePayload: any = {}; + 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 + */ + private createAppResult( + textSummary: string, + resourceUri: string, + mimeType: string, + htmlContent: string, + data: any + ): AppToolResult { + // 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 + */ + private injectDataIntoHTML(html: string, data: any): string { + const dataScript = ``; + + // Insert before or at the beginning of + if (html.includes('')) { + return html.replace('', `${dataScript}`); + } else if (html.includes('')) { + return html.replace('', `${dataScript}`); + } else { + return dataScript + html; + } + } + + /** + * Get resource handler by URI + */ + getResourceHandler(uri: string): AppResourceHandler | undefined { + return this.resourceHandlers.get(uri); + } + + /** + * Get all registered resource URIs + */ + getResourceURIs(): string[] { + return Array.from(this.resourceHandlers.keys()); + } +} diff --git a/mcp-diagrams/ghl-mcp-apps-only/src/clients/ghl-api-client.ts b/mcp-diagrams/ghl-mcp-apps-only/src/clients/ghl-api-client.ts new file mode 100644 index 0000000..d5a307c --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/src/clients/ghl-api-client.ts @@ -0,0 +1,6858 @@ +/** + * GoHighLevel API Client + * Implements exact API endpoints from OpenAPI specifications v2021-07-28 (Contacts) and v2021-04-15 (Conversations) + */ + +import axios, { AxiosInstance, AxiosResponse, AxiosError } from 'axios'; +import { + GHLConfig, + GHLContact, + GHLCreateContactRequest, + GHLSearchContactsRequest, + GHLSearchContactsResponse, + GHLContactTagsRequest, + GHLContactTagsResponse, + GHLApiResponse, + GHLErrorResponse, + GHLTask, + GHLNote, + // Conversation types + GHLConversation, + GHLMessage, + GHLSendMessageRequest, + GHLSendMessageResponse, + GHLSearchConversationsRequest, + GHLSearchConversationsResponse, + GHLGetMessagesResponse, + GHLCreateConversationRequest, + GHLCreateConversationResponse, + GHLUpdateConversationRequest, + // Blog types + GHLBlogPost, + GHLCreateBlogPostRequest, + GHLUpdateBlogPostRequest, + GHLBlogPostCreateResponse, + GHLBlogPostUpdateResponse, + GHLBlogPostListResponse, + GHLBlogAuthor, + GHLBlogAuthorsResponse, + GHLBlogCategory, + GHLBlogCategoriesResponse, + GHLBlogSite, + GHLBlogSitesResponse, + GHLUrlSlugCheckResponse, + GHLGetBlogPostsRequest, + GHLGetBlogAuthorsRequest, + GHLGetBlogCategoriesRequest, + GHLGetBlogSitesRequest, + GHLCheckUrlSlugRequest, + GHLSearchOpportunitiesRequest, + GHLSearchOpportunitiesResponse, + GHLGetPipelinesResponse, + GHLOpportunity, + GHLCreateOpportunityRequest, + GHLUpdateOpportunityRequest, + GHLOpportunityStatus, + GHLUpdateOpportunityStatusRequest, + GHLUpsertOpportunityRequest, + GHLUpsertOpportunityResponse, + GHLGetCalendarGroupsResponse, + GHLCreateCalendarGroupRequest, + GHLCalendarGroup, + GHLGetCalendarsResponse, + GHLCreateCalendarRequest, + GHLCalendar, + GHLUpdateCalendarRequest, + GHLGetCalendarEventsRequest, + GHLGetCalendarEventsResponse, + GHLGetFreeSlotsRequest, + GHLGetFreeSlotsResponse, + GHLCreateAppointmentRequest, + GHLCalendarEvent, + GHLUpdateAppointmentRequest, + GHLCreateBlockSlotRequest, + GHLBlockSlotResponse, + GHLUpdateBlockSlotRequest, + GHLEmailCampaignsResponse, + MCPGetEmailCampaignsParams, + MCPCreateEmailTemplateParams, + MCPGetEmailTemplatesParams, + MCPUpdateEmailTemplateParams, + MCPDeleteEmailTemplateParams, + GHLEmailTemplate, + // Location types + GHLLocationSearchResponse, + GHLLocationDetailsResponse, + GHLLocationDetailed, + GHLCreateLocationRequest, + GHLUpdateLocationRequest, + GHLLocationDeleteResponse, + GHLLocationTagsResponse, + GHLLocationTagResponse, + GHLLocationTagRequest, + GHLLocationTagDeleteResponse, + GHLLocationTaskSearchRequest, + GHLLocationTaskSearchResponse, + GHLLocationCustomFieldsResponse, + GHLLocationCustomFieldResponse, + GHLCreateCustomFieldRequest, + GHLUpdateCustomFieldRequest, + GHLCustomFieldDeleteResponse, + GHLFileUploadRequest, + GHLFileUploadResponse, + GHLLocationCustomValuesResponse, + GHLLocationCustomValueResponse, + GHLCustomValueRequest, + GHLCustomValueDeleteResponse, + GHLLocationTemplatesResponse, + // Email ISV types + GHLEmailVerificationRequest, + GHLEmailVerificationResponse, + // Additional Contact types + GHLAppointment, + GHLUpsertContactResponse, + GHLBulkTagsResponse, + GHLBulkBusinessResponse, + GHLFollowersResponse, + GHLCampaign, + GHLWorkflow, + // Additional Conversation/Message types + GHLEmailMessage, + GHLProcessInboundMessageRequest, + GHLProcessOutboundMessageRequest, + GHLProcessMessageResponse, + GHLCancelScheduledResponse, + GHLMessageRecordingResponse, + GHLMessageTranscription, + GHLMessageTranscriptionResponse, + GHLLiveChatTypingRequest, + GHLLiveChatTypingResponse, + GHLUploadFilesRequest, + GHLUploadFilesResponse, + GHLUpdateMessageStatusRequest, + // Social Media Posting API types + GHLSocialPlatform, + GHLSearchPostsRequest, + GHLSearchPostsResponse, + GHLCreatePostRequest, + GHLCreatePostResponse, + GHLUpdatePostRequest, + GHLGetPostResponse, + GHLBulkDeletePostsRequest, + GHLBulkDeleteResponse, + GHLGetAccountsResponse, + GHLUploadCSVRequest, + GHLUploadCSVResponse, + GHLGetUploadStatusResponse, + GHLSetAccountsRequest, + GHLCSVFinalizeRequest, + GHLGetCategoriesResponse, + GHLGetCategoryResponse, + GHLGetTagsResponse, + GHLGetTagsByIdsRequest, + GHLGetTagsByIdsResponse, + GHLOAuthStartResponse, + GHLGetGoogleLocationsResponse, + GHLAttachGMBLocationRequest, + GHLGetFacebookPagesResponse, + GHLAttachFBAccountRequest, + GHLGetInstagramAccountsResponse, + GHLAttachIGAccountRequest, + GHLGetLinkedInAccountsResponse, + GHLAttachLinkedInAccountRequest, + GHLGetTwitterAccountsResponse, + GHLAttachTwitterAccountRequest, + GHLGetTikTokAccountsResponse, + GHLAttachTikTokAccountRequest, + GHLCSVImport, + GHLSocialPost, + GHLSocialAccount, + GHLValidateGroupSlugResponse, + GHLGroupSuccessResponse, + GHLGroupStatusUpdateRequest, + GHLUpdateCalendarGroupRequest, + GHLGetAppointmentNotesResponse, + GHLCreateAppointmentNoteRequest, + GHLAppointmentNoteResponse, + GHLUpdateAppointmentNoteRequest, + GHLDeleteAppointmentNoteResponse, + GHLCalendarResource, + GHLCreateCalendarResourceRequest, + GHLCalendarResourceResponse, + GHLCalendarResourceByIdResponse, + GHLUpdateCalendarResourceRequest, + GHLResourceDeleteResponse, + GHLCalendarNotification, + GHLCreateCalendarNotificationRequest, + GHLUpdateCalendarNotificationRequest, + GHLCalendarNotificationDeleteResponse, + GHLGetCalendarNotificationsRequest, + GHLGetBlockedSlotsRequest, + GHLGetMediaFilesRequest, + GHLGetMediaFilesResponse, + GHLUploadMediaFileRequest, + GHLUploadMediaFileResponse, + GHLDeleteMediaRequest, + GHLDeleteMediaResponse, + // Custom Objects API types + GHLGetObjectSchemaRequest, + GHLGetObjectSchemaResponse, + GHLObjectListResponse, + GHLCreateObjectSchemaRequest, + GHLObjectSchemaResponse, + GHLUpdateObjectSchemaRequest, + GHLCreateObjectRecordRequest, + GHLObjectRecordResponse, + GHLDetailedObjectRecordResponse, + GHLUpdateObjectRecordRequest, + GHLObjectRecordDeleteResponse, + GHLSearchObjectRecordsRequest, + GHLSearchObjectRecordsResponse, + // Associations API types + GHLAssociation, + GHLRelation, + GHLCreateAssociationRequest, + GHLUpdateAssociationRequest, + GHLCreateRelationRequest, + GHLGetAssociationsRequest, + GHLGetRelationsByRecordRequest, + GHLGetAssociationByKeyRequest, + GHLGetAssociationByObjectKeyRequest, + GHLDeleteRelationRequest, + GHLAssociationResponse, + GHLDeleteAssociationResponse, + GHLGetAssociationsResponse, + GHLGetRelationsResponse, + // Custom Fields V2 API types + GHLV2CustomField, + GHLV2CustomFieldFolder, + GHLV2CreateCustomFieldRequest, + GHLV2UpdateCustomFieldRequest, + GHLV2CreateCustomFieldFolderRequest, + GHLV2UpdateCustomFieldFolderRequest, + GHLV2GetCustomFieldsByObjectKeyRequest, + GHLV2DeleteCustomFieldFolderRequest, + GHLV2CustomFieldResponse, + GHLV2CustomFieldsResponse, + GHLV2CustomFieldFolderResponse, + GHLV2DeleteCustomFieldResponse, + // Workflows API types + GHLGetWorkflowsRequest, + GHLGetWorkflowsResponse, + // Surveys API types + GHLGetSurveysRequest, + GHLGetSurveysResponse, + GHLGetSurveySubmissionsRequest, + GHLGetSurveySubmissionsResponse, + // Store API types + GHLCreateShippingZoneRequest, + GHLCreateShippingZoneResponse, + GHLListShippingZonesResponse, + GHLGetShippingZonesRequest, + GHLGetShippingZoneResponse, + GHLUpdateShippingZoneRequest, + GHLUpdateShippingZoneResponse, + GHLDeleteShippingZoneRequest, + GHLDeleteShippingZoneResponse, + GHLGetAvailableShippingRatesRequest, + GHLGetAvailableShippingRatesResponse, + GHLCreateShippingRateRequest, + GHLCreateShippingRateResponse, + GHLListShippingRatesResponse, + GHLGetShippingRatesRequest, + GHLGetShippingRateResponse, + GHLUpdateShippingRateRequest, + GHLUpdateShippingRateResponse, + GHLDeleteShippingRateRequest, + GHLDeleteShippingRateResponse, + GHLCreateShippingCarrierRequest, + GHLCreateShippingCarrierResponse, + GHLListShippingCarriersResponse, + GHLGetShippingCarriersRequest, + GHLGetShippingCarrierResponse, + GHLUpdateShippingCarrierRequest, + GHLUpdateShippingCarrierResponse, + GHLDeleteShippingCarrierRequest, + GHLDeleteShippingCarrierResponse, + GHLCreateStoreSettingRequest, + GHLCreateStoreSettingResponse, + GHLGetStoreSettingRequest, + GHLGetStoreSettingResponse, + GHLCreateProductRequest, + GHLCreateProductResponse, + GHLUpdateProductRequest, + GHLUpdateProductResponse, + GHLGetProductRequest, + GHLGetProductResponse, + GHLListProductsRequest, + GHLListProductsResponse, + GHLDeleteProductRequest, + GHLDeleteProductResponse, + GHLBulkUpdateRequest, + GHLBulkUpdateResponse, + GHLCreatePriceRequest, + GHLCreatePriceResponse, + GHLUpdatePriceRequest, + GHLUpdatePriceResponse, + GHLGetPriceRequest, + GHLGetPriceResponse, + GHLListPricesRequest, + GHLListPricesResponse, + GHLDeletePriceRequest, + GHLDeletePriceResponse, + GHLListInventoryRequest, + GHLListInventoryResponse, + GHLUpdateInventoryRequest, + GHLUpdateInventoryResponse, + GHLGetProductStoreStatsRequest, + GHLGetProductStoreStatsResponse, + GHLUpdateProductStoreRequest, + GHLUpdateProductStoreResponse, + GHLCreateProductCollectionRequest, + GHLCreateCollectionResponse, + GHLUpdateProductCollectionRequest, + GHLUpdateProductCollectionResponse, + GHLGetProductCollectionRequest, + GHLDefaultCollectionResponse, + GHLListProductCollectionsRequest, + GHLListCollectionResponse, + GHLDeleteProductCollectionRequest, + GHLDeleteProductCollectionResponse, + GHLListProductReviewsRequest, + GHLListProductReviewsResponse, + GHLGetReviewsCountRequest, + GHLCountReviewsByStatusResponse, + GHLUpdateProductReviewRequest, + GHLUpdateProductReviewsResponse, + GHLDeleteProductReviewRequest, + GHLDeleteProductReviewResponse, + GHLBulkUpdateProductReviewsRequest, + // Invoice API types + CreateInvoiceTemplateDto, + CreateInvoiceTemplateResponseDto, + UpdateInvoiceTemplateDto, + UpdateInvoiceTemplateResponseDto, + DeleteInvoiceTemplateResponseDto, + ListTemplatesResponse, + InvoiceTemplate, + UpdateInvoiceLateFeesConfigurationDto, + UpdatePaymentMethodsConfigurationDto, + CreateInvoiceScheduleDto, + CreateInvoiceScheduleResponseDto, + UpdateInvoiceScheduleDto, + UpdateInvoiceScheduleResponseDto, + DeleteInvoiceScheduleResponseDto, + ListSchedulesResponse, + GetScheduleResponseDto, + ScheduleInvoiceScheduleDto, + ScheduleInvoiceScheduleResponseDto, + AutoPaymentScheduleDto, + AutoPaymentInvoiceScheduleResponseDto, + CancelInvoiceScheduleDto, + CancelInvoiceScheduleResponseDto, + UpdateAndScheduleInvoiceScheduleResponseDto, + Text2PayDto, + Text2PayInvoiceResponseDto, + GenerateInvoiceNumberResponse, + GetInvoiceResponseDto, + UpdateInvoiceDto, + UpdateInvoiceResponseDto, + DeleteInvoiceResponseDto, + VoidInvoiceDto, + VoidInvoiceResponseDto, + SendInvoiceDto, + SendInvoicesResponseDto, + RecordPaymentDto, + RecordPaymentResponseDto, + PatchInvoiceStatsLastViewedDto, + CreateEstimatesDto, + EstimateResponseDto, + UpdateEstimateDto, + GenerateEstimateNumberResponse, + SendEstimateDto, + CreateInvoiceFromEstimateDto, + CreateInvoiceFromEstimateResponseDto, + ListEstimatesResponseDto, + EstimateIdParam, + ListEstimateTemplateResponseDto, + EstimateTemplatesDto, + EstimateTemplateResponseDto, + CreateInvoiceDto, + CreateInvoiceResponseDto, + ListInvoicesResponseDto, + AltDto +} from '../types/ghl-types.js'; + +/** + * GoHighLevel API Client + * Handles all API communication with GHL services + */ +export class GHLApiClient { + private axiosInstance: AxiosInstance; + private config: GHLConfig; + + constructor(config: GHLConfig) { + this.config = config; + + // Create axios instance with base configuration + this.axiosInstance = axios.create({ + baseURL: config.baseUrl, + headers: { + 'Authorization': `Bearer ${config.accessToken}`, + 'Version': config.version, + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + timeout: 30000 // 30 second timeout + }); + + // Add request interceptor for logging + this.axiosInstance.interceptors.request.use( + (config) => { + process.stderr.write(`[GHL API] ${config.method?.toUpperCase()} ${config.url}\n`); + return config; + }, + (error) => { + console.error('[GHL API] Request error:', error); + return Promise.reject(error); + } + ); + + // Add response interceptor for error handling + this.axiosInstance.interceptors.response.use( + (response) => { + process.stderr.write(`[GHL API] Response ${response.status}: ${response.config.url}\n`); + return response; + }, + (error: AxiosError) => { + console.error('[GHL API] Response error:', { + status: error.response?.status, + message: error.response?.data?.message, + url: error.config?.url + }); + return Promise.reject(this.handleApiError(error)); + } + ); + } + + /** + * Handle API errors and convert to standardized format + */ + private handleApiError(error: AxiosError): Error { + const status = error.response?.status || 500; + const message = error.response?.data?.message || error.message || 'Unknown error'; + const errorMessage = Array.isArray(message) ? message.join(', ') : message; + + return new Error(`GHL API Error (${status}): ${errorMessage}`); + } + + /** + * Wrap API responses in standardized format + */ + private wrapResponse(data: T): GHLApiResponse { + return { + success: true, + data + }; + } + + /** + * Create custom headers for different API versions + */ + private getConversationHeaders() { + return { + 'Authorization': `Bearer ${this.config.accessToken}`, + 'Version': '2021-04-15', // Conversations API uses different version + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }; + } + + /** + * CONTACTS API METHODS + */ + + /** + * Create a new contact + * POST /contacts/ + */ + async createContact(contactData: GHLCreateContactRequest): Promise> { + try { + // Ensure locationId is set + const payload = { + ...contactData, + locationId: contactData.locationId || this.config.locationId + }; + + const response: AxiosResponse<{ contact: GHLContact }> = await this.axiosInstance.post( + '/contacts/', + payload + ); + + return this.wrapResponse(response.data.contact); + } catch (error) { + throw error; + } + } + + /** + * Get contact by ID + * GET /contacts/{contactId} + */ + async getContact(contactId: string): Promise> { + try { + const response: AxiosResponse<{ contact: GHLContact }> = await this.axiosInstance.get( + `/contacts/${contactId}` + ); + + return this.wrapResponse(response.data.contact); + } catch (error) { + throw error; + } + } + + /** + * Update existing contact + * PUT /contacts/{contactId} + */ + async updateContact(contactId: string, updates: Partial): Promise> { + try { + const response: AxiosResponse<{ contact: GHLContact; succeded: boolean }> = await this.axiosInstance.put( + `/contacts/${contactId}`, + updates + ); + + return this.wrapResponse(response.data.contact); + } catch (error) { + throw error; + } + } + + /** + * Delete contact + * DELETE /contacts/{contactId} + */ + async deleteContact(contactId: string): Promise> { + try { + const response: AxiosResponse<{ succeded: boolean }> = await this.axiosInstance.delete( + `/contacts/${contactId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Search contacts with advanced filters + * POST /contacts/search + */ + async searchContacts(searchParams: GHLSearchContactsRequest): Promise> { + try { + // Build minimal request body with only required/supported parameters + // Start with just locationId and pageLimit as per API requirements + const payload: any = { + locationId: searchParams.locationId || this.config.locationId, + pageLimit: searchParams.limit || 25 + }; + + // Only add optional parameters if they have valid values + if (searchParams.query && searchParams.query.trim()) { + payload.query = searchParams.query.trim(); + } + + if (searchParams.startAfterId && searchParams.startAfterId.trim()) { + payload.startAfterId = searchParams.startAfterId.trim(); + } + + if (searchParams.startAfter && typeof searchParams.startAfter === 'number') { + payload.startAfter = searchParams.startAfter; + } + + // Only add filters if we have valid filter values + if (searchParams.filters) { + const filters: any = {}; + let hasFilters = false; + + if (searchParams.filters.email && typeof searchParams.filters.email === 'string' && searchParams.filters.email.trim()) { + filters.email = searchParams.filters.email.trim(); + hasFilters = true; + } + + if (searchParams.filters.phone && typeof searchParams.filters.phone === 'string' && searchParams.filters.phone.trim()) { + filters.phone = searchParams.filters.phone.trim(); + hasFilters = true; + } + + if (searchParams.filters.tags && Array.isArray(searchParams.filters.tags) && searchParams.filters.tags.length > 0) { + filters.tags = searchParams.filters.tags; + hasFilters = true; + } + + if (searchParams.filters.dateAdded && typeof searchParams.filters.dateAdded === 'object') { + filters.dateAdded = searchParams.filters.dateAdded; + hasFilters = true; + } + + // Only add filters object if we have actual filters + if (hasFilters) { + payload.filters = filters; + } + } + + process.stderr.write(`[GHL API] Search contacts payload: ${JSON.stringify(payload, null, 2)}\n`); + + const response: AxiosResponse = await this.axiosInstance.post( + '/contacts/search', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + const axiosError = error as AxiosError; + process.stderr.write(`[GHL API] Search contacts error: ${JSON.stringify({ + status: axiosError.response?.status, + statusText: axiosError.response?.statusText, + data: axiosError.response?.data, + message: axiosError.message + }, null, 2)}\n`); + + const handledError = this.handleApiError(axiosError); + return { + success: false, + error: { + message: handledError.message, + statusCode: axiosError.response?.status || 500, + details: axiosError.response?.data + } + }; + } + } + + /** + * Get duplicate contact by email or phone + * GET /contacts/search/duplicate + */ + async getDuplicateContact(email?: string, phone?: string): Promise> { + try { + const params: any = { + locationId: this.config.locationId + }; + + if (email) params.email = encodeURIComponent(email); + if (phone) params.number = encodeURIComponent(phone); + + const response: AxiosResponse<{ contact?: GHLContact }> = await this.axiosInstance.get( + '/contacts/search/duplicate', + { params } + ); + + return this.wrapResponse(response.data.contact || null); + } catch (error) { + throw error; + } + } + + /** + * Add tags to contact + * POST /contacts/{contactId}/tags + */ + async addContactTags(contactId: string, tags: string[]): Promise> { + try { + const payload: GHLContactTagsRequest = { tags }; + + const response: AxiosResponse = await this.axiosInstance.post( + `/contacts/${contactId}/tags`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Remove tags from contact + * DELETE /contacts/{contactId}/tags + */ + async removeContactTags(contactId: string, tags: string[]): Promise> { + try { + const payload: GHLContactTagsRequest = { tags }; + + const response: AxiosResponse = await this.axiosInstance.delete( + `/contacts/${contactId}/tags`, + { data: payload } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * CONVERSATIONS API METHODS + */ + + /** + * Search conversations with filters + * GET /conversations/search + */ + async searchConversations(searchParams: GHLSearchConversationsRequest): Promise> { + try { + // Ensure locationId is set + const params = { + ...searchParams, + locationId: searchParams.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/conversations/search', + { + params, + headers: this.getConversationHeaders() + } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get conversation by ID + * GET /conversations/{conversationId} + */ + async getConversation(conversationId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.get( + `/conversations/${conversationId}`, + { headers: this.getConversationHeaders() } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Create a new conversation + * POST /conversations/ + */ + async createConversation(conversationData: GHLCreateConversationRequest): Promise> { + try { + // Ensure locationId is set + const payload = { + ...conversationData, + locationId: conversationData.locationId || this.config.locationId + }; + + const response: AxiosResponse<{ success: boolean; conversation: GHLCreateConversationResponse }> = await this.axiosInstance.post( + '/conversations/', + payload, + { headers: this.getConversationHeaders() } + ); + + return this.wrapResponse(response.data.conversation); + } catch (error) { + throw error; + } + } + + /** + * Update conversation + * PUT /conversations/{conversationId} + */ + async updateConversation(conversationId: string, updates: GHLUpdateConversationRequest): Promise> { + try { + // Ensure locationId is set + const payload = { + ...updates, + locationId: updates.locationId || this.config.locationId + }; + + const response: AxiosResponse<{ success: boolean; conversation: GHLConversation }> = await this.axiosInstance.put( + `/conversations/${conversationId}`, + payload, + { headers: this.getConversationHeaders() } + ); + + return this.wrapResponse(response.data.conversation); + } catch (error) { + throw error; + } + } + + /** + * Delete conversation + * DELETE /conversations/{conversationId} + */ + async deleteConversation(conversationId: string): Promise> { + try { + const response: AxiosResponse<{ success: boolean }> = await this.axiosInstance.delete( + `/conversations/${conversationId}`, + { headers: this.getConversationHeaders() } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get messages from a conversation + * GET /conversations/{conversationId}/messages + */ + async getConversationMessages( + conversationId: string, + options?: { + lastMessageId?: string; + limit?: number; + type?: string; + } + ): Promise> { + try { + const params: any = {}; + if (options?.lastMessageId) params.lastMessageId = options.lastMessageId; + if (options?.limit) params.limit = options.limit; + if (options?.type) params.type = options.type; + + const response: AxiosResponse = await this.axiosInstance.get( + `/conversations/${conversationId}/messages`, + { + params, + headers: this.getConversationHeaders() + } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get message by ID + * GET /conversations/messages/{id} + */ + async getMessage(messageId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.get( + `/conversations/messages/${messageId}`, + { headers: this.getConversationHeaders() } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Send a new message (SMS, Email, etc.) + * POST /conversations/messages + */ + async sendMessage(messageData: GHLSendMessageRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.post( + '/conversations/messages', + messageData, + { headers: this.getConversationHeaders() } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Send SMS message to a contact + * Convenience method for sending SMS + */ + async sendSMS(contactId: string, message: string, fromNumber?: string): Promise> { + try { + const messageData: GHLSendMessageRequest = { + type: 'SMS', + contactId, + message, + fromNumber + }; + + return await this.sendMessage(messageData); + } catch (error) { + throw error; + } + } + + /** + * Send Email message to a contact + * Convenience method for sending Email + */ + async sendEmail( + contactId: string, + subject: string, + message?: string, + html?: string, + options?: { + emailFrom?: string; + emailTo?: string; + emailCc?: string[]; + emailBcc?: string[]; + attachments?: string[]; + } + ): Promise> { + try { + const messageData: GHLSendMessageRequest = { + type: 'Email', + contactId, + subject, + message, + html, + ...options + }; + + return await this.sendMessage(messageData); + } catch (error) { + throw error; + } + } + + /** + * BLOG API METHODS + */ + + /** + * Get all blog sites for a location + * GET /blogs/site/all + */ + async getBlogSites(params: GHLGetBlogSitesRequest): Promise> { + try { + // Ensure locationId is set + const queryParams = { + locationId: params.locationId || this.config.locationId, + skip: params.skip, + limit: params.limit, + ...(params.searchTerm && { searchTerm: params.searchTerm }) + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/blogs/site/all', + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get blog posts for a specific blog + * GET /blogs/posts/all + */ + async getBlogPosts(params: GHLGetBlogPostsRequest): Promise> { + try { + // Ensure locationId is set + const queryParams = { + locationId: params.locationId || this.config.locationId, + blogId: params.blogId, + limit: params.limit, + offset: params.offset, + ...(params.searchTerm && { searchTerm: params.searchTerm }), + ...(params.status && { status: params.status }) + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/blogs/posts/all', + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Create a new blog post + * POST /blogs/posts + */ + async createBlogPost(postData: GHLCreateBlogPostRequest): Promise> { + try { + // Ensure locationId is set + const payload = { + ...postData, + locationId: postData.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/blogs/posts', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update an existing blog post + * PUT /blogs/posts/{postId} + */ + async updateBlogPost(postId: string, postData: GHLUpdateBlogPostRequest): Promise> { + try { + // Ensure locationId is set + const payload = { + ...postData, + locationId: postData.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.put( + `/blogs/posts/${postId}`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get all blog authors for a location + * GET /blogs/authors + */ + async getBlogAuthors(params: GHLGetBlogAuthorsRequest): Promise> { + try { + // Ensure locationId is set + const queryParams = { + locationId: params.locationId || this.config.locationId, + limit: params.limit, + offset: params.offset + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/blogs/authors', + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get all blog categories for a location + * GET /blogs/categories + */ + async getBlogCategories(params: GHLGetBlogCategoriesRequest): Promise> { + try { + // Ensure locationId is set + const queryParams = { + locationId: params.locationId || this.config.locationId, + limit: params.limit, + offset: params.offset + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/blogs/categories', + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Check if a URL slug exists (for validation before creating/updating posts) + * GET /blogs/posts/url-slug-exists + */ + async checkUrlSlugExists(params: GHLCheckUrlSlugRequest): Promise> { + try { + // Ensure locationId is set + const queryParams = { + locationId: params.locationId || this.config.locationId, + urlSlug: params.urlSlug, + ...(params.postId && { postId: params.postId }) + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/blogs/posts/url-slug-exists', + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * TASKS API METHODS + */ + + /** + * Get all tasks for a contact + * GET /contacts/{contactId}/tasks + */ + async getContactTasks(contactId: string): Promise> { + try { + const response: AxiosResponse<{ tasks: GHLTask[] }> = await this.axiosInstance.get( + `/contacts/${contactId}/tasks` + ); + + return this.wrapResponse(response.data.tasks); + } catch (error) { + throw error; + } + } + + /** + * Create task for contact + * POST /contacts/{contactId}/tasks + */ + async createContactTask(contactId: string, taskData: Omit): Promise> { + try { + const response: AxiosResponse<{ task: GHLTask }> = await this.axiosInstance.post( + `/contacts/${contactId}/tasks`, + taskData + ); + + return this.wrapResponse(response.data.task); + } catch (error) { + throw error; + } + } + + /** + * NOTES API METHODS + */ + + /** + * Get all notes for a contact + * GET /contacts/{contactId}/notes + */ + async getContactNotes(contactId: string): Promise> { + try { + const response: AxiosResponse<{ notes: GHLNote[] }> = await this.axiosInstance.get( + `/contacts/${contactId}/notes` + ); + + return this.wrapResponse(response.data.notes); + } catch (error) { + throw error; + } + } + + /** + * Create note for contact + * POST /contacts/{contactId}/notes + */ + async createContactNote(contactId: string, noteData: Omit): Promise> { + try { + const response: AxiosResponse<{ note: GHLNote }> = await this.axiosInstance.post( + `/contacts/${contactId}/notes`, + noteData + ); + + return this.wrapResponse(response.data.note); + } catch (error) { + throw error; + } + } + + /** + * ADDITIONAL CONTACT API METHODS + */ + + /** + * Get a specific task for a contact + * GET /contacts/{contactId}/tasks/{taskId} + */ + async getContactTask(contactId: string, taskId: string): Promise> { + try { + const response: AxiosResponse<{ task: GHLTask }> = await this.axiosInstance.get( + `/contacts/${contactId}/tasks/${taskId}` + ); + + return this.wrapResponse(response.data.task); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update a task for a contact + * PUT /contacts/{contactId}/tasks/{taskId} + */ + async updateContactTask(contactId: string, taskId: string, updates: Partial): Promise> { + try { + const response: AxiosResponse<{ task: GHLTask }> = await this.axiosInstance.put( + `/contacts/${contactId}/tasks/${taskId}`, + updates + ); + + return this.wrapResponse(response.data.task); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Delete a task for a contact + * DELETE /contacts/{contactId}/tasks/{taskId} + */ + async deleteContactTask(contactId: string, taskId: string): Promise> { + try { + const response: AxiosResponse<{ succeded: boolean }> = await this.axiosInstance.delete( + `/contacts/${contactId}/tasks/${taskId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update task completion status + * PUT /contacts/{contactId}/tasks/{taskId}/completed + */ + async updateTaskCompletion(contactId: string, taskId: string, completed: boolean): Promise> { + try { + const response: AxiosResponse<{ task: GHLTask }> = await this.axiosInstance.put( + `/contacts/${contactId}/tasks/${taskId}/completed`, + { completed } + ); + + return this.wrapResponse(response.data.task); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get a specific note for a contact + * GET /contacts/{contactId}/notes/{noteId} + */ + async getContactNote(contactId: string, noteId: string): Promise> { + try { + const response: AxiosResponse<{ note: GHLNote }> = await this.axiosInstance.get( + `/contacts/${contactId}/notes/${noteId}` + ); + + return this.wrapResponse(response.data.note); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update a note for a contact + * PUT /contacts/{contactId}/notes/{noteId} + */ + async updateContactNote(contactId: string, noteId: string, updates: Partial): Promise> { + try { + const response: AxiosResponse<{ note: GHLNote }> = await this.axiosInstance.put( + `/contacts/${contactId}/notes/${noteId}`, + updates + ); + + return this.wrapResponse(response.data.note); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Delete a note for a contact + * DELETE /contacts/{contactId}/notes/{noteId} + */ + async deleteContactNote(contactId: string, noteId: string): Promise> { + try { + const response: AxiosResponse<{ succeded: boolean }> = await this.axiosInstance.delete( + `/contacts/${contactId}/notes/${noteId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Upsert contact (create or update based on email/phone) + * POST /contacts/upsert + */ + async upsertContact(contactData: Partial): Promise> { + try { + const payload = { + ...contactData, + locationId: contactData.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/contacts/upsert', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get contacts by business ID + * GET /contacts/business/{businessId} + */ + async getContactsByBusiness(businessId: string, params: { limit?: number; skip?: number; query?: string } = {}): Promise> { + try { + const queryParams = { + limit: params.limit || 25, + skip: params.skip || 0, + ...(params.query && { query: params.query }) + }; + + const response: AxiosResponse = await this.axiosInstance.get( + `/contacts/business/${businessId}`, + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get contact appointments + * GET /contacts/{contactId}/appointments + */ + async getContactAppointments(contactId: string): Promise> { + try { + const response: AxiosResponse<{ events: GHLAppointment[] }> = await this.axiosInstance.get( + `/contacts/${contactId}/appointments` + ); + + return this.wrapResponse(response.data.events); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Bulk update contact tags + * POST /contacts/tags/bulk + */ + async bulkUpdateContactTags(contactIds: string[], tags: string[], operation: 'add' | 'remove', removeAllTags?: boolean): Promise> { + try { + const payload = { + ids: contactIds, + tags, + operation, + ...(removeAllTags !== undefined && { removeAllTags }) + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/contacts/tags/bulk', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Bulk update contact business + * POST /contacts/business/bulk + */ + async bulkUpdateContactBusiness(contactIds: string[], businessId?: string): Promise> { + try { + const payload = { + ids: contactIds, + businessId: businessId || null + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/contacts/business/bulk', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Add contact followers + * POST /contacts/{contactId}/followers + */ + async addContactFollowers(contactId: string, followers: string[]): Promise> { + try { + const payload = { followers }; + + const response: AxiosResponse = await this.axiosInstance.post( + `/contacts/${contactId}/followers`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Remove contact followers + * DELETE /contacts/{contactId}/followers + */ + async removeContactFollowers(contactId: string, followers: string[]): Promise> { + try { + const payload = { followers }; + + const response: AxiosResponse = await this.axiosInstance.delete( + `/contacts/${contactId}/followers`, + { data: payload } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Add contact to campaign + * POST /contacts/{contactId}/campaigns/{campaignId} + */ + async addContactToCampaign(contactId: string, campaignId: string): Promise> { + try { + const response: AxiosResponse<{ succeded: boolean }> = await this.axiosInstance.post( + `/contacts/${contactId}/campaigns/${campaignId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Remove contact from campaign + * DELETE /contacts/{contactId}/campaigns/{campaignId} + */ + async removeContactFromCampaign(contactId: string, campaignId: string): Promise> { + try { + const response: AxiosResponse<{ succeded: boolean }> = await this.axiosInstance.delete( + `/contacts/${contactId}/campaigns/${campaignId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Remove contact from all campaigns + * DELETE /contacts/{contactId}/campaigns + */ + async removeContactFromAllCampaigns(contactId: string): Promise> { + try { + const response: AxiosResponse<{ succeded: boolean }> = await this.axiosInstance.delete( + `/contacts/${contactId}/campaigns` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Add contact to workflow + * POST /contacts/{contactId}/workflow/{workflowId} + */ + async addContactToWorkflow(contactId: string, workflowId: string, eventStartTime?: string): Promise> { + try { + const payload = eventStartTime ? { eventStartTime } : {}; + + const response: AxiosResponse<{ succeded: boolean }> = await this.axiosInstance.post( + `/contacts/${contactId}/workflow/${workflowId}`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Remove contact from workflow + * DELETE /contacts/{contactId}/workflow/{workflowId} + */ + async removeContactFromWorkflow(contactId: string, workflowId: string, eventStartTime?: string): Promise> { + try { + const payload = eventStartTime ? { eventStartTime } : {}; + + const response: AxiosResponse<{ succeded: boolean }> = await this.axiosInstance.delete( + `/contacts/${contactId}/workflow/${workflowId}`, + { data: payload } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * UTILITY METHODS + */ + + /** + * Test API connection and authentication + */ + async testConnection(): Promise> { + try { + // Test with a simple GET request to check API connectivity + const response: AxiosResponse = await this.axiosInstance.get('/locations/' + this.config.locationId); + + return this.wrapResponse({ + status: 'connected', + locationId: this.config.locationId + }); + } catch (error) { + throw new Error(`GHL API connection test failed: ${error}`); + } + } + + /** + * Update access token + */ + updateAccessToken(newToken: string): void { + this.config.accessToken = newToken; + this.axiosInstance.defaults.headers['Authorization'] = `Bearer ${newToken}`; + process.stderr.write('[GHL API] Access token updated\n'); + } + + /** + * Get current configuration + */ + getConfig(): Readonly { + return { ...this.config }; + } + + /** + * Generic request method for new endpoints + * Used by new tool modules that don't have specific client methods yet + */ + async makeRequest(method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', path: string, body?: Record): Promise> { + try { + let response; + switch (method) { + case 'GET': + response = await this.axiosInstance.get(path); + break; + case 'POST': + response = await this.axiosInstance.post(path, body); + break; + case 'PUT': + response = await this.axiosInstance.put(path, body); + break; + case 'PATCH': + response = await this.axiosInstance.patch(path, body); + break; + case 'DELETE': + response = await this.axiosInstance.delete(path); + break; + } + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * OPPORTUNITIES API METHODS + */ + + /** + * Search opportunities with advanced filters + * GET /opportunities/search + */ + async searchOpportunities(searchParams: GHLSearchOpportunitiesRequest): Promise> { + try { + // Build query parameters with exact API naming (underscores) + const params: any = { + location_id: searchParams.location_id || this.config.locationId + }; + + // Add optional search parameters only if they have values + if (searchParams.q && searchParams.q.trim()) { + params.q = searchParams.q.trim(); + } + + if (searchParams.pipeline_id) { + params.pipeline_id = searchParams.pipeline_id; + } + + if (searchParams.pipeline_stage_id) { + params.pipeline_stage_id = searchParams.pipeline_stage_id; + } + + if (searchParams.contact_id) { + params.contact_id = searchParams.contact_id; + } + + if (searchParams.status) { + params.status = searchParams.status; + } + + if (searchParams.assigned_to) { + params.assigned_to = searchParams.assigned_to; + } + + if (searchParams.campaignId) { + params.campaignId = searchParams.campaignId; + } + + if (searchParams.id) { + params.id = searchParams.id; + } + + if (searchParams.order) { + params.order = searchParams.order; + } + + if (searchParams.endDate) { + params.endDate = searchParams.endDate; + } + + if (searchParams.startAfter) { + params.startAfter = searchParams.startAfter; + } + + if (searchParams.startAfterId) { + params.startAfterId = searchParams.startAfterId; + } + + if (searchParams.date) { + params.date = searchParams.date; + } + + if (searchParams.country) { + params.country = searchParams.country; + } + + if (searchParams.page) { + params.page = searchParams.page; + } + + if (searchParams.limit) { + params.limit = searchParams.limit; + } + + if (searchParams.getTasks !== undefined) { + params.getTasks = searchParams.getTasks; + } + + if (searchParams.getNotes !== undefined) { + params.getNotes = searchParams.getNotes; + } + + if (searchParams.getCalendarEvents !== undefined) { + params.getCalendarEvents = searchParams.getCalendarEvents; + } + + process.stderr.write(`[GHL API] Search opportunities params: ${JSON.stringify(params, null, 2)}\n`); + + const response: AxiosResponse = await this.axiosInstance.get( + '/opportunities/search', + { params } + ); + + return this.wrapResponse(response.data); + } catch (error) { + const axiosError = error as AxiosError; + process.stderr.write(`[GHL API] Search opportunities error: ${JSON.stringify({ + status: axiosError.response?.status, + statusText: axiosError.response?.statusText, + data: axiosError.response?.data, + message: axiosError.message + }, null, 2)}\n`); + + throw this.handleApiError(axiosError); + } + } + + /** + * Get all pipelines for a location + * GET /opportunities/pipelines + */ + async getPipelines(locationId?: string): Promise> { + try { + const params = { + locationId: locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/opportunities/pipelines', + { params } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get opportunity by ID + * GET /opportunities/{id} + */ + async getOpportunity(opportunityId: string): Promise> { + try { + const response: AxiosResponse<{ opportunity: GHLOpportunity }> = await this.axiosInstance.get( + `/opportunities/${opportunityId}` + ); + + return this.wrapResponse(response.data.opportunity); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Create a new opportunity + * POST /opportunities/ + */ + async createOpportunity(opportunityData: GHLCreateOpportunityRequest): Promise> { + try { + // Ensure locationId is set + const payload = { + ...opportunityData, + locationId: opportunityData.locationId || this.config.locationId + }; + + const response: AxiosResponse<{ opportunity: GHLOpportunity }> = await this.axiosInstance.post( + '/opportunities/', + payload + ); + + return this.wrapResponse(response.data.opportunity); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update existing opportunity + * PUT /opportunities/{id} + */ + async updateOpportunity(opportunityId: string, updates: GHLUpdateOpportunityRequest): Promise> { + try { + const response: AxiosResponse<{ opportunity: GHLOpportunity }> = await this.axiosInstance.put( + `/opportunities/${opportunityId}`, + updates + ); + + return this.wrapResponse(response.data.opportunity); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update opportunity status + * PUT /opportunities/{id}/status + */ + async updateOpportunityStatus(opportunityId: string, status: GHLOpportunityStatus): Promise> { + try { + const payload: GHLUpdateOpportunityStatusRequest = { status }; + + const response: AxiosResponse<{ succeded: boolean }> = await this.axiosInstance.put( + `/opportunities/${opportunityId}/status`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Upsert opportunity (create or update) + * POST /opportunities/upsert + */ + async upsertOpportunity(opportunityData: GHLUpsertOpportunityRequest): Promise> { + try { + // Ensure locationId is set + const payload = { + ...opportunityData, + locationId: opportunityData.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/opportunities/upsert', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Delete opportunity + * DELETE /opportunities/{id} + */ + async deleteOpportunity(opportunityId: string): Promise> { + try { + const response: AxiosResponse<{ succeded: boolean }> = await this.axiosInstance.delete( + `/opportunities/${opportunityId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Add followers to opportunity + * POST /opportunities/{id}/followers + */ + async addOpportunityFollowers(opportunityId: string, followers: string[]): Promise> { + try { + const payload = { followers }; + + const response: AxiosResponse = await this.axiosInstance.post( + `/opportunities/${opportunityId}/followers`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Remove followers from opportunity + * DELETE /opportunities/{id}/followers + */ + async removeOpportunityFollowers(opportunityId: string, followers: string[]): Promise> { + try { + const payload = { followers }; + + const response: AxiosResponse = await this.axiosInstance.delete( + `/opportunities/${opportunityId}/followers`, + { data: payload } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * CALENDAR & APPOINTMENTS API METHODS + */ + + /** + * Get all calendar groups in a location + * GET /calendars/groups + */ + async getCalendarGroups(locationId?: string): Promise> { + try { + const params = { + locationId: locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/calendars/groups', + { params } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Create a new calendar group + * POST /calendars/groups + */ + async createCalendarGroup(groupData: GHLCreateCalendarGroupRequest): Promise> { + try { + const payload = { + ...groupData, + locationId: groupData.locationId || this.config.locationId + }; + + const response: AxiosResponse<{ group: GHLCalendarGroup }> = await this.axiosInstance.post( + '/calendars/groups', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get all calendars in a location + * GET /calendars/ + */ + async getCalendars(params?: { locationId?: string; groupId?: string; showDrafted?: boolean }): Promise> { + try { + const queryParams = { + locationId: params?.locationId || this.config.locationId, + ...(params?.groupId && { groupId: params.groupId }), + ...(params?.showDrafted !== undefined && { showDrafted: params.showDrafted }) + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/calendars/', + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Create a new calendar + * POST /calendars/ + */ + async createCalendar(calendarData: GHLCreateCalendarRequest): Promise> { + try { + const payload = { + ...calendarData, + locationId: calendarData.locationId || this.config.locationId + }; + + const response: AxiosResponse<{ calendar: GHLCalendar }> = await this.axiosInstance.post( + '/calendars/', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get calendar by ID + * GET /calendars/{calendarId} + */ + async getCalendar(calendarId: string): Promise> { + try { + const response: AxiosResponse<{ calendar: GHLCalendar }> = await this.axiosInstance.get( + `/calendars/${calendarId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update calendar by ID + * PUT /calendars/{calendarId} + */ + async updateCalendar(calendarId: string, updates: GHLUpdateCalendarRequest): Promise> { + try { + const response: AxiosResponse<{ calendar: GHLCalendar }> = await this.axiosInstance.put( + `/calendars/${calendarId}`, + updates + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Delete calendar by ID + * DELETE /calendars/{calendarId} + */ + async deleteCalendar(calendarId: string): Promise> { + try { + const response: AxiosResponse<{ success: boolean }> = await this.axiosInstance.delete( + `/calendars/${calendarId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get calendar events/appointments + * GET /calendars/events + */ + async getCalendarEvents(eventParams: GHLGetCalendarEventsRequest): Promise> { + try { + const params = { + locationId: eventParams.locationId || this.config.locationId, + startTime: eventParams.startTime, + endTime: eventParams.endTime, + ...(eventParams.userId && { userId: eventParams.userId }), + ...(eventParams.calendarId && { calendarId: eventParams.calendarId }), + ...(eventParams.groupId && { groupId: eventParams.groupId }) + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/calendars/events', + { params } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get blocked slots + * GET /calendars/blocked-slots + */ + async getBlockedSlots(eventParams: GHLGetCalendarEventsRequest): Promise> { + try { + const params = { + locationId: eventParams.locationId || this.config.locationId, + startTime: eventParams.startTime, + endTime: eventParams.endTime, + ...(eventParams.userId && { userId: eventParams.userId }), + ...(eventParams.calendarId && { calendarId: eventParams.calendarId }), + ...(eventParams.groupId && { groupId: eventParams.groupId }) + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/calendars/blocked-slots', + { params } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get free slots for a calendar + * GET /calendars/{calendarId}/free-slots + */ + async getFreeSlots(slotParams: GHLGetFreeSlotsRequest): Promise> { + try { + const params = { + startDate: slotParams.startDate, + endDate: slotParams.endDate, + ...(slotParams.timezone && { timezone: slotParams.timezone }), + ...(slotParams.userId && { userId: slotParams.userId }), + ...(slotParams.userIds && { userIds: slotParams.userIds }), + ...(slotParams.enableLookBusy !== undefined && { enableLookBusy: slotParams.enableLookBusy }) + }; + + const response: AxiosResponse = await this.axiosInstance.get( + `/calendars/${slotParams.calendarId}/free-slots`, + { params } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Create a new appointment + * POST /calendars/events/appointments + */ + async createAppointment(appointmentData: GHLCreateAppointmentRequest): Promise> { + try { + const payload = { + ...appointmentData, + locationId: appointmentData.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/calendars/events/appointments', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get appointment by ID + * GET /calendars/events/appointments/{eventId} + */ + async getAppointment(appointmentId: string): Promise> { + try { + const response: AxiosResponse<{ event: GHLCalendarEvent }> = await this.axiosInstance.get( + `/calendars/events/appointments/${appointmentId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update appointment by ID + * PUT /calendars/events/appointments/{eventId} + */ + async updateAppointment(appointmentId: string, updates: GHLUpdateAppointmentRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.put( + `/calendars/events/appointments/${appointmentId}`, + updates + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Delete appointment by ID + * DELETE /calendars/events/appointments/{eventId} + */ + async deleteAppointment(appointmentId: string): Promise> { + try { + const response: AxiosResponse<{ succeeded: boolean }> = await this.axiosInstance.delete( + `/calendars/events/appointments/${appointmentId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + + + /** + * Update block slot by ID + * PUT /calendars/events/block-slots/{eventId} + */ + async updateBlockSlot(blockSlotId: string, updates: GHLUpdateBlockSlotRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.put( + `/calendars/events/block-slots/${blockSlotId}`, + updates + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * EMAIL API METHODS + */ + + async getEmailCampaigns(params: MCPGetEmailCampaignsParams): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.get('/emails/schedule', { + params: { + locationId: this.config.locationId, + ...params + } + }); + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + async createEmailTemplate(params: MCPCreateEmailTemplateParams): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.post('/emails/builder', { + locationId: this.config.locationId, + type: 'html', + ...params + }); + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + async getEmailTemplates(params: MCPGetEmailTemplatesParams): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.get('/emails/builder', { + params: { + locationId: this.config.locationId, + ...params + } + }); + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + async updateEmailTemplate(params: MCPUpdateEmailTemplateParams): Promise> { + try { + const { templateId, ...data } = params; + const response: AxiosResponse = await this.axiosInstance.post('/emails/builder/data', { + locationId: this.config.locationId, + templateId, + ...data, + editorType: 'html' + }); + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + async deleteEmailTemplate(params: MCPDeleteEmailTemplateParams): Promise> { + try { + const { templateId } = params; + const response: AxiosResponse = await this.axiosInstance.delete(`/emails/builder/${this.config.locationId}/${templateId}`); + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * LOCATION API METHODS + */ + + /** + * Search locations/sub-accounts + * GET /locations/search + */ + async searchLocations(params: { + companyId?: string; + skip?: number; + limit?: number; + order?: 'asc' | 'desc'; + email?: string; + } = {}): Promise> { + try { + const queryParams = { + skip: params.skip || 0, + limit: params.limit || 10, + order: params.order || 'asc', + ...(params.companyId && { companyId: params.companyId }), + ...(params.email && { email: params.email }) + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/locations/search', + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get location by ID + * GET /locations/{locationId} + */ + async getLocationById(locationId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.get( + `/locations/${locationId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Create a new location/sub-account + * POST /locations/ + */ + async createLocation(locationData: GHLCreateLocationRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.post( + '/locations/', + locationData + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update location/sub-account + * PUT /locations/{locationId} + */ + async updateLocation(locationId: string, updates: GHLUpdateLocationRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.put( + `/locations/${locationId}`, + updates + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Delete location/sub-account + * DELETE /locations/{locationId} + */ + async deleteLocation(locationId: string, deleteTwilioAccount: boolean): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.delete( + `/locations/${locationId}`, + { + params: { deleteTwilioAccount } + } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * LOCATION TAGS API METHODS + */ + + /** + * Get location tags + * GET /locations/{locationId}/tags + */ + async getLocationTags(locationId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.get( + `/locations/${locationId}/tags` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Create location tag + * POST /locations/{locationId}/tags + */ + async createLocationTag(locationId: string, tagData: GHLLocationTagRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.post( + `/locations/${locationId}/tags`, + tagData + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get location tag by ID + * GET /locations/{locationId}/tags/{tagId} + */ + async getLocationTag(locationId: string, tagId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.get( + `/locations/${locationId}/tags/${tagId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update location tag + * PUT /locations/{locationId}/tags/{tagId} + */ + async updateLocationTag(locationId: string, tagId: string, tagData: GHLLocationTagRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.put( + `/locations/${locationId}/tags/${tagId}`, + tagData + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Delete location tag + * DELETE /locations/{locationId}/tags/{tagId} + */ + async deleteLocationTag(locationId: string, tagId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.delete( + `/locations/${locationId}/tags/${tagId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * LOCATION TASKS API METHODS + */ + + /** + * Search location tasks + * POST /locations/{locationId}/tasks/search + */ + async searchLocationTasks(locationId: string, searchParams: GHLLocationTaskSearchRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.post( + `/locations/${locationId}/tasks/search`, + searchParams + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * CUSTOM FIELDS API METHODS + */ + + /** + * Get custom fields for location + * GET /locations/{locationId}/customFields + */ + async getLocationCustomFields(locationId: string, model?: 'contact' | 'opportunity' | 'all'): Promise> { + try { + const params: any = {}; + if (model) params.model = model; + + const response: AxiosResponse = await this.axiosInstance.get( + `/locations/${locationId}/customFields`, + { params } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Create custom field for location + * POST /locations/{locationId}/customFields + */ + async createLocationCustomField(locationId: string, fieldData: GHLCreateCustomFieldRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.post( + `/locations/${locationId}/customFields`, + fieldData + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get custom field by ID + * GET /locations/{locationId}/customFields/{id} + */ + async getLocationCustomField(locationId: string, customFieldId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.get( + `/locations/${locationId}/customFields/${customFieldId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update custom field + * PUT /locations/{locationId}/customFields/{id} + */ + async updateLocationCustomField(locationId: string, customFieldId: string, fieldData: GHLUpdateCustomFieldRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.put( + `/locations/${locationId}/customFields/${customFieldId}`, + fieldData + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Delete custom field + * DELETE /locations/{locationId}/customFields/{id} + */ + async deleteLocationCustomField(locationId: string, customFieldId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.delete( + `/locations/${locationId}/customFields/${customFieldId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Upload file to custom fields + * POST /locations/{locationId}/customFields/upload + */ + async uploadLocationCustomFieldFile(locationId: string, uploadData: GHLFileUploadRequest): Promise> { + try { + // Note: This endpoint expects multipart/form-data but we'll handle it as JSON for now + // In a real implementation, you'd use FormData for file uploads + const response: AxiosResponse = await this.axiosInstance.post( + `/locations/${locationId}/customFields/upload`, + uploadData, + { headers: { 'Content-Type': 'multipart/form-data' } } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * CUSTOM VALUES API METHODS + */ + + /** + * Get custom values for location + * GET /locations/{locationId}/customValues + */ + async getLocationCustomValues(locationId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.get( + `/locations/${locationId}/customValues` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Create custom value for location + * POST /locations/{locationId}/customValues + */ + async createLocationCustomValue(locationId: string, valueData: GHLCustomValueRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.post( + `/locations/${locationId}/customValues`, + valueData + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get custom value by ID + * GET /locations/{locationId}/customValues/{id} + */ + async getLocationCustomValue(locationId: string, customValueId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.get( + `/locations/${locationId}/customValues/${customValueId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update custom value + * PUT /locations/{locationId}/customValues/{id} + */ + async updateLocationCustomValue(locationId: string, customValueId: string, valueData: GHLCustomValueRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.put( + `/locations/${locationId}/customValues/${customValueId}`, + valueData + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Delete custom value + * DELETE /locations/{locationId}/customValues/{id} + */ + async deleteLocationCustomValue(locationId: string, customValueId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.delete( + `/locations/${locationId}/customValues/${customValueId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * TEMPLATES API METHODS + */ + + /** + * Get location templates (SMS/Email) + * GET /locations/{locationId}/templates + */ + async getLocationTemplates(locationId: string, params: { + originId: string; + deleted?: boolean; + skip?: number; + limit?: number; + type?: 'sms' | 'email' | 'whatsapp'; + }): Promise> { + try { + const queryParams = { + originId: params.originId, + deleted: params.deleted || false, + skip: params.skip || 0, + limit: params.limit || 25, + ...(params.type && { type: params.type }) + }; + + const response: AxiosResponse = await this.axiosInstance.get( + `/locations/${locationId}/templates`, + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Delete location template + * DELETE /locations/{locationId}/templates/{id} + */ + async deleteLocationTemplate(locationId: string, templateId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.delete( + `/locations/${locationId}/templates/${templateId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * TIMEZONES API METHODS + */ + + /** + * Get available timezones + * GET /locations/{locationId}/timezones + */ + async getTimezones(locationId?: string): Promise> { + try { + const endpoint = locationId ? `/locations/${locationId}/timezones` : '/locations/timezones'; + const response: AxiosResponse = await this.axiosInstance.get(endpoint); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * EMAIL ISV (VERIFICATION) API METHODS + */ + + /** + * Verify email address or contact + * POST /email/verify + */ + async verifyEmail(locationId: string, verificationData: GHLEmailVerificationRequest): Promise> { + try { + const params = { + locationId: locationId + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/email/verify', + verificationData, + { params } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * ADDITIONAL CONVERSATION/MESSAGE API METHODS + */ + + /** + * Get email message by ID + * GET /conversations/messages/email/{id} + */ + async getEmailMessage(emailMessageId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.get( + `/conversations/messages/email/${emailMessageId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Cancel scheduled email message + * DELETE /conversations/messages/email/{emailMessageId}/schedule + */ + async cancelScheduledEmail(emailMessageId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.delete( + `/conversations/messages/email/${emailMessageId}/schedule` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Add inbound message manually + * POST /conversations/messages/inbound + */ + async addInboundMessage(messageData: GHLProcessInboundMessageRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.post( + '/conversations/messages/inbound', + messageData, + { headers: this.getConversationHeaders() } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Add outbound call manually + * POST /conversations/messages/outbound + */ + async addOutboundCall(messageData: GHLProcessOutboundMessageRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.post( + '/conversations/messages/outbound', + messageData, + { headers: this.getConversationHeaders() } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Cancel scheduled message + * DELETE /conversations/messages/{messageId}/schedule + */ + async cancelScheduledMessage(messageId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.delete( + `/conversations/messages/${messageId}/schedule`, + { headers: this.getConversationHeaders() } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Upload file attachments for messages + * POST /conversations/messages/upload + */ + async uploadMessageAttachments(uploadData: GHLUploadFilesRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.post( + '/conversations/messages/upload', + uploadData, + { + headers: { + ...this.getConversationHeaders(), + 'Content-Type': 'multipart/form-data' + } + } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update message status + * PUT /conversations/messages/{messageId}/status + */ + async updateMessageStatus(messageId: string, statusData: GHLUpdateMessageStatusRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.put( + `/conversations/messages/${messageId}/status`, + statusData, + { headers: this.getConversationHeaders() } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get message recording + * GET /conversations/messages/{messageId}/locations/{locationId}/recording + */ + async getMessageRecording(messageId: string, locationId?: string): Promise> { + try { + const locId = locationId || this.config.locationId; + const response: AxiosResponse = await this.axiosInstance.get( + `/conversations/messages/${messageId}/locations/${locId}/recording`, + { + headers: this.getConversationHeaders(), + responseType: 'arraybuffer' + } + ); + + const recordingResponse: GHLMessageRecordingResponse = { + audioData: response.data, + contentType: response.headers['content-type'] || 'audio/x-wav', + contentDisposition: response.headers['content-disposition'] || 'attachment; filename=audio.wav' + }; + + return this.wrapResponse(recordingResponse); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get message transcription + * GET /conversations/locations/{locationId}/messages/{messageId}/transcription + */ + async getMessageTranscription(messageId: string, locationId?: string): Promise> { + try { + const locId = locationId || this.config.locationId; + const response: AxiosResponse = await this.axiosInstance.get( + `/conversations/locations/${locId}/messages/${messageId}/transcription`, + { headers: this.getConversationHeaders() } + ); + + return this.wrapResponse({ transcriptions: response.data }); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Download message transcription + * GET /conversations/locations/{locationId}/messages/{messageId}/transcription/download + */ + async downloadMessageTranscription(messageId: string, locationId?: string): Promise> { + try { + const locId = locationId || this.config.locationId; + const response: AxiosResponse = await this.axiosInstance.get( + `/conversations/locations/${locId}/messages/${messageId}/transcription/download`, + { + headers: this.getConversationHeaders(), + responseType: 'text' + } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Live chat typing indicator + * POST /conversations/providers/live-chat/typing + */ + async liveChatTyping(typingData: GHLLiveChatTypingRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.post( + '/conversations/providers/live-chat/typing', + typingData, + { headers: this.getConversationHeaders() } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + // ============================================================================ + // SOCIAL MEDIA POSTING API METHODS + // ============================================================================ + + // ===== POST MANAGEMENT ===== + + /** + * Search/List Social Media Posts + */ + async searchSocialPosts(searchData: GHLSearchPostsRequest): Promise> { + try { + const locationId = this.config.locationId; + const response: AxiosResponse = await this.axiosInstance.post( + `/social-media-posting/${locationId}/posts/list`, + searchData + ); + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Create Social Media Post + */ + async createSocialPost(postData: GHLCreatePostRequest): Promise> { + try { + const locationId = this.config.locationId; + const response: AxiosResponse = await this.axiosInstance.post( + `/social-media-posting/${locationId}/posts`, + postData + ); + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get Social Media Post by ID + */ + async getSocialPost(postId: string): Promise> { + try { + const locationId = this.config.locationId; + const response: AxiosResponse = await this.axiosInstance.get( + `/social-media-posting/${locationId}/posts/${postId}` + ); + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update Social Media Post + */ + async updateSocialPost(postId: string, updateData: GHLUpdatePostRequest): Promise> { + try { + const locationId = this.config.locationId; + const response: AxiosResponse = await this.axiosInstance.put( + `/social-media-posting/${locationId}/posts/${postId}`, + updateData + ); + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Delete Social Media Post + */ + async deleteSocialPost(postId: string): Promise> { + try { + const locationId = this.config.locationId; + const response: AxiosResponse = await this.axiosInstance.delete( + `/social-media-posting/${locationId}/posts/${postId}` + ); + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Bulk Delete Social Media Posts + */ + async bulkDeleteSocialPosts(deleteData: GHLBulkDeletePostsRequest): Promise> { + try { + const locationId = this.config.locationId; + const response: AxiosResponse = await this.axiosInstance.post( + `/social-media-posting/${locationId}/posts/bulk-delete`, + deleteData + ); + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + // ===== ACCOUNT MANAGEMENT ===== + + /** + * Get Social Media Accounts and Groups + */ + async getSocialAccounts(): Promise> { + try { + const locationId = this.config.locationId; + const response: AxiosResponse = await this.axiosInstance.get( + `/social-media-posting/${locationId}/accounts` + ); + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Delete Social Media Account + */ + async deleteSocialAccount(accountId: string, companyId?: string, userId?: string): Promise> { + try { + const locationId = this.config.locationId; + const params: any = {}; + if (companyId) params.companyId = companyId; + if (userId) params.userId = userId; + + const response: AxiosResponse = await this.axiosInstance.delete( + `/social-media-posting/${locationId}/accounts/${accountId}`, + { params } + ); + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + // ===== CSV OPERATIONS ===== + + /** + * Upload CSV for Social Media Posts + */ + async uploadSocialCSV(csvData: GHLUploadCSVRequest): Promise> { + try { + const locationId = this.config.locationId; + // Note: This would typically use FormData for file upload + const response: AxiosResponse = await this.axiosInstance.post( + `/social-media-posting/${locationId}/csv`, + csvData + ); + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get CSV Upload Status + */ + async getSocialCSVUploadStatus(skip?: number, limit?: number, includeUsers?: boolean, userId?: string): Promise> { + try { + const locationId = this.config.locationId; + const params: any = {}; + if (skip !== undefined) params.skip = skip.toString(); + if (limit !== undefined) params.limit = limit.toString(); + if (includeUsers !== undefined) params.includeUsers = includeUsers.toString(); + if (userId) params.userId = userId; + + const response: AxiosResponse = await this.axiosInstance.get( + `/social-media-posting/${locationId}/csv`, + { params } + ); + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Set Accounts for CSV Import + */ + async setSocialCSVAccounts(accountsData: GHLSetAccountsRequest): Promise> { + try { + const locationId = this.config.locationId; + const response: AxiosResponse = await this.axiosInstance.post( + `/social-media-posting/${locationId}/set-accounts`, + accountsData + ); + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get CSV Posts + */ + async getSocialCSVPosts(csvId: string, skip?: number, limit?: number): Promise> { + try { + const locationId = this.config.locationId; + const params: any = {}; + if (skip !== undefined) params.skip = skip.toString(); + if (limit !== undefined) params.limit = limit.toString(); + + const response: AxiosResponse = await this.axiosInstance.get( + `/social-media-posting/${locationId}/csv/${csvId}`, + { params } + ); + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Start CSV Finalization + */ + async finalizeSocialCSV(csvId: string, finalizeData: GHLCSVFinalizeRequest): Promise> { + try { + const locationId = this.config.locationId; + const response: AxiosResponse = await this.axiosInstance.patch( + `/social-media-posting/${locationId}/csv/${csvId}`, + finalizeData + ); + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Delete CSV Import + */ + async deleteSocialCSV(csvId: string): Promise> { + try { + const locationId = this.config.locationId; + const response: AxiosResponse = await this.axiosInstance.delete( + `/social-media-posting/${locationId}/csv/${csvId}` + ); + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Delete CSV Post + */ + async deleteSocialCSVPost(csvId: string, postId: string): Promise> { + try { + const locationId = this.config.locationId; + const response: AxiosResponse = await this.axiosInstance.delete( + `/social-media-posting/${locationId}/csv/${csvId}/post/${postId}` + ); + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + // ===== CATEGORIES & TAGS ===== + + /** + * Get Social Media Categories + */ + async getSocialCategories(searchText?: string, limit?: number, skip?: number): Promise> { + // TODO: Implement this method properly + throw new Error('Method not yet implemented'); + } + + // TODO: Implement remaining social media API methods + async getSocialCategory(categoryId: string): Promise> { + throw new Error('Method not yet implemented'); + } + + async getSocialTags(searchText?: string, limit?: number, skip?: number): Promise> { + throw new Error('Method not yet implemented'); + } + + async getSocialTagsByIds(tagData: GHLGetTagsByIdsRequest): Promise> { + throw new Error('Method not yet implemented'); + } + + async startSocialOAuth(platform: GHLSocialPlatform, userId: string, page?: string, reconnect?: boolean): Promise> { + throw new Error('Method not yet implemented'); + } + + async getGoogleBusinessLocations(accountId: string): Promise> { + throw new Error('Method not yet implemented'); + } + + async setGoogleBusinessLocations(accountId: string, locationData: GHLAttachGMBLocationRequest): Promise> { + throw new Error('Method not yet implemented'); + } + + async getFacebookPages(accountId: string): Promise> { + throw new Error('Method not yet implemented'); + } + + async attachFacebookPages(accountId: string, pageData: GHLAttachFBAccountRequest): Promise> { + throw new Error('Method not yet implemented'); + } + + async getInstagramAccounts(accountId: string): Promise> { + throw new Error('Method not yet implemented'); + } + + async attachInstagramAccounts(accountId: string, accountData: GHLAttachIGAccountRequest): Promise> { + throw new Error('Method not yet implemented'); + } + + async getLinkedInAccounts(accountId: string): Promise> { + throw new Error('Method not yet implemented'); + } + + async attachLinkedInAccounts(accountId: string, accountData: GHLAttachLinkedInAccountRequest): Promise> { + throw new Error('Method not yet implemented'); + } + + async getTwitterProfile(accountId: string): Promise> { + throw new Error('Method not yet implemented'); + } + + async attachTwitterProfile(accountId: string, profileData: GHLAttachTwitterAccountRequest): Promise> { + throw new Error('Method not yet implemented'); + } + + async getTikTokProfile(accountId: string): Promise> { + throw new Error('Method not yet implemented'); + } + + async attachTikTokProfile(accountId: string, profileData: GHLAttachTikTokAccountRequest): Promise> { + throw new Error('Method not yet implemented'); + } + + async getTikTokBusinessProfile(accountId: string): Promise> { + throw new Error('Method not yet implemented'); + } + + // ===== MISSING CALENDAR GROUPS MANAGEMENT METHODS ===== + + /** + * Validate calendar group slug + * GET /calendars/groups/slug/validate + */ + async validateCalendarGroupSlug(slug: string, locationId?: string): Promise> { + try { + const params = { + locationId: locationId || this.config.locationId, + slug + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/calendars/groups/slug/validate', + { params } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update calendar group by ID + * PUT /calendars/groups/{groupId} + */ + async updateCalendarGroup(groupId: string, updateData: GHLUpdateCalendarGroupRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.put( + `/calendars/groups/${groupId}`, + updateData + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Delete calendar group by ID + * DELETE /calendars/groups/{groupId} + */ + async deleteCalendarGroup(groupId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.delete( + `/calendars/groups/${groupId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Disable calendar group + * POST /calendars/groups/{groupId}/status + */ + async disableCalendarGroup(groupId: string, isActive: boolean): Promise> { + try { + const payload: GHLGroupStatusUpdateRequest = { isActive }; + + const response: AxiosResponse = await this.axiosInstance.post( + `/calendars/groups/${groupId}/status`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + // ===== APPOINTMENT NOTES METHODS ===== + + /** + * Get appointment notes + * GET /calendars/events/appointments/{appointmentId}/notes + */ + async getAppointmentNotes(appointmentId: string, limit = 10, offset = 0): Promise> { + try { + const params = { limit, offset }; + + const response: AxiosResponse = await this.axiosInstance.get( + `/calendars/events/appointments/${appointmentId}/notes`, + { params } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Create appointment note + * POST /calendars/events/appointments/{appointmentId}/notes + */ + async createAppointmentNote(appointmentId: string, noteData: GHLCreateAppointmentNoteRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.post( + `/calendars/events/appointments/${appointmentId}/notes`, + noteData + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update appointment note + * PUT /calendars/events/appointments/{appointmentId}/notes/{noteId} + */ + async updateAppointmentNote(appointmentId: string, noteId: string, updateData: GHLUpdateAppointmentNoteRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.put( + `/calendars/events/appointments/${appointmentId}/notes/${noteId}`, + updateData + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Delete appointment note + * DELETE /calendars/events/appointments/{appointmentId}/notes/{noteId} + */ + async deleteAppointmentNote(appointmentId: string, noteId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.delete( + `/calendars/events/appointments/${appointmentId}/notes/${noteId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + // ===== CALENDAR RESOURCES METHODS ===== + + /** + * Get calendar resources + * GET /calendars/resources/{resourceType} + */ + async getCalendarResources(resourceType: 'equipments' | 'rooms', limit = 20, skip = 0, locationId?: string): Promise> { + try { + const params = { + locationId: locationId || this.config.locationId, + limit, + skip + }; + + const response: AxiosResponse = await this.axiosInstance.get( + `/calendars/resources/${resourceType}`, + { params } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Create calendar resource + * POST /calendars/resources/{resourceType} + */ + async createCalendarResource(resourceType: 'equipments' | 'rooms', resourceData: GHLCreateCalendarResourceRequest): Promise> { + try { + const payload = { + ...resourceData, + locationId: resourceData.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.post( + `/calendars/resources/${resourceType}`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get calendar resource by ID + * GET /calendars/resources/{resourceType}/{resourceId} + */ + async getCalendarResource(resourceType: 'equipments' | 'rooms', resourceId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.get( + `/calendars/resources/${resourceType}/${resourceId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update calendar resource + * PUT /calendars/resources/{resourceType}/{resourceId} + */ + async updateCalendarResource(resourceType: 'equipments' | 'rooms', resourceId: string, updateData: GHLUpdateCalendarResourceRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.put( + `/calendars/resources/${resourceType}/${resourceId}`, + updateData + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Delete calendar resource + * DELETE /calendars/resources/{resourceType}/{resourceId} + */ + async deleteCalendarResource(resourceType: 'equipments' | 'rooms', resourceId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.delete( + `/calendars/resources/${resourceType}/${resourceId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + // ===== CALENDAR NOTIFICATIONS METHODS ===== + + /** + * Get calendar notifications + * GET /calendars/{calendarId}/notifications + */ + async getCalendarNotifications(calendarId: string, queryParams?: GHLGetCalendarNotificationsRequest): Promise> { + try { + const params = { + ...queryParams + }; + + const response: AxiosResponse = await this.axiosInstance.get( + `/calendars/${calendarId}/notifications`, + { params } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Create calendar notifications + * POST /calendars/{calendarId}/notifications + */ + async createCalendarNotifications(calendarId: string, notifications: GHLCreateCalendarNotificationRequest[]): Promise> { + try { + const payload = { notifications }; + + const response: AxiosResponse = await this.axiosInstance.post( + `/calendars/${calendarId}/notifications`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get calendar notification by ID + * GET /calendars/{calendarId}/notifications/{notificationId} + */ + async getCalendarNotification(calendarId: string, notificationId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.get( + `/calendars/${calendarId}/notifications/${notificationId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update calendar notification + * PUT /calendars/{calendarId}/notifications/{notificationId} + */ + async updateCalendarNotification(calendarId: string, notificationId: string, updateData: GHLUpdateCalendarNotificationRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.put( + `/calendars/${calendarId}/notifications/${notificationId}`, + updateData + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Delete calendar notification + * DELETE /calendars/{calendarId}/notifications/{notificationId} + */ + async deleteCalendarNotification(calendarId: string, notificationId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.delete( + `/calendars/${calendarId}/notifications/${notificationId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get blocked slots by location + * GET /calendars/blocked-slots + */ + async getBlockedSlotsByLocation(slotParams: GHLGetBlockedSlotsRequest): Promise> { + try { + const params = new URLSearchParams({ + locationId: slotParams.locationId, + startTime: slotParams.startTime, + endTime: slotParams.endTime, + ...(slotParams.userId && { userId: slotParams.userId }), + ...(slotParams.calendarId && { calendarId: slotParams.calendarId }), + ...(slotParams.groupId && { groupId: slotParams.groupId }) + }); + + const response: AxiosResponse = await this.axiosInstance.get( + `/calendars/blocked-slots?${params}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Create a new block slot + * POST /calendars/blocked-slots + */ + async createBlockSlot(blockSlotData: GHLCreateBlockSlotRequest): Promise> { + try { + const payload = { + ...blockSlotData, + locationId: blockSlotData.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/calendars/blocked-slots', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + // ===== MEDIA LIBRARY API METHODS ===== + + /** + * Get list of files and folders from media library + * GET /medias/files + */ + async getMediaFiles(params: GHLGetMediaFilesRequest): Promise> { + try { + const queryParams = new URLSearchParams({ + sortBy: params.sortBy, + sortOrder: params.sortOrder, + altType: params.altType, + altId: params.altId, + ...(params.offset !== undefined && { offset: params.offset.toString() }), + ...(params.limit !== undefined && { limit: params.limit.toString() }), + ...(params.type && { type: params.type }), + ...(params.query && { query: params.query }), + ...(params.parentId && { parentId: params.parentId }) + }); + + const response: AxiosResponse = await this.axiosInstance.get( + `/medias/files?${queryParams}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Upload file to media library + * POST /medias/upload-file + */ + async uploadMediaFile(uploadData: GHLUploadMediaFileRequest): Promise> { + try { + const formData = new FormData(); + + // Handle file upload (either direct file or hosted file URL) + if (uploadData.hosted && uploadData.fileUrl) { + formData.append('hosted', 'true'); + formData.append('fileUrl', uploadData.fileUrl); + } else if (uploadData.file) { + formData.append('hosted', 'false'); + formData.append('file', uploadData.file); + } else { + throw new Error('Either file or fileUrl (with hosted=true) must be provided'); + } + + // Add optional fields + if (uploadData.name) { + formData.append('name', uploadData.name); + } + if (uploadData.parentId) { + formData.append('parentId', uploadData.parentId); + } + + const response: AxiosResponse = await this.axiosInstance.post( + '/medias/upload-file', + formData, + { + headers: { + 'Content-Type': 'multipart/form-data', + } + } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Delete file or folder from media library + * DELETE /medias/{id} + */ + async deleteMediaFile(deleteParams: GHLDeleteMediaRequest): Promise> { + try { + const queryParams = new URLSearchParams({ + altType: deleteParams.altType, + altId: deleteParams.altId + }); + + const response: AxiosResponse = await this.axiosInstance.delete( + `/medias/${deleteParams.id}?${queryParams}` + ); + + return this.wrapResponse({ success: true, message: 'Media file deleted successfully' }); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + // ===== CUSTOM OBJECTS API METHODS ===== + + /** + * Get all objects for a location + * GET /objects/ + */ + async getObjectsByLocation(locationId?: string): Promise> { + try { + const params = { + locationId: locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/objects/', + { params } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Create custom object schema + * POST /objects/ + */ + async createObjectSchema(schemaData: GHLCreateObjectSchemaRequest): Promise> { + try { + const payload = { + ...schemaData, + locationId: schemaData.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/objects/', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get object schema by key/id + * GET /objects/{key} + */ + async getObjectSchema(params: GHLGetObjectSchemaRequest): Promise> { + try { + const queryParams = { + locationId: params.locationId || this.config.locationId, + ...(params.fetchProperties !== undefined && { fetchProperties: params.fetchProperties.toString() }) + }; + + const response: AxiosResponse = await this.axiosInstance.get( + `/objects/${params.key}`, + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update object schema by key/id + * PUT /objects/{key} + */ + async updateObjectSchema(key: string, updateData: GHLUpdateObjectSchemaRequest): Promise> { + try { + const payload = { + ...updateData, + locationId: updateData.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.put( + `/objects/${key}`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Create object record + * POST /objects/{schemaKey}/records + */ + async createObjectRecord(schemaKey: string, recordData: GHLCreateObjectRecordRequest): Promise> { + try { + const payload = { + ...recordData, + locationId: recordData.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.post( + `/objects/${schemaKey}/records`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get object record by id + * GET /objects/{schemaKey}/records/{id} + */ + async getObjectRecord(schemaKey: string, recordId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.get( + `/objects/${schemaKey}/records/${recordId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update object record + * PUT /objects/{schemaKey}/records/{id} + */ + async updateObjectRecord(schemaKey: string, recordId: string, updateData: GHLUpdateObjectRecordRequest): Promise> { + try { + const queryParams = { + locationId: updateData.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.put( + `/objects/${schemaKey}/records/${recordId}`, + updateData, + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Delete object record + * DELETE /objects/{schemaKey}/records/{id} + */ + async deleteObjectRecord(schemaKey: string, recordId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.delete( + `/objects/${schemaKey}/records/${recordId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Search object records + * POST /objects/{schemaKey}/records/search + */ + async searchObjectRecords(schemaKey: string, searchData: GHLSearchObjectRecordsRequest): Promise> { + try { + const payload = { + ...searchData, + locationId: searchData.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.post( + `/objects/${schemaKey}/records/search`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + // ===== ASSOCIATIONS API METHODS ===== + + /** + * Get all associations for a location + * GET /associations/ + */ + async getAssociations(params: GHLGetAssociationsRequest): Promise> { + try { + const queryParams = { + locationId: params.locationId || this.config.locationId, + skip: params.skip.toString(), + limit: params.limit.toString() + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/associations/', + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Create association + * POST /associations/ + */ + async createAssociation(associationData: GHLCreateAssociationRequest): Promise> { + try { + const payload = { + ...associationData, + locationId: associationData.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/associations/', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get association by ID + * GET /associations/{associationId} + */ + async getAssociationById(associationId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.get( + `/associations/${associationId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update association + * PUT /associations/{associationId} + */ + async updateAssociation(associationId: string, updateData: GHLUpdateAssociationRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.put( + `/associations/${associationId}`, + updateData + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Delete association + * DELETE /associations/{associationId} + */ + async deleteAssociation(associationId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.delete( + `/associations/${associationId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get association by key name + * GET /associations/key/{key_name} + */ + async getAssociationByKey(params: GHLGetAssociationByKeyRequest): Promise> { + try { + const queryParams = { + locationId: params.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.get( + `/associations/key/${params.keyName}`, + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get association by object key + * GET /associations/objectKey/{objectKey} + */ + async getAssociationByObjectKey(params: GHLGetAssociationByObjectKeyRequest): Promise> { + try { + const queryParams = params.locationId ? { + locationId: params.locationId + } : {}; + + const response: AxiosResponse = await this.axiosInstance.get( + `/associations/objectKey/${params.objectKey}`, + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Create relation between entities + * POST /associations/relations + */ + async createRelation(relationData: GHLCreateRelationRequest): Promise> { + try { + const payload = { + ...relationData, + locationId: relationData.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/associations/relations', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get relations by record ID + * GET /associations/relations/{recordId} + */ + async getRelationsByRecord(params: GHLGetRelationsByRecordRequest): Promise> { + try { + const queryParams = { + locationId: params.locationId || this.config.locationId, + skip: params.skip.toString(), + limit: params.limit.toString(), + ...(params.associationIds && { associationIds: params.associationIds }) + }; + + const response: AxiosResponse = await this.axiosInstance.get( + `/associations/relations/${params.recordId}`, + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Delete relation + * DELETE /associations/relations/{relationId} + */ + async deleteRelation(params: GHLDeleteRelationRequest): Promise> { + try { + const queryParams = { + locationId: params.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.delete( + `/associations/relations/${params.relationId}`, + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + // ===== CUSTOM FIELDS V2 API METHODS ===== + + /** + * Get custom field or folder by ID + * GET /custom-fields/{id} + */ + async getCustomFieldV2ById(id: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.get( + `/custom-fields/${id}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Create custom field + * POST /custom-fields/ + */ + async createCustomFieldV2(fieldData: GHLV2CreateCustomFieldRequest): Promise> { + try { + const payload = { + ...fieldData, + locationId: fieldData.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/custom-fields/', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update custom field by ID + * PUT /custom-fields/{id} + */ + async updateCustomFieldV2(id: string, fieldData: GHLV2UpdateCustomFieldRequest): Promise> { + try { + const payload = { + ...fieldData, + locationId: fieldData.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.put( + `/custom-fields/${id}`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Delete custom field by ID + * DELETE /custom-fields/{id} + */ + async deleteCustomFieldV2(id: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.delete( + `/custom-fields/${id}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get custom fields by object key + * GET /custom-fields/object-key/{objectKey} + */ + async getCustomFieldsV2ByObjectKey(params: GHLV2GetCustomFieldsByObjectKeyRequest): Promise> { + try { + const queryParams = { + locationId: params.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.get( + `/custom-fields/object-key/${params.objectKey}`, + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Create custom field folder + * POST /custom-fields/folder + */ + async createCustomFieldV2Folder(folderData: GHLV2CreateCustomFieldFolderRequest): Promise> { + try { + const payload = { + ...folderData, + locationId: folderData.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/custom-fields/folder', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Update custom field folder name + * PUT /custom-fields/folder/{id} + */ + async updateCustomFieldV2Folder(id: string, folderData: GHLV2UpdateCustomFieldFolderRequest): Promise> { + try { + const payload = { + ...folderData, + locationId: folderData.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.put( + `/custom-fields/folder/${id}`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Delete custom field folder + * DELETE /custom-fields/folder/{id} + */ + async deleteCustomFieldV2Folder(params: GHLV2DeleteCustomFieldFolderRequest): Promise> { + try { + const queryParams = { + locationId: params.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.delete( + `/custom-fields/folder/${params.id}`, + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + // ===== WORKFLOWS API METHODS ===== + + /** + * Get all workflows for a location + * GET /workflows/ + */ + async getWorkflows(request: GHLGetWorkflowsRequest): Promise> { + try { + const queryParams = { + locationId: request.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/workflows/', + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + // ===== SURVEYS API METHODS ===== + + /** + * Get all surveys for a location + * GET /surveys/ + */ + async getSurveys(request: GHLGetSurveysRequest): Promise> { + try { + const queryParams: Record = { + locationId: request.locationId || this.config.locationId + }; + + if (request.skip !== undefined) { + queryParams.skip = request.skip.toString(); + } + if (request.limit !== undefined) { + queryParams.limit = request.limit.toString(); + } + if (request.type) { + queryParams.type = request.type; + } + + const response: AxiosResponse = await this.axiosInstance.get( + '/surveys/', + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw this.handleApiError(error as AxiosError); + } + } + + /** + * Get survey submissions with filtering and pagination + * GET /surveys/submissions + */ + async getSurveySubmissions(request: GHLGetSurveySubmissionsRequest): Promise> { + try { + const locationId = request.locationId || this.config.locationId; + + const params = new URLSearchParams(); + if (request.page) params.append('page', request.page.toString()); + if (request.limit) params.append('limit', request.limit.toString()); + if (request.surveyId) params.append('surveyId', request.surveyId); + if (request.q) params.append('q', request.q); + if (request.startAt) params.append('startAt', request.startAt); + if (request.endAt) params.append('endAt', request.endAt); + + const response: AxiosResponse = await this.axiosInstance.get( + `/locations/${locationId}/surveys/submissions?${params.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + // ===== STORE API METHODS ===== + + /** + * SHIPPING ZONES API METHODS + */ + + /** + * Create a new shipping zone + * POST /store/shipping-zone + */ + async createShippingZone(zoneData: GHLCreateShippingZoneRequest): Promise> { + try { + const payload = { + ...zoneData, + altId: zoneData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/store/shipping-zone', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * List all shipping zones + * GET /store/shipping-zone + */ + async listShippingZones(params: GHLGetShippingZonesRequest): Promise> { + try { + const altId = params.altId || this.config.locationId; + const queryParams = new URLSearchParams({ + altId, + altType: 'location' + }); + + if (params.limit) queryParams.append('limit', params.limit.toString()); + if (params.offset) queryParams.append('offset', params.offset.toString()); + if (params.withShippingRate !== undefined) queryParams.append('withShippingRate', params.withShippingRate.toString()); + + const response: AxiosResponse = await this.axiosInstance.get( + `/store/shipping-zone?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get a specific shipping zone by ID + * GET /store/shipping-zone/{shippingZoneId} + */ + async getShippingZone(shippingZoneId: string, params: Omit): Promise> { + try { + const altId = params.altId || this.config.locationId; + const queryParams = new URLSearchParams({ + altId, + altType: 'location' + }); + + if (params.withShippingRate !== undefined) queryParams.append('withShippingRate', params.withShippingRate.toString()); + + const response: AxiosResponse = await this.axiosInstance.get( + `/store/shipping-zone/${shippingZoneId}?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update a shipping zone + * PUT /store/shipping-zone/{shippingZoneId} + */ + async updateShippingZone(shippingZoneId: string, updateData: GHLUpdateShippingZoneRequest): Promise> { + try { + const payload = { + ...updateData, + altId: updateData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.put( + `/store/shipping-zone/${shippingZoneId}`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Delete a shipping zone + * DELETE /store/shipping-zone/{shippingZoneId} + */ + async deleteShippingZone(shippingZoneId: string, params: GHLDeleteShippingZoneRequest): Promise> { + try { + const altId = params.altId || this.config.locationId; + const queryParams = new URLSearchParams({ + altId, + altType: 'location' + }); + + const response: AxiosResponse = await this.axiosInstance.delete( + `/store/shipping-zone/${shippingZoneId}?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * SHIPPING RATES API METHODS + */ + + /** + * Get available shipping rates for an order + * POST /store/shipping-zone/shipping-rates + */ + async getAvailableShippingRates(rateData: GHLGetAvailableShippingRatesRequest): Promise> { + try { + const payload = { + ...rateData, + altId: rateData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/store/shipping-zone/shipping-rates', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Create a new shipping rate for a zone + * POST /store/shipping-zone/{shippingZoneId}/shipping-rate + */ + async createShippingRate(shippingZoneId: string, rateData: GHLCreateShippingRateRequest): Promise> { + try { + const payload = { + ...rateData, + altId: rateData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + `/store/shipping-zone/${shippingZoneId}/shipping-rate`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * List shipping rates for a zone + * GET /store/shipping-zone/{shippingZoneId}/shipping-rate + */ + async listShippingRates(shippingZoneId: string, params: GHLGetShippingRatesRequest): Promise> { + try { + const altId = params.altId || this.config.locationId; + const queryParams = new URLSearchParams({ + altId, + altType: 'location' + }); + + if (params.limit) queryParams.append('limit', params.limit.toString()); + if (params.offset) queryParams.append('offset', params.offset.toString()); + + const response: AxiosResponse = await this.axiosInstance.get( + `/store/shipping-zone/${shippingZoneId}/shipping-rate?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get a specific shipping rate + * GET /store/shipping-zone/{shippingZoneId}/shipping-rate/{shippingRateId} + */ + async getShippingRate(shippingZoneId: string, shippingRateId: string, params: Omit): Promise> { + try { + const altId = params.altId || this.config.locationId; + const queryParams = new URLSearchParams({ + altId, + altType: 'location' + }); + + const response: AxiosResponse = await this.axiosInstance.get( + `/store/shipping-zone/${shippingZoneId}/shipping-rate/${shippingRateId}?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update a shipping rate + * PUT /store/shipping-zone/{shippingZoneId}/shipping-rate/{shippingRateId} + */ + async updateShippingRate(shippingZoneId: string, shippingRateId: string, updateData: GHLUpdateShippingRateRequest): Promise> { + try { + const payload = { + ...updateData, + altId: updateData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.put( + `/store/shipping-zone/${shippingZoneId}/shipping-rate/${shippingRateId}`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Delete a shipping rate + * DELETE /store/shipping-zone/{shippingZoneId}/shipping-rate/{shippingRateId} + */ + async deleteShippingRate(shippingZoneId: string, shippingRateId: string, params: GHLDeleteShippingRateRequest): Promise> { + try { + const altId = params.altId || this.config.locationId; + const queryParams = new URLSearchParams({ + altId, + altType: 'location' + }); + + const response: AxiosResponse = await this.axiosInstance.delete( + `/store/shipping-zone/${shippingZoneId}/shipping-rate/${shippingRateId}?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * SHIPPING CARRIERS API METHODS + */ + + /** + * Create a new shipping carrier + * POST /store/shipping-carrier + */ + async createShippingCarrier(carrierData: GHLCreateShippingCarrierRequest): Promise> { + try { + const payload = { + ...carrierData, + altId: carrierData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/store/shipping-carrier', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * List all shipping carriers + * GET /store/shipping-carrier + */ + async listShippingCarriers(params: GHLGetShippingCarriersRequest): Promise> { + try { + const altId = params.altId || this.config.locationId; + const queryParams = new URLSearchParams({ + altId, + altType: 'location' + }); + + const response: AxiosResponse = await this.axiosInstance.get( + `/store/shipping-carrier?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get a specific shipping carrier by ID + * GET /store/shipping-carrier/{shippingCarrierId} + */ + async getShippingCarrier(shippingCarrierId: string, params: GHLGetShippingCarriersRequest): Promise> { + try { + const altId = params.altId || this.config.locationId; + const queryParams = new URLSearchParams({ + altId, + altType: 'location' + }); + + const response: AxiosResponse = await this.axiosInstance.get( + `/store/shipping-carrier/${shippingCarrierId}?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update a shipping carrier + * PUT /store/shipping-carrier/{shippingCarrierId} + */ + async updateShippingCarrier(shippingCarrierId: string, updateData: GHLUpdateShippingCarrierRequest): Promise> { + try { + const payload = { + ...updateData, + altId: updateData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.put( + `/store/shipping-carrier/${shippingCarrierId}`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Delete a shipping carrier + * DELETE /store/shipping-carrier/{shippingCarrierId} + */ + async deleteShippingCarrier(shippingCarrierId: string, params: GHLDeleteShippingCarrierRequest): Promise> { + try { + const altId = params.altId || this.config.locationId; + const queryParams = new URLSearchParams({ + altId, + altType: 'location' + }); + + const response: AxiosResponse = await this.axiosInstance.delete( + `/store/shipping-carrier/${shippingCarrierId}?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * STORE SETTINGS API METHODS + */ + + /** + * Create or update store settings + * POST /store/store-setting + */ + async createStoreSetting(settingData: GHLCreateStoreSettingRequest): Promise> { + try { + const payload = { + ...settingData, + altId: settingData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/store/store-setting', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get store settings + * GET /store/store-setting + */ + async getStoreSetting(params: GHLGetStoreSettingRequest): Promise> { + try { + const altId = params.altId || this.config.locationId; + const queryParams = new URLSearchParams({ + altId, + altType: 'location' + }); + + const response: AxiosResponse = await this.axiosInstance.get( + `/store/store-setting?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * PRODUCTS API METHODS + */ + + /** + * Create a new product + * POST /products/ + */ + async createProduct(productData: GHLCreateProductRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.post( + '/products/', + productData + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update a product by ID + * PUT /products/{productId} + */ + async updateProduct(productId: string, updateData: GHLUpdateProductRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.put( + `/products/${productId}`, + updateData + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get a product by ID + * GET /products/{productId} + */ + async getProduct(productId: string, locationId?: string): Promise> { + try { + const queryParams = new URLSearchParams({ + locationId: locationId || this.config.locationId + }); + + const response: AxiosResponse = await this.axiosInstance.get( + `/products/${productId}?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * List products + * GET /products/ + */ + async listProducts(params: GHLListProductsRequest): Promise> { + try { + const queryParams = new URLSearchParams({ + locationId: params.locationId || this.config.locationId + }); + + if (params.limit) queryParams.append('limit', params.limit.toString()); + if (params.offset) queryParams.append('offset', params.offset.toString()); + if (params.search) queryParams.append('search', params.search); + if (params.collectionIds?.length) queryParams.append('collectionIds', params.collectionIds.join(',')); + if (params.collectionSlug) queryParams.append('collectionSlug', params.collectionSlug); + if (params.expand?.length) params.expand.forEach(item => queryParams.append('expand', item)); + if (params.productIds?.length) params.productIds.forEach(id => queryParams.append('productIds', id)); + if (params.storeId) queryParams.append('storeId', params.storeId); + if (params.includedInStore !== undefined) queryParams.append('includedInStore', params.includedInStore.toString()); + if (params.availableInStore !== undefined) queryParams.append('availableInStore', params.availableInStore.toString()); + if (params.sortOrder) queryParams.append('sortOrder', params.sortOrder); + + const response: AxiosResponse = await this.axiosInstance.get( + `/products/?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Delete a product by ID + * DELETE /products/{productId} + */ + async deleteProduct(productId: string, locationId?: string): Promise> { + try { + const queryParams = new URLSearchParams({ + locationId: locationId || this.config.locationId + }); + + const response: AxiosResponse = await this.axiosInstance.delete( + `/products/${productId}?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Bulk update products + * POST /products/bulk-update + */ + async bulkUpdateProducts(updateData: GHLBulkUpdateRequest): Promise> { + try { + const payload = { + ...updateData, + altId: updateData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/products/bulk-update', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Create a price for a product + * POST /products/{productId}/price + */ + async createPrice(productId: string, priceData: GHLCreatePriceRequest): Promise> { + try { + const payload = { + ...priceData, + locationId: priceData.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.post( + `/products/${productId}/price`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update a price by ID + * PUT /products/{productId}/price/{priceId} + */ + async updatePrice(productId: string, priceId: string, updateData: GHLUpdatePriceRequest): Promise> { + try { + const payload = { + ...updateData, + locationId: updateData.locationId || this.config.locationId + }; + + const response: AxiosResponse = await this.axiosInstance.put( + `/products/${productId}/price/${priceId}`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get a price by ID + * GET /products/{productId}/price/{priceId} + */ + async getPrice(productId: string, priceId: string, locationId?: string): Promise> { + try { + const queryParams = new URLSearchParams({ + locationId: locationId || this.config.locationId + }); + + const response: AxiosResponse = await this.axiosInstance.get( + `/products/${productId}/price/${priceId}?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * List prices for a product + * GET /products/{productId}/price + */ + async listPrices(productId: string, params: GHLListPricesRequest): Promise> { + try { + const queryParams = new URLSearchParams({ + locationId: params.locationId || this.config.locationId + }); + + if (params.limit) queryParams.append('limit', params.limit.toString()); + if (params.offset) queryParams.append('offset', params.offset.toString()); + if (params.ids) queryParams.append('ids', params.ids); + + const response: AxiosResponse = await this.axiosInstance.get( + `/products/${productId}/price?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Delete a price by ID + * DELETE /products/{productId}/price/{priceId} + */ + async deletePrice(productId: string, priceId: string, locationId?: string): Promise> { + try { + const queryParams = new URLSearchParams({ + locationId: locationId || this.config.locationId + }); + + const response: AxiosResponse = await this.axiosInstance.delete( + `/products/${productId}/price/${priceId}?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * List inventory + * GET /products/inventory + */ + async listInventory(params: GHLListInventoryRequest): Promise> { + try { + const queryParams = new URLSearchParams({ + altId: params.altId || this.config.locationId, + altType: 'location' + }); + + if (params.limit) queryParams.append('limit', params.limit.toString()); + if (params.offset) queryParams.append('offset', params.offset.toString()); + if (params.search) queryParams.append('search', params.search); + + const response: AxiosResponse = await this.axiosInstance.get( + `/products/inventory?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update inventory + * POST /products/inventory + */ + async updateInventory(updateData: GHLUpdateInventoryRequest): Promise> { + try { + const payload = { + ...updateData, + altId: updateData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/products/inventory', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get product store stats + * GET /products/store/{storeId}/stats + */ + async getProductStoreStats(storeId: string, params: GHLGetProductStoreStatsRequest): Promise> { + try { + const queryParams = new URLSearchParams({ + altId: params.altId || this.config.locationId, + altType: 'location' + }); + + if (params.search) queryParams.append('search', params.search); + if (params.collectionIds) queryParams.append('collectionIds', params.collectionIds); + + const response: AxiosResponse = await this.axiosInstance.get( + `/products/store/${storeId}/stats?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update product store status + * POST /products/store/{storeId} + */ + async updateProductStore(storeId: string, updateData: GHLUpdateProductStoreRequest): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.post( + `/products/store/${storeId}`, + updateData + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Create a product collection + * POST /products/collections + */ + async createProductCollection(collectionData: GHLCreateProductCollectionRequest): Promise> { + try { + const payload = { + ...collectionData, + altId: collectionData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/products/collections', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update a product collection + * PUT /products/collections/{collectionId} + */ + async updateProductCollection(collectionId: string, updateData: GHLUpdateProductCollectionRequest): Promise> { + try { + const payload = { + ...updateData, + altId: updateData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.put( + `/products/collections/${collectionId}`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get a product collection by ID + * GET /products/collections/{collectionId} + */ + async getProductCollection(collectionId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.get( + `/products/collections/${collectionId}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * List product collections + * GET /products/collections + */ + async listProductCollections(params: GHLListProductCollectionsRequest): Promise> { + try { + const queryParams = new URLSearchParams({ + altId: params.altId || this.config.locationId, + altType: 'location' + }); + + if (params.limit) queryParams.append('limit', params.limit.toString()); + if (params.offset) queryParams.append('offset', params.offset.toString()); + if (params.collectionIds) queryParams.append('collectionIds', params.collectionIds); + if (params.name) queryParams.append('name', params.name); + + const response: AxiosResponse = await this.axiosInstance.get( + `/products/collections?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Delete a product collection + * DELETE /products/collections/{collectionId} + */ + async deleteProductCollection(collectionId: string, params: GHLDeleteProductCollectionRequest): Promise> { + try { + const queryParams = new URLSearchParams({ + altId: params.altId || this.config.locationId, + altType: 'location' + }); + + const response: AxiosResponse = await this.axiosInstance.delete( + `/products/collections/${collectionId}?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * List product reviews + * GET /products/reviews + */ + async listProductReviews(params: GHLListProductReviewsRequest): Promise> { + try { + const queryParams = new URLSearchParams({ + altId: params.altId || this.config.locationId, + altType: 'location' + }); + + if (params.limit) queryParams.append('limit', params.limit.toString()); + if (params.offset) queryParams.append('offset', params.offset.toString()); + if (params.sortField) queryParams.append('sortField', params.sortField); + if (params.sortOrder) queryParams.append('sortOrder', params.sortOrder); + if (params.rating) queryParams.append('rating', params.rating.toString()); + if (params.startDate) queryParams.append('startDate', params.startDate); + if (params.endDate) queryParams.append('endDate', params.endDate); + if (params.productId) queryParams.append('productId', params.productId); + if (params.storeId) queryParams.append('storeId', params.storeId); + + const response: AxiosResponse = await this.axiosInstance.get( + `/products/reviews?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get reviews count + * GET /products/reviews/count + */ + async getReviewsCount(params: GHLGetReviewsCountRequest): Promise> { + try { + const queryParams = new URLSearchParams({ + altId: params.altId || this.config.locationId, + altType: 'location' + }); + + if (params.rating) queryParams.append('rating', params.rating.toString()); + if (params.startDate) queryParams.append('startDate', params.startDate); + if (params.endDate) queryParams.append('endDate', params.endDate); + if (params.productId) queryParams.append('productId', params.productId); + if (params.storeId) queryParams.append('storeId', params.storeId); + + const response: AxiosResponse = await this.axiosInstance.get( + `/products/reviews/count?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update a product review + * PUT /products/reviews/{reviewId} + */ + async updateProductReview(reviewId: string, updateData: GHLUpdateProductReviewRequest): Promise> { + try { + const payload = { + ...updateData, + altId: updateData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.put( + `/products/reviews/${reviewId}`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Delete a product review + * DELETE /products/reviews/{reviewId} + */ + async deleteProductReview(reviewId: string, params: GHLDeleteProductReviewRequest): Promise> { + try { + const queryParams = new URLSearchParams({ + altId: params.altId || this.config.locationId, + altType: 'location', + productId: params.productId + }); + + const response: AxiosResponse = await this.axiosInstance.delete( + `/products/reviews/${reviewId}?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Bulk update product reviews + * POST /products/reviews/bulk-update + */ + async bulkUpdateProductReviews(updateData: GHLBulkUpdateProductReviewsRequest): Promise> { + try { + const payload = { + ...updateData, + altId: updateData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/products/reviews/bulk-update', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * PAYMENTS API METHODS + */ + + /** + * Create white-label integration provider + * POST /payments/integrations/provider/whitelabel + */ + async createWhiteLabelIntegrationProvider(data: any): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.post( + '/payments/integrations/provider/whitelabel', + data + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * List white-label integration providers + * GET /payments/integrations/provider/whitelabel + */ + async listWhiteLabelIntegrationProviders(params: Record): Promise> { + try { + const queryParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + queryParams.append(key, value.toString()); + } + }); + + const response: AxiosResponse = await this.axiosInstance.get( + `/payments/integrations/provider/whitelabel?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * List orders + * GET /payments/orders + */ + async listOrders(params: Record): Promise> { + try { + const queryParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + queryParams.append(key, value.toString()); + } + }); + + const response: AxiosResponse = await this.axiosInstance.get( + `/payments/orders?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get order by ID + * GET /payments/orders/{orderId} + */ + async getOrderById(orderId: string, params: Record): Promise> { + try { + const queryParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null && key !== 'orderId') { + queryParams.append(key, value.toString()); + } + }); + + const response: AxiosResponse = await this.axiosInstance.get( + `/payments/orders/${orderId}?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Create order fulfillment + * POST /payments/orders/{orderId}/fulfillments + */ + async createOrderFulfillment(orderId: string, data: any): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.post( + `/payments/orders/${orderId}/fulfillments`, + data + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * List order fulfillments + * GET /payments/orders/{orderId}/fulfillments + */ + async listOrderFulfillments(orderId: string, params: Record): Promise> { + try { + const queryParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null && key !== 'orderId') { + queryParams.append(key, value.toString()); + } + }); + + const response: AxiosResponse = await this.axiosInstance.get( + `/payments/orders/${orderId}/fulfillments?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * List transactions + * GET /payments/transactions + */ + async listTransactions(params: Record): Promise> { + try { + const queryParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + queryParams.append(key, value.toString()); + } + }); + + const response: AxiosResponse = await this.axiosInstance.get( + `/payments/transactions?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get transaction by ID + * GET /payments/transactions/{transactionId} + */ + async getTransactionById(transactionId: string, params: Record): Promise> { + try { + const queryParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null && key !== 'transactionId') { + queryParams.append(key, value.toString()); + } + }); + + const response: AxiosResponse = await this.axiosInstance.get( + `/payments/transactions/${transactionId}?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * List subscriptions + * GET /payments/subscriptions + */ + async listSubscriptions(params: Record): Promise> { + try { + const queryParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + queryParams.append(key, value.toString()); + } + }); + + const response: AxiosResponse = await this.axiosInstance.get( + `/payments/subscriptions?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get subscription by ID + * GET /payments/subscriptions/{subscriptionId} + */ + async getSubscriptionById(subscriptionId: string, params: Record): Promise> { + try { + const queryParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null && key !== 'subscriptionId') { + queryParams.append(key, value.toString()); + } + }); + + const response: AxiosResponse = await this.axiosInstance.get( + `/payments/subscriptions/${subscriptionId}?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * List coupons + * GET /payments/coupon/list + */ + async listCoupons(params: Record): Promise> { + try { + const queryParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + queryParams.append(key, value.toString()); + } + }); + + const response: AxiosResponse = await this.axiosInstance.get( + `/payments/coupon/list?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Create coupon + * POST /payments/coupon + */ + async createCoupon(data: any): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.post( + '/payments/coupon', + data + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update coupon + * PUT /payments/coupon + */ + async updateCoupon(data: any): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.put( + '/payments/coupon', + data + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Delete coupon + * DELETE /payments/coupon + */ + async deleteCoupon(data: any): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.delete( + '/payments/coupon', + { data } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get coupon + * GET /payments/coupon + */ + async getCoupon(params: Record): Promise> { + try { + const queryParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + queryParams.append(key, value.toString()); + } + }); + + const response: AxiosResponse = await this.axiosInstance.get( + `/payments/coupon?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Create custom provider integration + * POST /payments/custom-provider/provider + */ + async createCustomProviderIntegration(locationId: string, data: any): Promise> { + try { + const queryParams = new URLSearchParams({ locationId }); + + const response: AxiosResponse = await this.axiosInstance.post( + `/payments/custom-provider/provider?${queryParams.toString()}`, + data + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Delete custom provider integration + * DELETE /payments/custom-provider/provider + */ + async deleteCustomProviderIntegration(locationId: string): Promise> { + try { + const queryParams = new URLSearchParams({ locationId }); + + const response: AxiosResponse = await this.axiosInstance.delete( + `/payments/custom-provider/provider?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get custom provider config + * GET /payments/custom-provider/connect + */ + async getCustomProviderConfig(locationId: string): Promise> { + try { + const queryParams = new URLSearchParams({ locationId }); + + const response: AxiosResponse = await this.axiosInstance.get( + `/payments/custom-provider/connect?${queryParams.toString()}` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Create custom provider config + * POST /payments/custom-provider/connect + */ + async createCustomProviderConfig(locationId: string, data: any): Promise> { + try { + const queryParams = new URLSearchParams({ locationId }); + + const response: AxiosResponse = await this.axiosInstance.post( + `/payments/custom-provider/connect?${queryParams.toString()}`, + data + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Disconnect custom provider config + * POST /payments/custom-provider/disconnect + */ + async disconnectCustomProviderConfig(locationId: string, data: any): Promise> { + try { + const queryParams = new URLSearchParams({ locationId }); + + const response: AxiosResponse = await this.axiosInstance.post( + `/payments/custom-provider/disconnect?${queryParams.toString()}`, + data + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + // ============================================================================= + // INVOICES API METHODS + // ============================================================================= + + /** + * Create invoice template + * POST /invoices/template + */ + async createInvoiceTemplate(templateData: CreateInvoiceTemplateDto): Promise> { + try { + const payload = { + ...templateData, + altId: templateData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/invoices/template', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * List invoice templates + * GET /invoices/template + */ + async listInvoiceTemplates(params?: { + altId?: string; + altType?: 'location'; + status?: string; + startAt?: string; + endAt?: string; + search?: string; + paymentMode?: 'default' | 'live' | 'test'; + limit: string; + offset: string; + }): Promise> { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' as const, + limit: params?.limit || '10', + offset: params?.offset || '0', + ...(params?.status && { status: params.status }), + ...(params?.startAt && { startAt: params.startAt }), + ...(params?.endAt && { endAt: params.endAt }), + ...(params?.search && { search: params.search }), + ...(params?.paymentMode && { paymentMode: params.paymentMode }) + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/invoices/template', + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get invoice template by ID + * GET /invoices/template/{templateId} + */ + async getInvoiceTemplate(templateId: string, params?: { + altId?: string; + altType?: 'location'; + }): Promise> { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.get( + `/invoices/template/${templateId}`, + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update invoice template + * PUT /invoices/template/{templateId} + */ + async updateInvoiceTemplate(templateId: string, templateData: UpdateInvoiceTemplateDto): Promise> { + try { + const payload = { + ...templateData, + altId: templateData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.put( + `/invoices/template/${templateId}`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Delete invoice template + * DELETE /invoices/template/{templateId} + */ + async deleteInvoiceTemplate(templateId: string, params?: { + altId?: string; + altType?: 'location'; + }): Promise> { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.delete( + `/invoices/template/${templateId}`, + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update invoice template late fees configuration + * PATCH /invoices/template/{templateId}/late-fees-configuration + */ + async updateInvoiceTemplateLateFeesConfiguration(templateId: string, configData: UpdateInvoiceLateFeesConfigurationDto): Promise> { + try { + const payload = { + ...configData, + altId: configData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.patch( + `/invoices/template/${templateId}/late-fees-configuration`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update invoice template payment methods configuration + * PATCH /invoices/template/{templateId}/payment-methods-configuration + */ + async updateInvoiceTemplatePaymentMethodsConfiguration(templateId: string, configData: UpdatePaymentMethodsConfigurationDto): Promise> { + try { + const payload = { + ...configData, + altId: configData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.patch( + `/invoices/template/${templateId}/payment-methods-configuration`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Create invoice schedule + * POST /invoices/schedule + */ + async createInvoiceSchedule(scheduleData: CreateInvoiceScheduleDto): Promise> { + try { + const payload = { + ...scheduleData, + altId: scheduleData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/invoices/schedule', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * List invoice schedules + * GET /invoices/schedule + */ + async listInvoiceSchedules(params?: { + altId?: string; + altType?: 'location'; + status?: string; + startAt?: string; + endAt?: string; + search?: string; + paymentMode?: 'default' | 'live' | 'test'; + limit: string; + offset: string; + }): Promise> { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' as const, + limit: params?.limit || '10', + offset: params?.offset || '0', + ...(params?.status && { status: params.status }), + ...(params?.startAt && { startAt: params.startAt }), + ...(params?.endAt && { endAt: params.endAt }), + ...(params?.search && { search: params.search }), + ...(params?.paymentMode && { paymentMode: params.paymentMode }) + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/invoices/schedule', + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get invoice schedule by ID + * GET /invoices/schedule/{scheduleId} + */ + async getInvoiceSchedule(scheduleId: string, params?: { + altId?: string; + altType?: 'location'; + }): Promise> { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.get( + `/invoices/schedule/${scheduleId}`, + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update invoice schedule + * PUT /invoices/schedule/{scheduleId} + */ + async updateInvoiceSchedule(scheduleId: string, scheduleData: UpdateInvoiceScheduleDto): Promise> { + try { + const payload = { + ...scheduleData, + altId: scheduleData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.put( + `/invoices/schedule/${scheduleId}`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Delete invoice schedule + * DELETE /invoices/schedule/{scheduleId} + */ + async deleteInvoiceSchedule(scheduleId: string, params?: { + altId?: string; + altType?: 'location'; + }): Promise> { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.delete( + `/invoices/schedule/${scheduleId}`, + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update and schedule recurring invoice + * POST /invoices/schedule/{scheduleId}/updateAndSchedule + */ + async updateAndScheduleInvoiceSchedule(scheduleId: string): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.post( + `/invoices/schedule/${scheduleId}/updateAndSchedule` + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Schedule an invoice schedule + * POST /invoices/schedule/{scheduleId}/schedule + */ + async scheduleInvoiceSchedule(scheduleId: string, scheduleData: ScheduleInvoiceScheduleDto): Promise> { + try { + const payload = { + ...scheduleData, + altId: scheduleData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + `/invoices/schedule/${scheduleId}/schedule`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Manage auto payment for schedule invoice + * POST /invoices/schedule/{scheduleId}/auto-payment + */ + async autoPaymentInvoiceSchedule(scheduleId: string, paymentData: AutoPaymentScheduleDto): Promise> { + try { + const payload = { + ...paymentData, + altId: paymentData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + `/invoices/schedule/${scheduleId}/auto-payment`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Cancel scheduled invoice + * POST /invoices/schedule/{scheduleId}/cancel + */ + async cancelInvoiceSchedule(scheduleId: string, cancelData: CancelInvoiceScheduleDto): Promise> { + try { + const payload = { + ...cancelData, + altId: cancelData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + `/invoices/schedule/${scheduleId}/cancel`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Create or update text2pay invoice + * POST /invoices/text2pay + */ + async text2PayInvoice(invoiceData: Text2PayDto): Promise> { + try { + const payload = { + ...invoiceData, + altId: invoiceData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/invoices/text2pay', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Generate invoice number + * GET /invoices/generate-invoice-number + */ + async generateInvoiceNumber(params?: { + altId?: string; + altType?: 'location'; + }): Promise> { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/invoices/generate-invoice-number', + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Get invoice by ID + * GET /invoices/{invoiceId} + */ + async getInvoice(invoiceId: string, params?: { + altId?: string; + altType?: 'location'; + }): Promise> { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.get( + `/invoices/${invoiceId}`, + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update invoice + * PUT /invoices/{invoiceId} + */ + async updateInvoice(invoiceId: string, invoiceData: UpdateInvoiceDto): Promise> { + try { + const payload = { + ...invoiceData, + altId: invoiceData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.put( + `/invoices/${invoiceId}`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Delete invoice + * DELETE /invoices/{invoiceId} + */ + async deleteInvoice(invoiceId: string, params?: { + altId?: string; + altType?: 'location'; + }): Promise> { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.delete( + `/invoices/${invoiceId}`, + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update invoice late fees configuration + * PATCH /invoices/{invoiceId}/late-fees-configuration + */ + async updateInvoiceLateFeesConfiguration(invoiceId: string, configData: UpdateInvoiceLateFeesConfigurationDto): Promise> { + try { + const payload = { + ...configData, + altId: configData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.patch( + `/invoices/${invoiceId}/late-fees-configuration`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Void invoice + * POST /invoices/{invoiceId}/void + */ + async voidInvoice(invoiceId: string, voidData: VoidInvoiceDto): Promise> { + try { + const payload = { + ...voidData, + altId: voidData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + `/invoices/${invoiceId}/void`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Send invoice + * POST /invoices/{invoiceId}/send + */ + async sendInvoice(invoiceId: string, sendData: SendInvoiceDto): Promise> { + try { + const payload = { + ...sendData, + altId: sendData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + `/invoices/${invoiceId}/send`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Record manual payment for invoice + * POST /invoices/{invoiceId}/record-payment + */ + async recordInvoicePayment(invoiceId: string, paymentData: RecordPaymentDto): Promise> { + try { + const payload = { + ...paymentData, + altId: paymentData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + `/invoices/${invoiceId}/record-payment`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update invoice last visited at + * PATCH /invoices/stats/last-visited-at + */ + async updateInvoiceLastVisitedAt(statsData: PatchInvoiceStatsLastViewedDto): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.patch( + '/invoices/stats/last-visited-at', + statsData + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Create new estimate + * POST /invoices/estimate + */ + async createEstimate(estimateData: CreateEstimatesDto): Promise> { + try { + const payload = { + ...estimateData, + altId: estimateData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/invoices/estimate', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update estimate + * PUT /invoices/estimate/{estimateId} + */ + async updateEstimate(estimateId: string, estimateData: UpdateEstimateDto): Promise> { + try { + const payload = { + ...estimateData, + altId: estimateData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.put( + `/invoices/estimate/${estimateId}`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Delete estimate + * DELETE /invoices/estimate/{estimateId} + */ + async deleteEstimate(estimateId: string, deleteData: AltDto): Promise> { + try { + const payload = { + ...deleteData, + altId: deleteData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.delete( + `/invoices/estimate/${estimateId}`, + { data: payload } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Generate estimate number + * GET /invoices/estimate/number/generate + */ + async generateEstimateNumber(params?: { + altId?: string; + altType?: 'location'; + }): Promise> { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/invoices/estimate/number/generate', + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Send estimate + * POST /invoices/estimate/{estimateId}/send + */ + async sendEstimate(estimateId: string, sendData: SendEstimateDto): Promise> { + try { + const payload = { + ...sendData, + altId: sendData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + `/invoices/estimate/${estimateId}/send`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Create invoice from estimate + * POST /invoices/estimate/{estimateId}/invoice + */ + async createInvoiceFromEstimate(estimateId: string, invoiceData: CreateInvoiceFromEstimateDto): Promise> { + try { + const payload = { + ...invoiceData, + altId: invoiceData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + `/invoices/estimate/${estimateId}/invoice`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * List estimates + * GET /invoices/estimate/list + */ + async listEstimates(params?: { + altId?: string; + altType?: 'location'; + startAt?: string; + endAt?: string; + search?: string; + status?: 'all' | 'draft' | 'sent' | 'accepted' | 'declined' | 'invoiced' | 'viewed'; + contactId?: string; + limit: string; + offset: string; + }): Promise> { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' as const, + limit: params?.limit || '10', + offset: params?.offset || '0', + ...(params?.startAt && { startAt: params.startAt }), + ...(params?.endAt && { endAt: params.endAt }), + ...(params?.search && { search: params.search }), + ...(params?.status && { status: params.status }), + ...(params?.contactId && { contactId: params.contactId }) + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/invoices/estimate/list', + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update estimate last visited at + * PATCH /invoices/estimate/stats/last-visited-at + */ + async updateEstimateLastVisitedAt(statsData: EstimateIdParam): Promise> { + try { + const response: AxiosResponse = await this.axiosInstance.patch( + '/invoices/estimate/stats/last-visited-at', + statsData + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * List estimate templates + * GET /invoices/estimate/template + */ + async listEstimateTemplates(params?: { + altId?: string; + altType?: 'location'; + search?: string; + limit: string; + offset: string; + }): Promise> { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' as const, + limit: params?.limit || '10', + offset: params?.offset || '0', + ...(params?.search && { search: params.search }) + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/invoices/estimate/template', + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Create estimate template + * POST /invoices/estimate/template + */ + async createEstimateTemplate(templateData: EstimateTemplatesDto): Promise> { + try { + const payload = { + ...templateData, + altId: templateData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/invoices/estimate/template', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Update estimate template + * PUT /invoices/estimate/template/{templateId} + */ + async updateEstimateTemplate(templateId: string, templateData: EstimateTemplatesDto): Promise> { + try { + const payload = { + ...templateData, + altId: templateData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.put( + `/invoices/estimate/template/${templateId}`, + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Delete estimate template + * DELETE /invoices/estimate/template/{templateId} + */ + async deleteEstimateTemplate(templateId: string, deleteData: AltDto): Promise> { + try { + const payload = { + ...deleteData, + altId: deleteData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.delete( + `/invoices/estimate/template/${templateId}`, + { data: payload } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Preview estimate template + * GET /invoices/estimate/template/preview + */ + async previewEstimateTemplate(params?: { + altId?: string; + altType?: 'location'; + templateId: string; + }): Promise> { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' as const, + templateId: params?.templateId || '' + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/invoices/estimate/template/preview', + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * Create invoice + * POST /invoices/ + */ + async createInvoice(invoiceData: CreateInvoiceDto): Promise> { + try { + const payload = { + ...invoiceData, + altId: invoiceData.altId || this.config.locationId, + altType: 'location' as const + }; + + const response: AxiosResponse = await this.axiosInstance.post( + '/invoices/', + payload + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } + + /** + * List invoices + * GET /invoices/ + */ + async listInvoices(params?: { + altId?: string; + altType?: 'location'; + status?: string; + startAt?: string; + endAt?: string; + search?: string; + paymentMode?: 'default' | 'live' | 'test'; + contactId?: string; + limit: string; + offset: string; + sortField?: 'issueDate'; + sortOrder?: 'ascend' | 'descend'; + }): Promise> { + try { + const queryParams = { + altId: params?.altId || this.config.locationId, + altType: 'location' as const, + limit: params?.limit || '10', + offset: params?.offset || '0', + ...(params?.status && { status: params.status }), + ...(params?.startAt && { startAt: params.startAt }), + ...(params?.endAt && { endAt: params.endAt }), + ...(params?.search && { search: params.search }), + ...(params?.paymentMode && { paymentMode: params.paymentMode }), + ...(params?.contactId && { contactId: params.contactId }), + ...(params?.sortField && { sortField: params.sortField }), + ...(params?.sortOrder && { sortOrder: params.sortOrder }) + }; + + const response: AxiosResponse = await this.axiosInstance.get( + '/invoices/', + { params: queryParams } + ); + + return this.wrapResponse(response.data); + } catch (error) { + throw error; + } + } +} \ No newline at end of file diff --git a/mcp-diagrams/ghl-mcp-apps-only/src/server.ts b/mcp-diagrams/ghl-mcp-apps-only/src/server.ts new file mode 100644 index 0000000..a558944 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/src/server.ts @@ -0,0 +1,172 @@ +/** + * 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'; +import { GHLConfig } from './types/ghl-types.js'; + +// Load environment variables +dotenv.config(); + +/** + * MCP Apps Only Server + */ +class GHLMCPAppsServer { + private server: Server; + private ghlClient: GHLApiClient; + private mcpAppsManager: 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 + */ + private initializeGHLClient(): GHLApiClient { + const config: GHLConfig = { + 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 + */ + private setupHandlers(): void { + // 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: any) { + process.stderr.write(`[MCP Apps Only] Tool error: ${error.message}\n`); + throw new McpError(ErrorCode.InternalError, error.message); + } + }); + } + + /** + * Setup error handling + */ + private setupErrorHandling(): void { + 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(): Promise { + 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); +}); diff --git a/mcp-diagrams/ghl-mcp-apps-only/src/types/ghl-types.ts b/mcp-diagrams/ghl-mcp-apps-only/src/types/ghl-types.ts new file mode 100644 index 0000000..0d46517 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/src/types/ghl-types.ts @@ -0,0 +1,6688 @@ +/** + * TypeScript interfaces for GoHighLevel API integration + * Based on official OpenAPI specifications v2021-07-28 (Contacts) and v2021-04-15 (Conversations) + */ + +// Base GHL API Configuration +export interface GHLConfig { + accessToken: string; + baseUrl: string; + version: string; + locationId: string; +} + +// OAuth Token Response +export interface GHLTokenResponse { + access_token: string; + token_type: 'Bearer'; + expires_in: number; + refresh_token: string; + scope: string; + userType: 'Location' | 'Company'; + locationId?: string; + companyId: string; + userId: string; + planId?: string; +} + +// Contact Interfaces - Exact from OpenAPI +export interface GHLContact { + id?: string; + locationId: string; + firstName?: string; + lastName?: string; + name?: string; + email?: string; + emailLowerCase?: string; + phone?: string; + address1?: string; + city?: string; + state?: string; + country?: string; + postalCode?: string; + website?: string; + timezone?: string; + companyName?: string; + source?: string; + tags?: string[]; + customFields?: GHLCustomField[]; + dnd?: boolean; + dndSettings?: GHLDndSettings; + assignedTo?: string; + followers?: string[]; + businessId?: string; + dateAdded?: string; + dateUpdated?: string; + dateOfBirth?: string; + type?: string; + validEmail?: boolean; +} + +// Custom Field Interface +export interface GHLCustomField { + id: string; + key?: string; + field_value: string | string[] | object; +} + +// DND Settings Interface +export interface GHLDndSettings { + Call?: GHLDndSetting; + Email?: GHLDndSetting; + SMS?: GHLDndSetting; + WhatsApp?: GHLDndSetting; + GMB?: GHLDndSetting; + FB?: GHLDndSetting; +} + +export interface GHLDndSetting { + status: 'active' | 'inactive' | 'permanent'; + message?: string; + code?: string; +} + +// Search Contacts Request Body +export interface GHLSearchContactsRequest { + locationId: string; + query?: string; + startAfterId?: string; + startAfter?: number; + limit?: number; + filters?: { + email?: string; + phone?: string; + tags?: string[]; + dateAdded?: { + gte?: string; + lte?: string; + }; + }; +} + +// Search Contacts Response +export interface GHLSearchContactsResponse { + contacts: GHLContact[]; + total: number; +} + +// Create Contact Request +export interface GHLCreateContactRequest { + locationId: string; + firstName?: string; + lastName?: string; + name?: string; + email?: string; + phone?: string; + address1?: string; + city?: string; + state?: string; + country?: string; + postalCode?: string; + website?: string; + timezone?: string; + companyName?: string; + source?: string; + tags?: string[]; + customFields?: GHLCustomField[]; + dnd?: boolean; + dndSettings?: GHLDndSettings; + assignedTo?: string; +} + +// Contact Tags Operations +export interface GHLContactTagsRequest { + tags: string[]; +} + +// Contact Tags Response +export interface GHLContactTagsResponse { + tags: string[]; +} + +// CONVERSATION INTERFACES - Based on Conversations API v2021-04-15 + +// Message Types Enum +export type GHLMessageType = + | 'TYPE_CALL' | 'TYPE_SMS' | 'TYPE_EMAIL' | 'TYPE_SMS_REVIEW_REQUEST' + | 'TYPE_WEBCHAT' | 'TYPE_SMS_NO_SHOW_REQUEST' | 'TYPE_CAMPAIGN_SMS' + | 'TYPE_CAMPAIGN_CALL' | 'TYPE_CAMPAIGN_EMAIL' | 'TYPE_CAMPAIGN_VOICEMAIL' + | 'TYPE_FACEBOOK' | 'TYPE_CAMPAIGN_FACEBOOK' | 'TYPE_CAMPAIGN_MANUAL_CALL' + | 'TYPE_CAMPAIGN_MANUAL_SMS' | 'TYPE_GMB' | 'TYPE_CAMPAIGN_GMB' + | 'TYPE_REVIEW' | 'TYPE_INSTAGRAM' | 'TYPE_WHATSAPP' | 'TYPE_CUSTOM_SMS' + | 'TYPE_CUSTOM_EMAIL' | 'TYPE_CUSTOM_PROVIDER_SMS' | 'TYPE_CUSTOM_PROVIDER_EMAIL' + | 'TYPE_IVR_CALL' | 'TYPE_ACTIVITY_CONTACT' | 'TYPE_ACTIVITY_INVOICE' + | 'TYPE_ACTIVITY_PAYMENT' | 'TYPE_ACTIVITY_OPPORTUNITY' | 'TYPE_LIVE_CHAT' + | 'TYPE_LIVE_CHAT_INFO_MESSAGE' | 'TYPE_ACTIVITY_APPOINTMENT' + | 'TYPE_FACEBOOK_COMMENT' | 'TYPE_INSTAGRAM_COMMENT' | 'TYPE_CUSTOM_CALL' + | 'TYPE_INTERNAL_COMMENT'; + +// Send Message Types +export type GHLSendMessageType = 'SMS' | 'Email' | 'WhatsApp' | 'IG' | 'FB' | 'Custom' | 'Live_Chat'; + +// Message Status +export type GHLMessageStatus = + | 'pending' | 'scheduled' | 'sent' | 'delivered' | 'read' + | 'undelivered' | 'connected' | 'failed' | 'opened' | 'clicked' | 'opt_out'; + +// Message Direction +export type GHLMessageDirection = 'inbound' | 'outbound'; + +// Conversation Interface +export interface GHLConversation { + id: string; + contactId: string; + locationId: string; + lastMessageBody: string; + lastMessageType: GHLMessageType; + type: string; + unreadCount: number; + fullName: string; + contactName: string; + email: string; + phone: string; + assignedTo?: string; + starred?: boolean; + deleted?: boolean; + inbox?: boolean; + lastMessageDate?: string; + dateAdded?: string; + dateUpdated?: string; +} + +// Message Interface +export interface GHLMessage { + id: string; + type: number; + messageType: GHLMessageType; + locationId: string; + contactId: string; + conversationId: string; + dateAdded: string; + body?: string; + direction: GHLMessageDirection; + status: GHLMessageStatus; + contentType: string; + attachments?: string[]; + meta?: GHLMessageMeta; + source?: 'workflow' | 'bulk_actions' | 'campaign' | 'api' | 'app'; + userId?: string; + conversationProviderId?: string; +} + +// Message Meta Interface +export interface GHLMessageMeta { + callDuration?: string; + callStatus?: 'pending' | 'completed' | 'answered' | 'busy' | 'no-answer' | 'failed' | 'canceled' | 'voicemail'; + email?: { + messageIds?: string[]; + }; +} + +// Send Message Request +export interface GHLSendMessageRequest { + type: GHLSendMessageType; + contactId: string; + message?: string; + html?: string; + subject?: string; + attachments?: string[]; + emailFrom?: string; + emailTo?: string; + emailCc?: string[]; + emailBcc?: string[]; + replyMessageId?: string; + templateId?: string; + threadId?: string; + scheduledTimestamp?: number; + conversationProviderId?: string; + emailReplyMode?: 'reply' | 'reply_all'; + fromNumber?: string; + toNumber?: string; + appointmentId?: string; +} + +// Send Message Response +export interface GHLSendMessageResponse { + conversationId: string; + messageId: string; + emailMessageId?: string; + messageIds?: string[]; + msg?: string; +} + +// Search Conversations Request +export interface GHLSearchConversationsRequest { + locationId: string; + contactId?: string; + assignedTo?: string; + followers?: string; + mentions?: string; + query?: string; + sort?: 'asc' | 'desc'; + startAfterDate?: number | number[]; + id?: string; + limit?: number; + lastMessageType?: GHLMessageType; + lastMessageAction?: 'automated' | 'manual'; + lastMessageDirection?: GHLMessageDirection; + status?: 'all' | 'read' | 'unread' | 'starred' | 'recents'; + sortBy?: 'last_manual_message_date' | 'last_message_date' | 'score_profile'; +} + +// Search Conversations Response +export interface GHLSearchConversationsResponse { + conversations: GHLConversation[]; + total: number; +} + +// Get Messages Response +export interface GHLGetMessagesResponse { + lastMessageId: string; + nextPage: boolean; + messages: GHLMessage[]; +} + +// Create Conversation Request +export interface GHLCreateConversationRequest { + locationId: string; + contactId: string; +} + +// Create Conversation Response +export interface GHLCreateConversationResponse { + id: string; + dateUpdated: string; + dateAdded: string; + deleted: boolean; + contactId: string; + locationId: string; + lastMessageDate: string; + assignedTo?: string; +} + +// Update Conversation Request +export interface GHLUpdateConversationRequest { + locationId: string; + unreadCount?: number; + starred?: boolean; + feedback?: object; +} + +// API Response Wrapper +export interface GHLApiResponse { + success: boolean; + data?: T; + error?: { + message: string; + statusCode: number; + details?: any; + }; +} + +// Error Response from API +export interface GHLErrorResponse { + statusCode: number; + message: string | string[]; + error?: string; +} + +// Task Interface +export interface GHLTask { + id?: string; + title: string; + body?: string; + assignedTo?: string; + dueDate: string; + completed: boolean; + contactId: string; +} + +// Note Interface +export interface GHLNote { + id?: string; + body: string; + userId?: string; + contactId: string; + dateAdded?: string; +} + +// Campaign Interface +export interface GHLCampaign { + id: string; + name: string; + status: string; +} + +// Workflow Interface +export interface GHLWorkflow { + id: string; + name: string; + status: string; + eventStartTime?: string; +} + +// Appointment Interface +export interface GHLAppointment { + id: string; + calendarId: string; + status: string; + title: string; + appointmentStatus: string; + assignedUserId: string; + notes?: string; + startTime: string; + endTime: string; + address?: string; + locationId: string; + contactId: string; + groupId?: string; + users?: string[]; + dateAdded: string; + dateUpdated: string; + assignedResources?: string[]; +} + +// Upsert Contact Response +export interface GHLUpsertContactResponse { + contact: GHLContact; + new: boolean; + traceId?: string; +} + +// Bulk Tags Update Response +export interface GHLBulkTagsResponse { + succeeded: boolean; + errorCount: number; + responses: Array<{ + contactId: string; + message: string; + type: 'success' | 'error'; + oldTags?: string[]; + tagsAdded?: string[]; + tagsRemoved?: string[]; + }>; +} + +// Bulk Business Update Response +export interface GHLBulkBusinessResponse { + success: boolean; + ids: string[]; +} + +// Followers Response +export interface GHLFollowersResponse { + followers: string[]; + followersAdded?: string[]; + followersRemoved?: string[]; +} + +// MCP Tool Parameters - Contact Operations +export interface MCPCreateContactParams { + firstName?: string; + lastName?: string; + email: string; + phone?: string; + tags?: string[]; + source?: string; +} + +export interface MCPSearchContactsParams { + query?: string; + email?: string; + phone?: string; + limit?: number; +} + +export interface MCPUpdateContactParams { + contactId: string; + firstName?: string; + lastName?: string; + email?: string; + phone?: string; + tags?: string[]; +} + +export interface MCPAddContactTagsParams { + contactId: string; + tags: string[]; +} + +export interface MCPRemoveContactTagsParams { + contactId: string; + tags: string[]; +} + +// MCP Tool Parameters - Contact Task Management +export interface MCPGetContactTasksParams { + contactId: string; +} + +export interface MCPCreateContactTaskParams { + contactId: string; + title: string; + body?: string; + dueDate: string; // ISO date string + completed?: boolean; + assignedTo?: string; +} + +export interface MCPGetContactTaskParams { + contactId: string; + taskId: string; +} + +export interface MCPUpdateContactTaskParams { + contactId: string; + taskId: string; + title?: string; + body?: string; + dueDate?: string; + completed?: boolean; + assignedTo?: string; +} + +export interface MCPDeleteContactTaskParams { + contactId: string; + taskId: string; +} + +export interface MCPUpdateTaskCompletionParams { + contactId: string; + taskId: string; + completed: boolean; +} + +// MCP Tool Parameters - Contact Note Management +export interface MCPGetContactNotesParams { + contactId: string; +} + +export interface MCPCreateContactNoteParams { + contactId: string; + body: string; + userId?: string; +} + +export interface MCPGetContactNoteParams { + contactId: string; + noteId: string; +} + +export interface MCPUpdateContactNoteParams { + contactId: string; + noteId: string; + body: string; + userId?: string; +} + +export interface MCPDeleteContactNoteParams { + contactId: string; + noteId: string; +} + +// MCP Tool Parameters - Advanced Contact Operations +export interface MCPUpsertContactParams { + firstName?: string; + lastName?: string; + name?: string; + email?: string; + phone?: string; + address?: string; + city?: string; + state?: string; + country?: string; + postalCode?: string; + website?: string; + timezone?: string; + companyName?: string; + tags?: string[]; + customFields?: GHLCustomField[]; + source?: string; + assignedTo?: string; +} + +export interface MCPGetDuplicateContactParams { + email?: string; + phone?: string; +} + +export interface MCPGetContactsByBusinessParams { + businessId: string; + limit?: number; + skip?: number; + query?: string; +} + +// MCP Tool Parameters - Contact Appointments +export interface MCPGetContactAppointmentsParams { + contactId: string; +} + +// MCP Tool Parameters - Bulk Operations +export interface MCPBulkUpdateContactTagsParams { + contactIds: string[]; + tags: string[]; + operation: 'add' | 'remove'; + removeAllTags?: boolean; +} + +export interface MCPBulkUpdateContactBusinessParams { + contactIds: string[]; + businessId?: string; // null to remove from business +} + +// MCP Tool Parameters - Followers Management +export interface MCPAddContactFollowersParams { + contactId: string; + followers: string[]; +} + +export interface MCPRemoveContactFollowersParams { + contactId: string; + followers: string[]; +} + +// MCP Tool Parameters - Campaign Management +export interface MCPAddContactToCampaignParams { + contactId: string; + campaignId: string; +} + +export interface MCPRemoveContactFromCampaignParams { + contactId: string; + campaignId: string; +} + +export interface MCPRemoveContactFromAllCampaignsParams { + contactId: string; +} + +// MCP Tool Parameters - Workflow Management +export interface MCPAddContactToWorkflowParams { + contactId: string; + workflowId: string; + eventStartTime?: string; +} + +export interface MCPRemoveContactFromWorkflowParams { + contactId: string; + workflowId: string; + eventStartTime?: string; +} + +// MCP Tool Parameters - Conversation Operations +export interface MCPSendSMSParams { + contactId: string; + message: string; + fromNumber?: string; +} + +export interface MCPSendEmailParams { + contactId: string; + subject: string; + message?: string; + html?: string; + emailFrom?: string; + attachments?: string[]; + emailCc?: string[]; + emailBcc?: string[]; +} + +export interface MCPSearchConversationsParams { + contactId?: string; + query?: string; + status?: 'all' | 'read' | 'unread' | 'starred'; + limit?: number; + assignedTo?: string; +} + +export interface MCPGetConversationParams { + conversationId: string; + limit?: number; + messageTypes?: string[]; +} + +export interface MCPCreateConversationParams { + contactId: string; +} + +export interface MCPUpdateConversationParams { + conversationId: string; + starred?: boolean; + unreadCount?: number; +} + +// BLOG INTERFACES - Based on Blogs API v2021-07-28 + +// Blog Post Status Enum +export type GHLBlogPostStatus = 'DRAFT' | 'PUBLISHED' | 'SCHEDULED' | 'ARCHIVED'; + +// Blog Post Response Interface +export interface GHLBlogPost { + _id: string; + title: string; + description: string; + imageUrl: string; + imageAltText: string; + urlSlug: string; + canonicalLink?: string; + author: string; // Author ID + publishedAt: string; + updatedAt: string; + status: GHLBlogPostStatus; + categories: string[]; // Array of category IDs + tags?: string[]; + archived: boolean; + rawHTML?: string; // Full HTML content +} + +// Create Blog Post Parameters +export interface GHLCreateBlogPostRequest { + title: string; + locationId: string; + blogId: string; + imageUrl: string; + description: string; + rawHTML: string; + status: GHLBlogPostStatus; + imageAltText: string; + categories: string[]; // Array of category IDs + tags?: string[]; + author: string; // Author ID + urlSlug: string; + canonicalLink?: string; + publishedAt: string; // ISO timestamp +} + +// Update Blog Post Parameters +export interface GHLUpdateBlogPostRequest { + title?: string; + locationId: string; + blogId: string; + imageUrl?: string; + description?: string; + rawHTML?: string; + status?: GHLBlogPostStatus; + imageAltText?: string; + categories?: string[]; + tags?: string[]; + author?: string; + urlSlug?: string; + canonicalLink?: string; + publishedAt?: string; +} + +// Blog Post Create Response +export interface GHLBlogPostCreateResponse { + data: GHLBlogPost; +} + +// Blog Post Update Response +export interface GHLBlogPostUpdateResponse { + updatedBlogPost: GHLBlogPost; +} + +// Blog Post List Response +export interface GHLBlogPostListResponse { + blogs: GHLBlogPost[]; +} + +// Blog Author Interface +export interface GHLBlogAuthor { + _id: string; + name: string; + locationId: string; + updatedAt: string; + canonicalLink: string; +} + +// Authors List Response +export interface GHLBlogAuthorsResponse { + authors: GHLBlogAuthor[]; +} + +// Blog Category Interface +export interface GHLBlogCategory { + _id: string; + label: string; + locationId: string; + updatedAt: string; + canonicalLink: string; + urlSlug: string; +} + +// Categories List Response +export interface GHLBlogCategoriesResponse { + categories: GHLBlogCategory[]; +} + +// Blog Site Interface +export interface GHLBlogSite { + _id: string; + name: string; +} + +// Blog Sites List Response +export interface GHLBlogSitesResponse { + data: GHLBlogSite[]; +} + +// URL Slug Check Response +export interface GHLUrlSlugCheckResponse { + exists: boolean; +} + +// Blog Post Search/List Parameters +export interface GHLGetBlogPostsRequest { + locationId: string; + blogId: string; + limit: number; + offset: number; + searchTerm?: string; + status?: GHLBlogPostStatus; +} + +// Blog Authors Request Parameters +export interface GHLGetBlogAuthorsRequest { + locationId: string; + limit: number; + offset: number; +} + +// Blog Categories Request Parameters +export interface GHLGetBlogCategoriesRequest { + locationId: string; + limit: number; + offset: number; +} + +// Blog Sites Request Parameters +export interface GHLGetBlogSitesRequest { + locationId: string; + skip: number; + limit: number; + searchTerm?: string; +} + +// URL Slug Check Parameters +export interface GHLCheckUrlSlugRequest { + locationId: string; + urlSlug: string; + postId?: string; +} + +// MCP Tool Parameters - Blog Operations +export interface MCPCreateBlogPostParams { + title: string; + blogId: string; + content: string; // Raw HTML content + description: string; + imageUrl: string; + imageAltText: string; + urlSlug: string; + author: string; // Author ID + categories: string[]; // Array of category IDs + tags?: string[]; + status?: GHLBlogPostStatus; + canonicalLink?: string; + publishedAt?: string; // ISO timestamp, defaults to now if not provided +} + +export interface MCPUpdateBlogPostParams { + postId: string; + blogId: string; + title?: string; + content?: string; + description?: string; + imageUrl?: string; + imageAltText?: string; + urlSlug?: string; + author?: string; + categories?: string[]; + tags?: string[]; + status?: GHLBlogPostStatus; + canonicalLink?: string; + publishedAt?: string; +} + +export interface MCPGetBlogPostsParams { + blogId: string; + limit?: number; + offset?: number; + searchTerm?: string; + status?: GHLBlogPostStatus; +} + +export interface MCPGetBlogSitesParams { + skip?: number; + limit?: number; + searchTerm?: string; +} + +export interface MCPGetBlogAuthorsParams { + limit?: number; + offset?: number; +} + +export interface MCPGetBlogCategoriesParams { + limit?: number; + offset?: number; +} + +export interface MCPCheckUrlSlugParams { + urlSlug: string; + postId?: string; +} + +// OPPORTUNITIES API INTERFACES - Based on Opportunities API v2021-07-28 + +// Opportunity Status Enum +export type GHLOpportunityStatus = 'open' | 'won' | 'lost' | 'abandoned' | 'all'; + +// Opportunity Contact Response Interface +export interface GHLOpportunityContact { + id: string; + name: string; + companyName?: string; + email?: string; + phone?: string; + tags?: string[]; +} + +// Custom Field Response Interface +export interface GHLCustomFieldResponse { + id: string; + fieldValue: string | object | string[] | object[]; +} + +// Opportunity Response Interface +export interface GHLOpportunity { + id: string; + name: string; + monetaryValue?: number; + pipelineId: string; + pipelineStageId: string; + assignedTo?: string; + status: GHLOpportunityStatus; + source?: string; + lastStatusChangeAt?: string; + lastStageChangeAt?: string; + lastActionDate?: string; + indexVersion?: number; + createdAt: string; + updatedAt: string; + contactId: string; + locationId: string; + contact?: GHLOpportunityContact; + notes?: string[]; + tasks?: string[]; + calendarEvents?: string[]; + customFields?: GHLCustomFieldResponse[]; + followers?: string[][]; +} + +// Search Meta Response Interface +export interface GHLOpportunitySearchMeta { + total: number; + nextPageUrl?: string; + startAfterId?: string; + startAfter?: number; + currentPage?: number; + nextPage?: number; + prevPage?: number; +} + +// Search Opportunities Response +export interface GHLSearchOpportunitiesResponse { + opportunities: GHLOpportunity[]; + meta: GHLOpportunitySearchMeta; + aggregations?: object; +} + +// Pipeline Stage Interface +export interface GHLPipelineStage { + id: string; + name: string; + position: number; +} + +// Pipeline Interface +export interface GHLPipeline { + id: string; + name: string; + stages: GHLPipelineStage[]; + showInFunnel: boolean; + showInPieChart: boolean; + locationId: string; +} + +// Get Pipelines Response +export interface GHLGetPipelinesResponse { + pipelines: GHLPipeline[]; +} + +// Search Opportunities Request +export interface GHLSearchOpportunitiesRequest { + q?: string; // query string + location_id: string; // Note: underscore format as per API + pipeline_id?: string; + pipeline_stage_id?: string; + contact_id?: string; + status?: GHLOpportunityStatus; + assigned_to?: string; + campaignId?: string; + id?: string; + order?: string; + endDate?: string; // mm-dd-yyyy format + startAfter?: string; + startAfterId?: string; + date?: string; // mm-dd-yyyy format + country?: string; + page?: number; + limit?: number; + getTasks?: boolean; + getNotes?: boolean; + getCalendarEvents?: boolean; +} + +// Create Opportunity Request +export interface GHLCreateOpportunityRequest { + pipelineId: string; + locationId: string; + name: string; + pipelineStageId?: string; + status: GHLOpportunityStatus; + contactId: string; + monetaryValue?: number; + assignedTo?: string; + customFields?: GHLCustomFieldInput[]; +} + +// Update Opportunity Request +export interface GHLUpdateOpportunityRequest { + pipelineId?: string; + name?: string; + pipelineStageId?: string; + status?: GHLOpportunityStatus; + monetaryValue?: number; + assignedTo?: string; + customFields?: GHLCustomFieldInput[]; +} + +// Update Opportunity Status Request +export interface GHLUpdateOpportunityStatusRequest { + status: GHLOpportunityStatus; +} + +// Upsert Opportunity Request +export interface GHLUpsertOpportunityRequest { + pipelineId: string; + locationId: string; + contactId: string; + name?: string; + status?: GHLOpportunityStatus; + pipelineStageId?: string; + monetaryValue?: number; + assignedTo?: string; +} + +// Upsert Opportunity Response +export interface GHLUpsertOpportunityResponse { + opportunity: GHLOpportunity; + new: boolean; +} + +// Custom Field Input Interfaces (reuse existing ones) +export interface GHLCustomFieldInput { + id?: string; + key?: string; + field_value: string | string[] | object; +} + +// MCP Tool Parameters - Opportunity Operations +export interface MCPSearchOpportunitiesParams { + query?: string; + pipelineId?: string; + pipelineStageId?: string; + contactId?: string; + status?: GHLOpportunityStatus; + assignedTo?: string; + campaignId?: string; + country?: string; + startDate?: string; // mm-dd-yyyy + endDate?: string; // mm-dd-yyyy + limit?: number; + page?: number; + includeTasks?: boolean; + includeNotes?: boolean; + includeCalendarEvents?: boolean; +} + +export interface MCPCreateOpportunityParams { + name: string; + pipelineId: string; + contactId: string; + status?: GHLOpportunityStatus; + pipelineStageId?: string; + monetaryValue?: number; + assignedTo?: string; + customFields?: GHLCustomFieldInput[]; +} + +export interface MCPUpdateOpportunityParams { + opportunityId: string; + name?: string; + pipelineId?: string; + pipelineStageId?: string; + status?: GHLOpportunityStatus; + monetaryValue?: number; + assignedTo?: string; + customFields?: GHLCustomFieldInput[]; +} + +export interface MCPUpsertOpportunityParams { + pipelineId: string; + contactId: string; + name?: string; + status?: GHLOpportunityStatus; + pipelineStageId?: string; + monetaryValue?: number; + assignedTo?: string; +} + +export interface MCPAddOpportunityFollowersParams { + opportunityId: string; + followers: string[]; +} + +export interface MCPRemoveOpportunityFollowersParams { + opportunityId: string; + followers: string[]; +} + +// CALENDAR & APPOINTMENTS API INTERFACES - Based on Calendar API v2021-04-15 + +// Calendar Group Interfaces +export interface GHLCalendarGroup { + id: string; + locationId: string; + name: string; + description: string; + slug: string; + isActive: boolean; +} + +export interface GHLGetCalendarGroupsResponse { + groups: GHLCalendarGroup[]; +} + +export interface GHLCreateCalendarGroupRequest { + locationId: string; + name: string; + description: string; + slug: string; + isActive?: boolean; +} + +// Meeting Location Configuration +export interface GHLLocationConfiguration { + kind: 'custom' | 'zoom_conference' | 'google_conference' | 'inbound_call' | 'outbound_call' | 'physical' | 'booker' | 'ms_teams_conference'; + location?: string; + meetingId?: string; +} + +// Team Member Configuration +export interface GHLTeamMember { + userId: string; + priority?: number; // 0, 0.5, 1 + isPrimary?: boolean; + locationConfigurations?: GHLLocationConfiguration[]; +} + +// Calendar Hour Configuration +export interface GHLHour { + openHour: number; + openMinute: number; + closeHour: number; + closeMinute: number; +} + +export interface GHLOpenHour { + daysOfTheWeek: number[]; // 0-6 + hours: GHLHour[]; +} + +// Calendar Availability +export interface GHLAvailability { + date: string; // YYYY-MM-DDTHH:mm:ss.sssZ format + hours: GHLHour[]; + deleted?: boolean; + id?: string; +} + +// Calendar Interfaces +export interface GHLCalendar { + id: string; + locationId: string; + groupId?: string; + name: string; + description?: string; + slug?: string; + widgetSlug?: string; + calendarType: 'round_robin' | 'event' | 'class_booking' | 'collective' | 'service_booking' | 'personal'; + widgetType?: 'default' | 'classic'; + eventTitle?: string; + eventColor?: string; + isActive?: boolean; + teamMembers?: GHLTeamMember[]; + locationConfigurations?: GHLLocationConfiguration[]; + slotDuration?: number; + slotDurationUnit?: 'mins' | 'hours'; + slotInterval?: number; + slotIntervalUnit?: 'mins' | 'hours'; + slotBuffer?: number; + slotBufferUnit?: 'mins' | 'hours'; + preBuffer?: number; + preBufferUnit?: 'mins' | 'hours'; + appoinmentPerSlot?: number; + appoinmentPerDay?: number; + allowBookingAfter?: number; + allowBookingAfterUnit?: 'hours' | 'days' | 'weeks' | 'months'; + allowBookingFor?: number; + allowBookingForUnit?: 'days' | 'weeks' | 'months'; + openHours?: GHLOpenHour[]; + availabilities?: GHLAvailability[]; + autoConfirm?: boolean; + allowReschedule?: boolean; + allowCancellation?: boolean; + formId?: string; + notes?: string; +} + +export interface GHLGetCalendarsResponse { + calendars: GHLCalendar[]; +} + +export interface GHLCreateCalendarRequest { + locationId: string; + groupId?: string; + name: string; + description?: string; + slug?: string; + calendarType: 'round_robin' | 'event' | 'class_booking' | 'collective' | 'service_booking' | 'personal'; + teamMembers?: GHLTeamMember[]; + locationConfigurations?: GHLLocationConfiguration[]; + slotDuration?: number; + slotDurationUnit?: 'mins' | 'hours'; + autoConfirm?: boolean; + allowReschedule?: boolean; + allowCancellation?: boolean; + openHours?: GHLOpenHour[]; + isActive?: boolean; +} + +export interface GHLUpdateCalendarRequest { + name?: string; + description?: string; + groupId?: string; + teamMembers?: GHLTeamMember[]; + locationConfigurations?: GHLLocationConfiguration[]; + slotDuration?: number; + slotDurationUnit?: 'mins' | 'hours'; + autoConfirm?: boolean; + allowReschedule?: boolean; + allowCancellation?: boolean; + openHours?: GHLOpenHour[]; + availabilities?: GHLAvailability[]; + isActive?: boolean; +} + +// Calendar Event/Appointment Interfaces +export interface GHLCalendarEvent { + id: string; + title: string; + calendarId: string; + locationId: string; + contactId: string; + groupId?: string; + appointmentStatus: 'new' | 'confirmed' | 'cancelled' | 'showed' | 'noshow' | 'invalid'; + assignedUserId: string; + users?: string[]; + address?: string; + notes?: string; + startTime: string; + endTime: string; + dateAdded: string; + dateUpdated: string; + isRecurring?: boolean; + rrule?: string; + masterEventId?: string; + assignedResources?: string[]; +} + +export interface GHLGetCalendarEventsResponse { + events: GHLCalendarEvent[]; +} + +export interface GHLGetCalendarEventsRequest { + locationId: string; + userId?: string; + calendarId?: string; + groupId?: string; + startTime: string; // milliseconds + endTime: string; // milliseconds +} + +// Free Slots Interface +export interface GHLFreeSlot { + slots: string[]; +} + +export interface GHLGetFreeSlotsResponse { + [date: string]: GHLFreeSlot; // Date as key +} + +export interface GHLGetFreeSlotsRequest { + calendarId: string; + startDate: number; // milliseconds + endDate: number; // milliseconds + timezone?: string; + userId?: string; + userIds?: string[]; + enableLookBusy?: boolean; +} + +// Appointment Management +export interface GHLCreateAppointmentRequest { + calendarId: string; + locationId: string; + contactId: string; + startTime: string; // ISO format + endTime?: string; // ISO format + title?: string; + appointmentStatus?: 'new' | 'confirmed' | 'cancelled' | 'showed' | 'noshow' | 'invalid'; + assignedUserId?: string; + address?: string; + meetingLocationType?: 'custom' | 'zoom' | 'gmeet' | 'phone' | 'address' | 'ms_teams' | 'google'; + meetingLocationId?: string; + ignoreDateRange?: boolean; + toNotify?: boolean; + ignoreFreeSlotValidation?: boolean; + rrule?: string; // Recurring rule +} + +export interface GHLUpdateAppointmentRequest { + title?: string; + appointmentStatus?: 'new' | 'confirmed' | 'cancelled' | 'showed' | 'noshow' | 'invalid'; + assignedUserId?: string; + address?: string; + startTime?: string; + endTime?: string; + meetingLocationType?: 'custom' | 'zoom' | 'gmeet' | 'phone' | 'address' | 'ms_teams' | 'google'; + toNotify?: boolean; + ignoreFreeSlotValidation?: boolean; +} + +// Block Slot Management +export interface GHLCreateBlockSlotRequest { + calendarId?: string; + locationId: string; + startTime: string; + endTime: string; + title?: string; + assignedUserId?: string; +} + +export interface GHLUpdateBlockSlotRequest { + calendarId?: string; + startTime?: string; + endTime?: string; + title?: string; + assignedUserId?: string; +} + +export interface GHLBlockSlotResponse { + id: string; + locationId: string; + title: string; + startTime: string; + endTime: string; + calendarId?: string; + assignedUserId?: string; +} + +// MCP Tool Parameters +export interface MCPGetCalendarsParams { + groupId?: string; + showDrafted?: boolean; +} + +export interface MCPCreateCalendarParams { + name: string; + description?: string; + calendarType: 'round_robin' | 'event' | 'class_booking' | 'collective' | 'service_booking' | 'personal'; + groupId?: string; + teamMembers?: GHLTeamMember[]; + slotDuration?: number; + slotDurationUnit?: 'mins' | 'hours'; + autoConfirm?: boolean; + allowReschedule?: boolean; + allowCancellation?: boolean; + isActive?: boolean; +} + +export interface MCPUpdateCalendarParams { + calendarId: string; + name?: string; + description?: string; + groupId?: string; + teamMembers?: GHLTeamMember[]; + slotDuration?: number; + autoConfirm?: boolean; + allowReschedule?: boolean; + allowCancellation?: boolean; + isActive?: boolean; +} + +export interface MCPGetCalendarEventsParams { + userId?: string; + calendarId?: string; + groupId?: string; + startTime: string; // milliseconds or ISO date + endTime: string; // milliseconds or ISO date +} + +export interface MCPGetFreeSlotsParams { + calendarId: string; + startDate: string; // YYYY-MM-DD or milliseconds + endDate: string; // YYYY-MM-DD or milliseconds + timezone?: string; + userId?: string; +} + +export interface MCPCreateAppointmentParams { + calendarId: string; + contactId: string; + startTime: string; // ISO format + endTime?: string; // ISO format + title?: string; + appointmentStatus?: 'new' | 'confirmed'; + assignedUserId?: string; + address?: string; + meetingLocationType?: 'custom' | 'zoom' | 'gmeet' | 'phone' | 'address'; + ignoreDateRange?: boolean; + toNotify?: boolean; +} + +export interface MCPUpdateAppointmentParams { + appointmentId: string; + title?: string; + appointmentStatus?: 'new' | 'confirmed' | 'cancelled' | 'showed' | 'noshow'; + assignedUserId?: string; + address?: string; + startTime?: string; + endTime?: string; + toNotify?: boolean; +} + +export interface MCPCreateBlockSlotParams { + calendarId?: string; + startTime: string; + endTime: string; + title?: string; + assignedUserId?: string; +} + +export interface MCPUpdateBlockSlotParams { + blockSlotId: string; + calendarId?: string; + startTime?: string; + endTime?: string; + title?: string; + assignedUserId?: string; +} + +// EMAIL API INTERFACES + +export interface GHLEmailCampaign { + id: string; + name: string; + status: string; + createdAt: string; + updatedAt: string; +} + +export interface GHLEmailCampaignsResponse { + schedules: GHLEmailCampaign[]; + total: number; +} + +export interface GHLEmailTemplate { + id: string; + name: string; + templateType: string; + lastUpdated: string; + dateAdded: string; + previewUrl: string; +} + +// MCP Tool Parameters - Email Operations +export interface MCPGetEmailCampaignsParams { + status?: 'active' | 'pause' | 'complete' | 'cancelled' | 'retry' | 'draft' | 'resend-scheduled'; + limit?: number; + offset?: number; +} + +export interface MCPCreateEmailTemplateParams { + title: string; + html: string; + isPlainText?: boolean; +} + +export interface MCPGetEmailTemplatesParams { + limit?: number; + offset?: number; +} + +export interface MCPUpdateEmailTemplateParams { + templateId: string; + html: string; + previewText?: string; +} + +export interface MCPDeleteEmailTemplateParams { + templateId: string; +} + +// LOCATION API INTERFACES - Based on Locations API v2021-07-28 + +// Location Settings Schema +export interface GHLLocationSettings { + allowDuplicateContact?: boolean; + allowDuplicateOpportunity?: boolean; + allowFacebookNameMerge?: boolean; + disableContactTimezone?: boolean; +} + +// Location Social Schema +export interface GHLLocationSocial { + facebookUrl?: string; + googlePlus?: string; + linkedIn?: string; + foursquare?: string; + twitter?: string; + yelp?: string; + instagram?: string; + youtube?: string; + pinterest?: string; + blogRss?: string; + googlePlacesId?: string; +} + +// Location Business Schema +export interface GHLLocationBusiness { + name?: string; + address?: string; + city?: string; + state?: string; + country?: string; + postalCode?: string; + website?: string; + timezone?: string; + logoUrl?: string; +} + +// Location Prospect Info +export interface GHLLocationProspectInfo { + firstName: string; + lastName: string; + email: string; +} + +// Twilio Configuration +export interface GHLLocationTwilio { + sid: string; + authToken: string; +} + +// Mailgun Configuration +export interface GHLLocationMailgun { + apiKey: string; + domain: string; +} + +// Snapshot Configuration +export interface GHLLocationSnapshot { + id: string; + override?: boolean; +} + +// Basic Location Schema +export interface GHLLocation { + id: string; + name: string; + phone?: string; + email?: string; + address?: string; + city?: string; + state?: string; + country?: string; + postalCode?: string; + website?: string; + timezone?: string; + settings?: GHLLocationSettings; + social?: GHLLocationSocial; +} + +// Detailed Location Schema +export interface GHLLocationDetailed { + id: string; + companyId: string; + name: string; + domain?: string; + address?: string; + city?: string; + state?: string; + logoUrl?: string; + country?: string; + postalCode?: string; + website?: string; + timezone?: string; + firstName?: string; + lastName?: string; + email?: string; + phone?: string; + business?: GHLLocationBusiness; + social?: GHLLocationSocial; + settings?: GHLLocationSettings; + reseller?: object; +} + +// Location Search Response +export interface GHLLocationSearchResponse { + locations: GHLLocation[]; +} + +// Location Details Response +export interface GHLLocationDetailsResponse { + location: GHLLocationDetailed; +} + +// Create Location Request +export interface GHLCreateLocationRequest { + name: string; + companyId: string; + phone?: string; + address?: string; + city?: string; + state?: string; + country?: string; + postalCode?: string; + website?: string; + timezone?: string; + prospectInfo?: GHLLocationProspectInfo; + settings?: GHLLocationSettings; + social?: GHLLocationSocial; + twilio?: GHLLocationTwilio; + mailgun?: GHLLocationMailgun; + snapshotId?: string; +} + +// Update Location Request +export interface GHLUpdateLocationRequest { + name?: string; + companyId: string; + phone?: string; + address?: string; + city?: string; + state?: string; + country?: string; + postalCode?: string; + website?: string; + timezone?: string; + prospectInfo?: GHLLocationProspectInfo; + settings?: GHLLocationSettings; + social?: GHLLocationSocial; + twilio?: GHLLocationTwilio; + mailgun?: GHLLocationMailgun; + snapshot?: GHLLocationSnapshot; +} + +// Location Delete Response +export interface GHLLocationDeleteResponse { + success: boolean; + message: string; +} + +// LOCATION TAGS INTERFACES + +// Location Tag Schema +export interface GHLLocationTag { + id: string; + name: string; + locationId: string; +} + +// Location Tags Response +export interface GHLLocationTagsResponse { + tags: GHLLocationTag[]; +} + +// Location Tag Response +export interface GHLLocationTagResponse { + tag: GHLLocationTag; +} + +// Tag Create/Update Request +export interface GHLLocationTagRequest { + name: string; +} + +// Tag Delete Response +export interface GHLLocationTagDeleteResponse { + succeded: boolean; +} + +// LOCATION TASKS INTERFACES + +// Task Search Parameters +export interface GHLLocationTaskSearchRequest { + contactId?: string[]; + completed?: boolean; + assignedTo?: string[]; + query?: string; + limit?: number; + skip?: number; + businessId?: string; +} + +// Task Search Response +export interface GHLLocationTaskSearchResponse { + tasks: any[]; +} + +// CUSTOM FIELDS INTERFACES + +// Text Box List Options +export interface GHLCustomFieldTextBoxOption { + label: string; + prefillValue?: string; +} + +// Custom Field Schema +export interface GHLLocationCustomField { + id: string; + name: string; + fieldKey: string; + placeholder?: string; + dataType: string; + position: number; + picklistOptions?: string[]; + picklistImageOptions?: string[]; + isAllowedCustomOption?: boolean; + isMultiFileAllowed?: boolean; + maxFileLimit?: number; + locationId: string; + model: 'contact' | 'opportunity'; +} + +// Custom Fields List Response +export interface GHLLocationCustomFieldsResponse { + customFields: GHLLocationCustomField[]; +} + +// Custom Field Response +export interface GHLLocationCustomFieldResponse { + customField: GHLLocationCustomField; +} + +// Create Custom Field Request +export interface GHLCreateCustomFieldRequest { + name: string; + dataType: string; + placeholder?: string; + acceptedFormat?: string[]; + isMultipleFile?: boolean; + maxNumberOfFiles?: number; + textBoxListOptions?: GHLCustomFieldTextBoxOption[]; + position?: number; + model?: 'contact' | 'opportunity'; +} + +// Update Custom Field Request +export interface GHLUpdateCustomFieldRequest { + name: string; + placeholder?: string; + acceptedFormat?: string[]; + isMultipleFile?: boolean; + maxNumberOfFiles?: number; + textBoxListOptions?: GHLCustomFieldTextBoxOption[]; + position?: number; + model?: 'contact' | 'opportunity'; +} + +// Custom Field Delete Response +export interface GHLCustomFieldDeleteResponse { + succeded: boolean; +} + +// File Upload Body +export interface GHLFileUploadRequest { + id: string; + maxFiles?: string; +} + +// File Upload Response +export interface GHLFileUploadResponse { + uploadedFiles: { [fileName: string]: string }; + meta: any[]; +} + +// CUSTOM VALUES INTERFACES + +// Custom Value Schema +export interface GHLLocationCustomValue { + id: string; + name: string; + fieldKey: string; + value: string; + locationId: string; +} + +// Custom Values Response +export interface GHLLocationCustomValuesResponse { + customValues: GHLLocationCustomValue[]; +} + +// Custom Value Response +export interface GHLLocationCustomValueResponse { + customValue: GHLLocationCustomValue; +} + +// Custom Value Request +export interface GHLCustomValueRequest { + name: string; + value: string; +} + +// Custom Value Delete Response +export interface GHLCustomValueDeleteResponse { + succeded: boolean; +} + +// TEMPLATES INTERFACES + +// SMS Template Schema +export interface GHLSmsTemplate { + body: string; + attachments: any[]; +} + +// Email Template Schema +export interface GHLEmailTemplateContent { + subject: string; + attachments: any[]; + html: string; +} + +// Template Response Schema (SMS) +export interface GHLSmsTemplateResponse { + id: string; + name: string; + type: 'sms'; + template: GHLSmsTemplate; + dateAdded: string; + locationId: string; + urlAttachments: string[]; +} + +// Template Response Schema (Email) +export interface GHLEmailTemplateResponse { + id: string; + name: string; + type: 'email'; + dateAdded: string; + template: GHLEmailTemplateContent; + locationId: string; +} + +// Templates List Response +export interface GHLLocationTemplatesResponse { + templates: (GHLSmsTemplateResponse | GHLEmailTemplateResponse)[]; + totalCount: number; +} + +// MCP TOOL PARAMETERS - Location Operations + +export interface MCPSearchLocationsParams { + companyId?: string; + skip?: number; + limit?: number; + order?: 'asc' | 'desc'; + email?: string; +} + +export interface MCPGetLocationParams { + locationId: string; +} + +export interface MCPCreateLocationParams { + name: string; + companyId: string; + phone?: string; + address?: string; + city?: string; + state?: string; + country?: string; + postalCode?: string; + website?: string; + timezone?: string; + prospectInfo?: GHLLocationProspectInfo; + settings?: GHLLocationSettings; + social?: GHLLocationSocial; + twilio?: GHLLocationTwilio; + mailgun?: GHLLocationMailgun; + snapshotId?: string; +} + +export interface MCPUpdateLocationParams { + locationId: string; + name?: string; + companyId: string; + phone?: string; + address?: string; + city?: string; + state?: string; + country?: string; + postalCode?: string; + website?: string; + timezone?: string; + prospectInfo?: GHLLocationProspectInfo; + settings?: GHLLocationSettings; + social?: GHLLocationSocial; + twilio?: GHLLocationTwilio; + mailgun?: GHLLocationMailgun; + snapshot?: GHLLocationSnapshot; +} + +export interface MCPDeleteLocationParams { + locationId: string; + deleteTwilioAccount: boolean; +} + +// Location Tags MCP Parameters +export interface MCPGetLocationTagsParams { + locationId: string; +} + +export interface MCPCreateLocationTagParams { + locationId: string; + name: string; +} + +export interface MCPGetLocationTagParams { + locationId: string; + tagId: string; +} + +export interface MCPUpdateLocationTagParams { + locationId: string; + tagId: string; + name: string; +} + +export interface MCPDeleteLocationTagParams { + locationId: string; + tagId: string; +} + +// Location Tasks MCP Parameters +export interface MCPSearchLocationTasksParams { + locationId: string; + contactId?: string[]; + completed?: boolean; + assignedTo?: string[]; + query?: string; + limit?: number; + skip?: number; + businessId?: string; +} + +// Custom Fields MCP Parameters +export interface MCPGetCustomFieldsParams { + locationId: string; + model?: 'contact' | 'opportunity' | 'all'; +} + +export interface MCPCreateCustomFieldParams { + locationId: string; + name: string; + dataType: string; + placeholder?: string; + acceptedFormat?: string[]; + isMultipleFile?: boolean; + maxNumberOfFiles?: number; + textBoxListOptions?: GHLCustomFieldTextBoxOption[]; + position?: number; + model?: 'contact' | 'opportunity'; +} + +export interface MCPGetCustomFieldParams { + locationId: string; + customFieldId: string; +} + +export interface MCPUpdateCustomFieldParams { + locationId: string; + customFieldId: string; + name: string; + placeholder?: string; + acceptedFormat?: string[]; + isMultipleFile?: boolean; + maxNumberOfFiles?: number; + textBoxListOptions?: GHLCustomFieldTextBoxOption[]; + position?: number; + model?: 'contact' | 'opportunity'; +} + +export interface MCPDeleteCustomFieldParams { + locationId: string; + customFieldId: string; +} + +export interface MCPUploadCustomFieldFileParams { + locationId: string; + id: string; + maxFiles?: string; +} + +// Custom Values MCP Parameters +export interface MCPGetCustomValuesParams { + locationId: string; +} + +export interface MCPCreateCustomValueParams { + locationId: string; + name: string; + value: string; +} + +export interface MCPGetCustomValueParams { + locationId: string; + customValueId: string; +} + +export interface MCPUpdateCustomValueParams { + locationId: string; + customValueId: string; + name: string; + value: string; +} + +export interface MCPDeleteCustomValueParams { + locationId: string; + customValueId: string; +} + +// Templates MCP Parameters +export interface MCPGetLocationTemplatesParams { + locationId: string; + originId: string; + deleted?: boolean; + skip?: number; + limit?: number; + type?: 'sms' | 'email' | 'whatsapp'; +} + +export interface MCPDeleteLocationTemplateParams { + locationId: string; + templateId: string; +} + +// Timezones MCP Parameters +export interface MCPGetTimezonesParams { + locationId?: string; +} + +// EMAIL ISV (VERIFICATION) API INTERFACES - Based on Email ISV API + +// Email Verification Request Body +export interface GHLEmailVerificationRequest { + type: 'email' | 'contact'; + verify: string; // email address or contact ID +} + +// Lead Connector Recommendation +export interface GHLLeadConnectorRecommendation { + isEmailValid: boolean; +} + +// Email Verification Success Response +export interface GHLEmailVerifiedResponse { + reason?: string[]; + result: 'deliverable' | 'undeliverable' | 'do_not_send' | 'unknown' | 'catch_all'; + risk: 'high' | 'low' | 'medium' | 'unknown'; + address: string; + leadconnectorRecomendation: GHLLeadConnectorRecommendation; +} + +// Email Verification Failed Response +export interface GHLEmailNotVerifiedResponse { + verified: false; + message: string; + address: string; +} + +// Combined Email Verification Response +export type GHLEmailVerificationResponse = GHLEmailVerifiedResponse | GHLEmailNotVerifiedResponse; + +// MCP Tool Parameters - Email ISV Operations +export interface MCPVerifyEmailParams { + locationId: string; + type: 'email' | 'contact'; + verify: string; // email address or contact ID +} + +// ADDITIONAL CONVERSATIONS API INTERFACES - Comprehensive Coverage + +// Email Message Interfaces +export interface GHLEmailMessage { + id: string; + altId?: string; + threadId: string; + locationId: string; + contactId: string; + conversationId: string; + dateAdded: string; + subject?: string; + body: string; + direction: GHLMessageDirection; + status: GHLMessageStatus; + contentType: string; + attachments?: string[]; + provider?: string; + from: string; + to: string[]; + cc?: string[]; + bcc?: string[]; + replyToMessageId?: string; + source?: 'workflow' | 'bulk_actions' | 'campaign' | 'api' | 'app'; + conversationProviderId?: string; +} + +// File Upload Interfaces +export interface GHLUploadFilesRequest { + conversationId: string; + locationId: string; + attachmentUrls: string[]; +} + +export interface GHLUploadFilesResponse { + uploadedFiles: { [fileName: string]: string }; +} + +export interface GHLUploadFilesError { + status: number; + message: string; +} + +// Message Status Update Interfaces +export interface GHLUpdateMessageStatusRequest { + status: 'delivered' | 'failed' | 'pending' | 'read'; + error?: { + code: string; + type: string; + message: string; + }; + emailMessageId?: string; + recipients?: string[]; +} + +// Inbound/Outbound Message Interfaces +export interface GHLProcessInboundMessageRequest { + type: 'SMS' | 'Email' | 'WhatsApp' | 'GMB' | 'IG' | 'FB' | 'Custom' | 'WebChat' | 'Live_Chat' | 'Call'; + attachments?: string[]; + message?: string; + conversationId: string; + conversationProviderId: string; + html?: string; + subject?: string; + emailFrom?: string; + emailTo?: string; + emailCc?: string[]; + emailBcc?: string[]; + emailMessageId?: string; + altId?: string; + direction?: 'outbound' | 'inbound'; + date?: string; + call?: { + to: string; + from: string; + status: 'pending' | 'completed' | 'answered' | 'busy' | 'no-answer' | 'failed' | 'canceled' | 'voicemail'; + }; +} + +export interface GHLProcessOutboundMessageRequest { + type: 'Call'; + attachments?: string[]; + conversationId: string; + conversationProviderId: string; + altId?: string; + date?: string; + call: { + to: string; + from: string; + status: 'pending' | 'completed' | 'answered' | 'busy' | 'no-answer' | 'failed' | 'canceled' | 'voicemail'; + }; +} + +export interface GHLProcessMessageResponse { + success: boolean; + conversationId: string; + messageId: string; + message: string; + contactId?: string; + dateAdded?: string; + emailMessageId?: string; +} + +// Call Recording & Transcription Interfaces +export interface GHLMessageRecordingResponse { + // Binary audio data response - typically audio/x-wav + audioData: ArrayBuffer | Buffer; + contentType: string; + contentDisposition: string; +} + +export interface GHLMessageTranscription { + mediaChannel: number; + sentenceIndex: number; + startTime: number; + endTime: number; + transcript: string; + confidence: number; +} + +export interface GHLMessageTranscriptionResponse { + transcriptions: GHLMessageTranscription[]; +} + +// Live Chat Typing Interfaces +export interface GHLLiveChatTypingRequest { + locationId: string; + isTyping: boolean; + visitorId: string; + conversationId: string; +} + +export interface GHLLiveChatTypingResponse { + success: boolean; +} + +// Scheduled Message Cancellation Interfaces +export interface GHLCancelScheduledResponse { + status: number; + message: string; +} + +// MCP Tool Parameters for new conversation endpoints + +export interface MCPGetEmailMessageParams { + emailMessageId: string; +} + +export interface MCPGetMessageParams { + messageId: string; +} + +export interface MCPUploadMessageAttachmentsParams { + conversationId: string; + attachmentUrls: string[]; +} + +export interface MCPUpdateMessageStatusParams { + messageId: string; + status: 'delivered' | 'failed' | 'pending' | 'read'; + error?: { + code: string; + type: string; + message: string; + }; + emailMessageId?: string; + recipients?: string[]; +} + +export interface MCPAddInboundMessageParams { + type: 'SMS' | 'Email' | 'WhatsApp' | 'GMB' | 'IG' | 'FB' | 'Custom' | 'WebChat' | 'Live_Chat' | 'Call'; + conversationId: string; + conversationProviderId: string; + message?: string; + attachments?: string[]; + html?: string; + subject?: string; + emailFrom?: string; + emailTo?: string; + emailCc?: string[]; + emailBcc?: string[]; + emailMessageId?: string; + altId?: string; + date?: string; + call?: { + to: string; + from: string; + status: 'pending' | 'completed' | 'answered' | 'busy' | 'no-answer' | 'failed' | 'canceled' | 'voicemail'; + }; +} + +export interface MCPAddOutboundCallParams { + conversationId: string; + conversationProviderId: string; + to: string; + from: string; + status: 'pending' | 'completed' | 'answered' | 'busy' | 'no-answer' | 'failed' | 'canceled' | 'voicemail'; + attachments?: string[]; + altId?: string; + date?: string; +} + +export interface MCPGetMessageRecordingParams { + messageId: string; +} + +export interface MCPGetMessageTranscriptionParams { + messageId: string; +} + +export interface MCPDownloadTranscriptionParams { + messageId: string; +} + +export interface MCPCancelScheduledMessageParams { + messageId: string; +} + +export interface MCPCancelScheduledEmailParams { + emailMessageId: string; +} + +export interface MCPLiveChatTypingParams { + visitorId: string; + conversationId: string; + isTyping: boolean; +} + +export interface MCPDeleteConversationParams { + conversationId: string; +} + +// SOCIAL MEDIA POSTING API INTERFACES - Based on Social Media Posting API + +// Platform Types +export type GHLSocialPlatform = 'google' | 'facebook' | 'instagram' | 'linkedin' | 'twitter' | 'tiktok' | 'tiktok-business'; +export type GHLPostStatus = 'in_progress' | 'draft' | 'failed' | 'published' | 'scheduled' | 'in_review' | 'notification_sent' | 'deleted'; +export type GHLPostType = 'post' | 'story' | 'reel'; +export type GHLPostSource = 'composer' | 'csv' | 'recurring' | 'review' | 'rss'; +export type GHLCSVStatus = 'pending' | 'in_progress' | 'completed' | 'failed' | 'in_review' | 'importing' | 'deleted'; +export type GHLAccountType = 'page' | 'group' | 'profile' | 'location' | 'business'; +export type GHLGMBEventType = 'STANDARD' | 'EVENT' | 'OFFER'; +export type GHLGMBActionType = 'none' | 'order' | 'book' | 'shop' | 'learn_more' | 'call' | 'sign_up'; +export type GHLTikTokPrivacyLevel = 'PUBLIC_TO_EVERYONE' | 'MUTUAL_FOLLOW_FRIENDS' | 'SELF_ONLY'; + +// OAuth Start Response Interface +export interface GHLOAuthStartResponse { + success: boolean; + statusCode: number; + message: string; +} + +// Media Interfaces +export interface GHLPostMedia { + url: string; + caption?: string; + type?: string; // MIME type + thumbnail?: string; + defaultThumb?: string; + id?: string; +} + +export interface GHLOgTags { + metaImage?: string; + metaLink?: string; +} + +// User Interface for Posts +export interface GHLPostUser { + id: string; + title?: string; + firstName?: string; + lastName?: string; + profilePhoto?: string; + phone?: string; + email?: string; +} + +// Post Approval Interface +export interface GHLPostApproval { + approver?: string; + requesterNote?: string; + approverNote?: string; + approvalStatus?: 'pending' | 'approved' | 'rejected' | 'not_required'; + approverUser?: GHLPostUser; +} + +// TikTok Post Details +export interface GHLTikTokPostDetails { + privacyLevel?: GHLTikTokPrivacyLevel; + promoteOtherBrand?: boolean; + enableComment?: boolean; + enableDuet?: boolean; + enableStitch?: boolean; + videoDisclosure?: boolean; + promoteYourBrand?: boolean; +} + +// GMB Post Details +export interface GHLGMBPostDetails { + gmbEventType?: GHLGMBEventType; + title?: string; + offerTitle?: string; + startDate?: { + startDate?: { year: number; month: number; day: number }; + startTime?: { hours: number; minutes: number; seconds: number }; + }; + endDate?: { + endDate?: { year: number; month: number; day: number }; + endTime?: { hours: number; minutes: number; seconds: number }; + }; + termsConditions?: string; + url?: string; + couponCode?: string; + redeemOnlineUrl?: string; + actionType?: GHLGMBActionType; +} + +// Post Interface +export interface GHLSocialPost { + _id: string; + source: GHLPostSource; + locationId: string; + platform: GHLSocialPlatform; + displayDate?: string; + createdAt: string; + updatedAt: string; + accountId?: string; + accountIds: string[]; + error?: string; + postId?: string; + publishedAt?: string; + summary: string; + media?: GHLPostMedia[]; + status: GHLPostStatus; + createdBy?: string; + type: GHLPostType; + tags?: string[]; + ogTagsDetails?: GHLOgTags; + postApprovalDetails?: GHLPostApproval; + tiktokPostDetails?: GHLTikTokPostDetails; + gmbPostDetails?: GHLGMBPostDetails; + user?: GHLPostUser; + followUpComment?: string; +} + +// Account Interface +export interface GHLSocialAccount { + id: string; + oauthId?: string; + profileId?: string; + name: string; + platform: GHLSocialPlatform; + type: GHLAccountType; + expire?: string; + isExpired?: boolean; + meta?: any; + avatar?: string; + originId?: string; + locationId?: string; + active?: boolean; + deleted?: boolean; + createdAt?: string; + updatedAt?: string; +} + +// Group Interface +export interface GHLSocialGroup { + id: string; + name: string; + accountIds: string[]; +} + +// Category Interface +export interface GHLSocialCategory { + _id: string; + name: string; + primaryColor?: string; + secondaryColor?: string; + locationId: string; + createdBy?: string; + deleted: boolean; + createdAt?: string; + updatedAt?: string; +} + +// Tag Interface +export interface GHLSocialTag { + _id: string; + tag: string; + locationId: string; + createdBy?: string; + deleted?: boolean; + createdAt?: string; + updatedAt?: string; +} + +// CSV Import Interface +export interface GHLCSVImport { + _id: string; + locationId: string; + fileName: string; + accountIds: string[]; + file: string; + status: GHLCSVStatus; + count: number; + createdBy?: string; + traceId?: string; + originId?: string; + approver?: string; + createdAt: string; +} + +// Request Interfaces + +// Search Posts Request +export interface GHLSearchPostsRequest { + type?: 'recent' | 'all' | 'scheduled' | 'draft' | 'failed' | 'in_review' | 'published' | 'in_progress' | 'deleted'; + accounts?: string; // Comma-separated account IDs + skip?: string; + limit?: string; + fromDate: string; + toDate: string; + includeUsers: string; + postType?: GHLPostType; +} + +// Create/Update Post Request +export interface GHLCreatePostRequest { + accountIds: string[]; + summary: string; + media?: GHLPostMedia[]; + status?: GHLPostStatus; + scheduleDate?: string; + createdBy?: string; + followUpComment?: string; + ogTagsDetails?: GHLOgTags; + type: GHLPostType; + postApprovalDetails?: GHLPostApproval; + scheduleTimeUpdated?: boolean; + tags?: string[]; + categoryId?: string; + tiktokPostDetails?: GHLTikTokPostDetails; + gmbPostDetails?: GHLGMBPostDetails; + userId?: string; +} + +export interface GHLUpdatePostRequest extends Partial {} + +// Bulk Delete Request +export interface GHLBulkDeletePostsRequest { + postIds: string[]; +} + +// CSV Upload Request +export interface GHLUploadCSVRequest { + file: any; // File upload +} + +// Set Accounts Request +export interface GHLSetAccountsRequest { + accountIds: string[]; + filePath: string; + rowsCount: number; + fileName: string; + approver?: string; + userId?: string; +} + +// CSV Finalize Request +export interface GHLCSVFinalizeRequest { + userId?: string; +} + +// Tag Search Request +export interface GHLGetTagsByIdsRequest { + tagIds: string[]; +} + +// OAuth Platform Account Interfaces +export interface GHLGoogleLocation { + name: string; + storeCode?: string; + title: string; + metadata?: any; + storefrontAddress?: any; + relationshipData?: any; + maxLocation?: boolean; + isVerified?: boolean; + isConnected?: boolean; +} + +export interface GHLGoogleAccount { + name: string; + accountName: string; + type: string; + verificationState?: string; + vettedState?: string; +} + +export interface GHLFacebookPage { + id: string; + name: string; + avatar?: string; + isOwned?: boolean; + isConnected?: boolean; +} + +export interface GHLInstagramAccount { + id: string; + name: string; + avatar?: string; + pageId?: string; + isConnected?: boolean; +} + +export interface GHLLinkedInPage { + id: string; + name: string; + avatar?: string; + urn?: string; + isConnected?: boolean; +} + +export interface GHLLinkedInProfile { + id: string; + name: string; + avatar?: string; + urn?: string; + isConnected?: boolean; +} + +export interface GHLTwitterProfile { + id: string; + name: string; + username?: string; + avatar?: string; + protected?: boolean; + verified?: boolean; + isConnected?: boolean; +} + +export interface GHLTikTokProfile { + id: string; + name: string; + username?: string; + avatar?: string; + verified?: boolean; + isConnected?: boolean; + type?: 'business' | 'profile'; +} + +// OAuth Attach Requests +export interface GHLAttachGMBLocationRequest { + location: any; + account: any; + companyId?: string; +} + +export interface GHLAttachFBAccountRequest { + type: 'page'; + originId: string; + name: string; + avatar?: string; + companyId?: string; +} + +export interface GHLAttachIGAccountRequest { + originId: string; + name: string; + avatar?: string; + pageId: string; + companyId?: string; +} + +export interface GHLAttachLinkedInAccountRequest { + type: GHLAccountType; + originId: string; + name: string; + avatar?: string; + urn?: string; + companyId?: string; +} + +export interface GHLAttachTwitterAccountRequest { + originId: string; + name: string; + username?: string; + avatar?: string; + protected?: boolean; + verified?: boolean; + companyId?: string; +} + +export interface GHLAttachTikTokAccountRequest { + type: GHLAccountType; + originId: string; + name: string; + avatar?: string; + verified?: boolean; + username?: string; + companyId?: string; +} + +// Response Interfaces +export interface GHLSearchPostsResponse { + posts: GHLSocialPost[]; + count: number; +} + +export interface GHLGetPostResponse { + post: GHLSocialPost; +} + +export interface GHLCreatePostResponse { + post: GHLSocialPost; +} + +export interface GHLBulkDeleteResponse { + message: string; + deletedCount: number; +} + +export interface GHLGetAccountsResponse { + accounts: GHLSocialAccount[]; + groups: GHLSocialGroup[]; +} + +export interface GHLUploadCSVResponse { + filePath: string; + rowsCount: number; + fileName: string; +} + +export interface GHLGetUploadStatusResponse { + csvs: GHLCSVImport[]; + count: number; +} + +export interface GHLGetCategoriesResponse { + categories: GHLSocialCategory[]; + count: number; +} + +export interface GHLGetCategoryResponse { + category: GHLSocialCategory; +} + +export interface GHLGetTagsResponse { + tags: GHLSocialTag[]; + count: number; +} + +export interface GHLGetTagsByIdsResponse { + tags: GHLSocialTag[]; + count: number; +} + +// OAuth Response Interfaces +export interface GHLGetGoogleLocationsResponse { + locations: { + location: GHLGoogleLocation; + account: GHLGoogleAccount; + }; +} + +export interface GHLGetFacebookPagesResponse { + pages: GHLFacebookPage[]; +} + +export interface GHLGetInstagramAccountsResponse { + accounts: GHLInstagramAccount[]; +} + +export interface GHLGetLinkedInAccountsResponse { + pages: GHLLinkedInPage[]; + profile: GHLLinkedInProfile[]; +} + +export interface GHLGetTwitterAccountsResponse { + profile: GHLTwitterProfile[]; +} + +export interface GHLGetTikTokAccountsResponse { + profile: GHLTikTokProfile[]; +} + +// MCP Tool Parameters - Social Media Operations + +export interface MCPSearchPostsParams { + type?: 'recent' | 'all' | 'scheduled' | 'draft' | 'failed' | 'in_review' | 'published' | 'in_progress' | 'deleted'; + accounts?: string; + skip?: number; + limit?: number; + fromDate: string; + toDate: string; + includeUsers?: boolean; + postType?: GHLPostType; +} + +export interface MCPCreatePostParams { + accountIds: string[]; + summary: string; + media?: GHLPostMedia[]; + status?: GHLPostStatus; + scheduleDate?: string; + followUpComment?: string; + type: GHLPostType; + tags?: string[]; + categoryId?: string; + tiktokPostDetails?: GHLTikTokPostDetails; + gmbPostDetails?: GHLGMBPostDetails; + userId?: string; +} + +export interface MCPGetPostParams { + postId: string; +} + +export interface MCPUpdatePostParams { + postId: string; + accountIds?: string[]; + summary?: string; + media?: GHLPostMedia[]; + status?: GHLPostStatus; + scheduleDate?: string; + followUpComment?: string; + type?: GHLPostType; + tags?: string[]; + categoryId?: string; + tiktokPostDetails?: GHLTikTokPostDetails; + gmbPostDetails?: GHLGMBPostDetails; + userId?: string; +} + +export interface MCPDeletePostParams { + postId: string; +} + +export interface MCPBulkDeletePostsParams { + postIds: string[]; +} + +export interface MCPGetAccountsParams { + // No additional params - uses location from config +} + +export interface MCPDeleteAccountParams { + accountId: string; + companyId?: string; + userId?: string; +} + +export interface MCPUploadCSVParams { + file: any; +} + +export interface MCPGetUploadStatusParams { + skip?: number; + limit?: number; + includeUsers?: boolean; + userId?: string; +} + +export interface MCPSetAccountsParams { + accountIds: string[]; + filePath: string; + rowsCount: number; + fileName: string; + approver?: string; + userId?: string; +} + +export interface MCPGetCSVPostParams { + csvId: string; + skip?: number; + limit?: number; +} + +export interface MCPFinalizeCSVParams { + csvId: string; + userId?: string; +} + +export interface MCPDeleteCSVParams { + csvId: string; +} + +export interface MCPDeleteCSVPostParams { + csvId: string; + postId: string; +} + +export interface MCPGetCategoriesParams { + searchText?: string; + limit?: number; + skip?: number; +} + +export interface MCPGetCategoryParams { + categoryId: string; +} + +export interface MCPGetTagsParams { + searchText?: string; + limit?: number; + skip?: number; +} + +export interface MCPGetTagsByIdsParams { + tagIds: string[]; +} + +// OAuth MCP Parameters +export interface MCPStartOAuthParams { + platform: GHLSocialPlatform; + userId: string; + page?: string; + reconnect?: boolean; +} + +export interface MCPGetOAuthAccountsParams { + platform: GHLSocialPlatform; + accountId: string; +} + +export interface MCPAttachOAuthAccountParams { + platform: GHLSocialPlatform; + accountId: string; + attachData: any; // Platform-specific attach data +} + +// ==== MISSING CALENDAR API TYPES ==== + +// Calendar Groups Management Types +export interface GHLValidateGroupSlugRequest { + locationId: string; + slug: string; +} + +export interface GHLValidateGroupSlugResponse { + available: boolean; +} + +export interface GHLUpdateCalendarGroupRequest { + name: string; + description: string; + slug: string; +} + +export interface GHLGroupStatusUpdateRequest { + isActive: boolean; +} + +export interface GHLGroupSuccessResponse { + success: boolean; +} + +// Appointment Notes Types +export interface GHLAppointmentNote { + id: string; + body: string; + userId: string; + dateAdded: string; + contactId: string; + createdBy: { + id: string; + name: string; + }; +} + +export interface GHLGetAppointmentNotesResponse { + notes: GHLAppointmentNote[]; + hasMore: boolean; +} + +export interface GHLCreateAppointmentNoteRequest { + userId?: string; + body: string; +} + +export interface GHLUpdateAppointmentNoteRequest { + userId?: string; + body: string; +} + +export interface GHLAppointmentNoteResponse { + note: GHLAppointmentNote; +} + +export interface GHLDeleteAppointmentNoteResponse { + success: boolean; +} + +// Calendar Resources Types +export interface GHLCalendarResource { + id: string; + locationId: string; + name: string; + resourceType: 'equipments' | 'rooms'; + isActive: boolean; + description?: string; + quantity?: number; + outOfService?: number; + capacity?: number; + calendarIds: string[]; +} + +export interface GHLCreateCalendarResourceRequest { + locationId: string; + name: string; + description: string; + quantity: number; + outOfService: number; + capacity: number; + calendarIds: string[]; +} + +export interface GHLUpdateCalendarResourceRequest { + locationId?: string; + name?: string; + description?: string; + quantity?: number; + outOfService?: number; + capacity?: number; + calendarIds?: string[]; + isActive?: boolean; +} + +export interface GHLCalendarResourceResponse { + locationId: string; + name: string; + resourceType: 'equipments' | 'rooms'; + isActive: boolean; + description?: string; + quantity?: number; + outOfService?: number; + capacity?: number; +} + +export interface GHLCalendarResourceByIdResponse { + locationId: string; + name: string; + resourceType: 'equipments' | 'rooms'; + isActive: boolean; + description?: string; + quantity?: number; + outOfService?: number; + capacity?: number; + calendarIds: string[]; +} + +export interface GHLResourceDeleteResponse { + success: boolean; +} + +export interface GHLGetCalendarResourcesRequest { + locationId: string; + limit: number; + skip: number; +} + +// Calendar Notifications Types +export interface GHLScheduleDTO { + timeOffset: number; + unit: string; +} + +export interface GHLCalendarNotification { + _id: string; + altType: 'calendar'; + calendarId: string; + receiverType: 'contact' | 'guest' | 'assignedUser' | 'emails'; + additionalEmailIds?: string[]; + channel: 'email' | 'inApp'; + notificationType: 'booked' | 'confirmation' | 'cancellation' | 'reminder' | 'followup' | 'reschedule'; + isActive: boolean; + templateId?: string; + body?: string; + subject?: string; + afterTime?: GHLScheduleDTO[]; + beforeTime?: GHLScheduleDTO[]; + selectedUsers?: string[]; + deleted: boolean; +} + +export interface GHLCreateCalendarNotificationRequest { + receiverType: 'contact' | 'guest' | 'assignedUser' | 'emails'; + channel: 'email' | 'inApp'; + notificationType: 'booked' | 'confirmation' | 'cancellation' | 'reminder' | 'followup' | 'reschedule'; + isActive?: boolean; + templateId?: string; + body?: string; + subject?: string; + afterTime?: GHLScheduleDTO[]; + beforeTime?: GHLScheduleDTO[]; + additionalEmailIds?: string[]; + selectedUsers?: string[]; + fromAddress?: string; + fromName?: string; +} + +export interface GHLUpdateCalendarNotificationRequest { + altType?: 'calendar'; + altId?: string; + receiverType?: 'contact' | 'guest' | 'assignedUser' | 'emails'; + additionalEmailIds?: string[]; + channel?: 'email' | 'inApp'; + notificationType?: 'booked' | 'confirmation' | 'cancellation' | 'reminder' | 'followup' | 'reschedule'; + isActive?: boolean; + deleted?: boolean; + templateId?: string; + body?: string; + subject?: string; + afterTime?: GHLScheduleDTO[]; + beforeTime?: GHLScheduleDTO[]; + fromAddress?: string; + fromName?: string; +} + +export interface GHLCalendarNotificationDeleteResponse { + message: string; +} + +export interface GHLGetCalendarNotificationsRequest { + altType?: 'calendar'; + altId?: string; + isActive?: boolean; + deleted?: boolean; + limit?: number; + skip?: number; +} + +// Blocked Slots Types +export interface GHLGetBlockedSlotsRequest { + locationId: string; + userId?: string; + calendarId?: string; + groupId?: string; + startTime: string; + endTime: string; +} + +// MCP Parameters for Missing Calendar Endpoints + +// Calendar Groups Management Parameters +export interface MCPCreateCalendarGroupParams { + name: string; + description: string; + slug: string; + isActive?: boolean; +} + +export interface MCPValidateGroupSlugParams { + slug: string; + locationId?: string; +} + +export interface MCPUpdateCalendarGroupParams { + groupId: string; + name: string; + description: string; + slug: string; +} + +export interface MCPDeleteCalendarGroupParams { + groupId: string; +} + +export interface MCPDisableCalendarGroupParams { + groupId: string; + isActive: boolean; +} + +// Appointment Notes Parameters +export interface MCPGetAppointmentNotesParams { + appointmentId: string; + limit: number; + offset: number; +} + +export interface MCPCreateAppointmentNoteParams { + appointmentId: string; + body: string; + userId?: string; +} + +export interface MCPUpdateAppointmentNoteParams { + appointmentId: string; + noteId: string; + body: string; + userId?: string; +} + +export interface MCPDeleteAppointmentNoteParams { + appointmentId: string; + noteId: string; +} + +// Calendar Resources Parameters +export interface MCPGetCalendarResourcesParams { + resourceType: 'equipments' | 'rooms'; + limit: number; + skip: number; + locationId?: string; +} + +export interface MCPCreateCalendarResourceParams { + resourceType: 'equipments' | 'rooms'; + name: string; + description: string; + quantity: number; + outOfService: number; + capacity: number; + calendarIds: string[]; + locationId?: string; +} + +export interface MCPGetCalendarResourceParams { + resourceType: 'equipments' | 'rooms'; + resourceId: string; +} + +export interface MCPUpdateCalendarResourceParams { + resourceType: 'equipments' | 'rooms'; + resourceId: string; + name?: string; + description?: string; + quantity?: number; + outOfService?: number; + capacity?: number; + calendarIds?: string[]; + isActive?: boolean; +} + +export interface MCPDeleteCalendarResourceParams { + resourceType: 'equipments' | 'rooms'; + resourceId: string; +} + +// Calendar Notifications Parameters +export interface MCPGetCalendarNotificationsParams { + calendarId: string; + altType?: 'calendar'; + altId?: string; + isActive?: boolean; + deleted?: boolean; + limit?: number; + skip?: number; +} + +export interface MCPCreateCalendarNotificationParams { + calendarId: string; + notifications: GHLCreateCalendarNotificationRequest[]; +} + +export interface MCPGetCalendarNotificationParams { + calendarId: string; + notificationId: string; +} + +export interface MCPUpdateCalendarNotificationParams { + calendarId: string; + notificationId: string; + receiverType?: 'contact' | 'guest' | 'assignedUser' | 'emails'; + additionalEmailIds?: string[]; + channel?: 'email' | 'inApp'; + notificationType?: 'booked' | 'confirmation' | 'cancellation' | 'reminder' | 'followup' | 'reschedule'; + isActive?: boolean; + deleted?: boolean; + templateId?: string; + body?: string; + subject?: string; + afterTime?: GHLScheduleDTO[]; + beforeTime?: GHLScheduleDTO[]; + fromAddress?: string; + fromName?: string; +} + +export interface MCPDeleteCalendarNotificationParams { + calendarId: string; + notificationId: string; +} + +// Blocked Slots Parameters +export interface MCPGetBlockedSlotsParams { + userId?: string; + calendarId?: string; + groupId?: string; + startTime: string; + endTime: string; +} + +// ==== MEDIA LIBRARY API TYPES ==== + +// Media File Types +export interface GHLMediaFile { + id?: string; + altId: string; + altType: 'location' | 'agency'; + name: string; + parentId?: string; + url: string; + path: string; + type?: 'file' | 'folder'; + size?: number; + mimeType?: string; + createdAt?: string; + updatedAt?: string; +} + +// API Request/Response Types +export interface GHLGetMediaFilesRequest { + offset?: number; + limit?: number; + sortBy: string; + sortOrder: 'asc' | 'desc'; + type?: 'file' | 'folder'; + query?: string; + altType: 'location' | 'agency'; + altId: string; + parentId?: string; +} + +export interface GHLGetMediaFilesResponse { + files: GHLMediaFile[]; + total?: number; + hasMore?: boolean; +} + +export interface GHLUploadMediaFileRequest { + file?: any; // Binary file data + hosted?: boolean; + fileUrl?: string; + name?: string; + parentId?: string; + altType?: 'location' | 'agency'; + altId?: string; +} + +export interface GHLUploadMediaFileResponse { + fileId: string; + url?: string; + name?: string; + size?: number; + mimeType?: string; +} + +export interface GHLDeleteMediaRequest { + id: string; + altType: 'location' | 'agency'; + altId: string; +} + +export interface GHLDeleteMediaResponse { + success: boolean; + message?: string; +} + +// MCP Parameters for Media Library Endpoints +export interface MCPGetMediaFilesParams { + offset?: number; + limit?: number; + sortBy?: string; + sortOrder?: 'asc' | 'desc'; + type?: 'file' | 'folder'; + query?: string; + altType?: 'location' | 'agency'; + altId?: string; + parentId?: string; +} + +export interface MCPUploadMediaFileParams { + file?: any; + hosted?: boolean; + fileUrl?: string; + name?: string; + parentId?: string; + altType?: 'location' | 'agency'; + altId?: string; +} + +export interface MCPDeleteMediaParams { + id: string; + altType?: 'location' | 'agency'; + altId?: string; +} + +// ===== CUSTOM OBJECTS API TYPES ===== + +// Object Schema Types +export interface GHLCustomObjectLabel { + singular: string; + plural: string; +} + +export interface GHLCustomObjectDisplayProperty { + key: string; + name: string; + dataType: string; +} + +export interface GHLCustomFieldOption { + key: string; + label: string; + url?: string; +} + +export interface GHLCustomField { + locationId: string; + name: string; + description?: string; + placeholder?: string; + showInForms: boolean; + options?: GHLCustomFieldOption[]; + acceptedFormats?: '.pdf' | '.docx' | '.doc' | '.jpg' | '.jpeg' | '.png' | '.gif' | '.csv' | '.xlsx' | '.xls' | 'all'; + id: string; + objectKey: string; + dataType: 'TEXT' | 'LARGE_TEXT' | 'NUMERICAL' | 'PHONE' | 'MONETORY' | 'CHECKBOX' | 'SINGLE_OPTIONS' | 'MULTIPLE_OPTIONS' | 'DATE' | 'TEXTBOX_LIST' | 'FILE_UPLOAD' | 'RADIO'; + parentId: string; + fieldKey: string; + allowCustomOption?: boolean; + maxFileLimit?: number; + dateAdded: string; + dateUpdated: string; +} + +export interface GHLCustomObjectSchema { + id: string; + standard: boolean; + key: string; + labels: GHLCustomObjectLabel; + description?: string; + locationId: string; + primaryDisplayProperty: string; + dateAdded: string; + dateUpdated: string; + type?: any; +} + +export interface GHLObjectRecord { + id: string; + owner: string[]; + followers: string[]; + properties: Record; + dateAdded: string; + dateUpdated: string; +} + +export interface GHLCreatedByMeta { + channel: string; + createdAt: string; + source: string; + sourceId: string; +} + +export interface GHLDetailedObjectRecord { + id: string; + owner: string[]; + followers: string[]; + properties: Record; + createdAt: string; + updatedAt: string; + locationId: string; + objectId: string; + objectKey: string; + createdBy: GHLCreatedByMeta; + lastUpdatedBy: GHLCreatedByMeta; + searchAfter: (string | number)[]; +} + +// Request Types +export interface GHLGetObjectSchemaRequest { + key: string; + locationId: string; + fetchProperties?: boolean; +} + +export interface GHLCreateObjectSchemaRequest { + labels: GHLCustomObjectLabel; + key: string; + description?: string; + locationId: string; + primaryDisplayPropertyDetails: GHLCustomObjectDisplayProperty; +} + +export interface GHLUpdateObjectSchemaRequest { + labels?: Partial; + description?: string; + locationId: string; + searchableProperties: string[]; +} + +export interface GHLCreateObjectRecordRequest { + locationId: string; + properties: Record; + owner?: string[]; + followers?: string[]; +} + +export interface GHLUpdateObjectRecordRequest { + locationId: string; + properties?: Record; + owner?: string[]; + followers?: string[]; +} + +export interface GHLSearchObjectRecordsRequest { + locationId: string; + page: number; + pageLimit: number; + query: string; + searchAfter: string[]; +} + +// Response Types +export interface GHLGetObjectSchemaResponse { + object: GHLCustomObjectSchema; + cache: boolean; + fields?: GHLCustomField[]; +} + +export interface GHLObjectListResponse { + objects: GHLCustomObjectSchema[]; +} + +export interface GHLObjectSchemaResponse { + object: GHLCustomObjectSchema; +} + +export interface GHLObjectRecordResponse { + record: GHLObjectRecord; +} + +export interface GHLDetailedObjectRecordResponse { + record: GHLDetailedObjectRecord; +} + +export interface GHLObjectRecordDeleteResponse { + id: string; + success: boolean; +} + +export interface GHLSearchObjectRecordsResponse { + records: GHLDetailedObjectRecord[]; + total: number; +} + +// MCP Parameter Interfaces +export interface MCPGetObjectSchemaParams { + key: string; + locationId?: string; + fetchProperties?: boolean; +} + +export interface MCPGetAllObjectsParams { + locationId?: string; +} + +export interface MCPCreateObjectSchemaParams { + labels: GHLCustomObjectLabel; + key: string; + description?: string; + locationId?: string; + primaryDisplayPropertyDetails: GHLCustomObjectDisplayProperty; +} + +export interface MCPUpdateObjectSchemaParams { + key: string; + labels?: Partial; + description?: string; + locationId?: string; + searchableProperties: string[]; +} + +export interface MCPCreateObjectRecordParams { + schemaKey: string; + properties: Record; + locationId?: string; + owner?: string[]; + followers?: string[]; +} + +export interface MCPGetObjectRecordParams { + schemaKey: string; + recordId: string; +} + +export interface MCPUpdateObjectRecordParams { + schemaKey: string; + recordId: string; + properties?: Record; + locationId?: string; + owner?: string[]; + followers?: string[]; +} + +export interface MCPDeleteObjectRecordParams { + schemaKey: string; + recordId: string; +} + +export interface MCPSearchObjectRecordsParams { + schemaKey: string; + locationId?: string; + page?: number; + pageLimit?: number; + query: string; + searchAfter?: string[]; +} + +// ===== ASSOCIATIONS API TYPES ===== + +// Association Types +export interface GHLAssociation { + locationId: string; + id: string; + key: string; + firstObjectLabel: any; + firstObjectKey: any; + secondObjectLabel: any; + secondObjectKey: any; + associationType: 'USER_DEFINED' | 'SYSTEM_DEFINED'; +} + +export interface GHLRelation { + id: string; + associationId: string; + firstRecordId: string; + secondRecordId: string; + locationId: string; +} + +// Request Types +export interface GHLCreateAssociationRequest { + locationId: string; + key: string; + firstObjectLabel: any; + firstObjectKey: any; + secondObjectLabel: any; + secondObjectKey: any; +} + +export interface GHLUpdateAssociationRequest { + firstObjectLabel: any; + secondObjectLabel: any; +} + +export interface GHLCreateRelationRequest { + locationId: string; + associationId: string; + firstRecordId: string; + secondRecordId: string; +} + +export interface GHLGetAssociationsRequest { + locationId: string; + skip: number; + limit: number; +} + +export interface GHLGetRelationsByRecordRequest { + recordId: string; + locationId: string; + skip: number; + limit: number; + associationIds?: string[]; +} + +export interface GHLGetAssociationByKeyRequest { + keyName: string; + locationId: string; +} + +export interface GHLGetAssociationByObjectKeyRequest { + objectKey: string; + locationId?: string; +} + +export interface GHLDeleteRelationRequest { + relationId: string; + locationId: string; +} + +// Response Types +export interface GHLAssociationResponse { + locationId: string; + id: string; + key: string; + firstObjectLabel: any; + firstObjectKey: any; + secondObjectLabel: any; + secondObjectKey: any; + associationType: 'USER_DEFINED' | 'SYSTEM_DEFINED'; +} + +export interface GHLDeleteAssociationResponse { + deleted: boolean; + id: string; + message: string; +} + +export interface GHLGetAssociationsResponse { + associations: GHLAssociation[]; + total?: number; +} + +export interface GHLGetRelationsResponse { + relations: GHLRelation[]; + total?: number; +} + +// MCP Parameter Interfaces +export interface MCPCreateAssociationParams { + locationId?: string; + key: string; + firstObjectLabel: any; + firstObjectKey: any; + secondObjectLabel: any; + secondObjectKey: any; +} + +export interface MCPUpdateAssociationParams { + associationId: string; + firstObjectLabel: any; + secondObjectLabel: any; +} + +export interface MCPGetAllAssociationsParams { + locationId?: string; + skip?: number; + limit?: number; +} + +export interface MCPGetAssociationByIdParams { + associationId: string; +} + +export interface MCPGetAssociationByKeyParams { + keyName: string; + locationId?: string; +} + +export interface MCPGetAssociationByObjectKeyParams { + objectKey: string; + locationId?: string; +} + +export interface MCPDeleteAssociationParams { + associationId: string; +} + +export interface MCPCreateRelationParams { + locationId?: string; + associationId: string; + firstRecordId: string; + secondRecordId: string; +} + +export interface MCPGetRelationsByRecordParams { + recordId: string; + locationId?: string; + skip?: number; + limit?: number; + associationIds?: string[]; +} + +export interface MCPDeleteRelationParams { + relationId: string; + locationId?: string; +} + +// ===== CUSTOM FIELDS V2 API TYPES ===== + +// Custom Field V2 Option Types +export interface GHLV2CustomFieldOption { + key: string; + label: string; + url?: string; // Optional, valid only for RADIO type +} + +// Custom Field V2 Types +export interface GHLV2CustomField { + locationId: string; + name?: string; + description?: string; + placeholder?: string; + showInForms: boolean; + options?: GHLV2CustomFieldOption[]; + acceptedFormats?: '.pdf' | '.docx' | '.doc' | '.jpg' | '.jpeg' | '.png' | '.gif' | '.csv' | '.xlsx' | '.xls' | 'all'; + id: string; + objectKey: string; + dataType: 'TEXT' | 'LARGE_TEXT' | 'NUMERICAL' | 'PHONE' | 'MONETORY' | 'CHECKBOX' | 'SINGLE_OPTIONS' | 'MULTIPLE_OPTIONS' | 'DATE' | 'TEXTBOX_LIST' | 'FILE_UPLOAD' | 'RADIO' | 'EMAIL'; + parentId: string; + fieldKey: string; + allowCustomOption?: boolean; + maxFileLimit?: number; + dateAdded: string; + dateUpdated: string; +} + +export interface GHLV2CustomFieldFolder { + id: string; + objectKey: string; + locationId: string; + name: string; +} + +// Request Types +export interface GHLV2CreateCustomFieldRequest { + locationId: string; + name?: string; + description?: string; + placeholder?: string; + showInForms: boolean; + options?: GHLV2CustomFieldOption[]; + acceptedFormats?: '.pdf' | '.docx' | '.doc' | '.jpg' | '.jpeg' | '.png' | '.gif' | '.csv' | '.xlsx' | '.xls' | 'all'; + dataType: 'TEXT' | 'LARGE_TEXT' | 'NUMERICAL' | 'PHONE' | 'MONETORY' | 'CHECKBOX' | 'SINGLE_OPTIONS' | 'MULTIPLE_OPTIONS' | 'DATE' | 'TEXTBOX_LIST' | 'FILE_UPLOAD' | 'RADIO' | 'EMAIL'; + fieldKey: string; + objectKey: string; + maxFileLimit?: number; + allowCustomOption?: boolean; + parentId: string; +} + +export interface GHLV2UpdateCustomFieldRequest { + locationId: string; + name?: string; + description?: string; + placeholder?: string; + showInForms: boolean; + options?: GHLV2CustomFieldOption[]; + acceptedFormats?: '.pdf' | '.docx' | '.doc' | '.jpg' | '.jpeg' | '.png' | '.gif' | '.csv' | '.xlsx' | '.xls' | 'all'; + maxFileLimit?: number; +} + +export interface GHLV2CreateCustomFieldFolderRequest { + objectKey: string; + name: string; + locationId: string; +} + +export interface GHLV2UpdateCustomFieldFolderRequest { + name: string; + locationId: string; +} + +export interface GHLV2GetCustomFieldsByObjectKeyRequest { + objectKey: string; + locationId: string; +} + +export interface GHLV2DeleteCustomFieldFolderRequest { + id: string; + locationId: string; +} + +// Response Types +export interface GHLV2CustomFieldResponse { + field: GHLV2CustomField; +} + +export interface GHLV2CustomFieldsResponse { + fields: GHLV2CustomField[]; + folders: GHLV2CustomFieldFolder[]; +} + +export interface GHLV2CustomFieldFolderResponse { + id: string; + objectKey: string; + locationId: string; + name: string; +} + +export interface GHLV2DeleteCustomFieldResponse { + succeded: boolean; + id: string; + key: string; +} + +// MCP Parameter Interfaces +export interface MCPV2CreateCustomFieldParams { + locationId?: string; + name?: string; + description?: string; + placeholder?: string; + showInForms?: boolean; + options?: GHLV2CustomFieldOption[]; + acceptedFormats?: '.pdf' | '.docx' | '.doc' | '.jpg' | '.jpeg' | '.png' | '.gif' | '.csv' | '.xlsx' | '.xls' | 'all'; + dataType: 'TEXT' | 'LARGE_TEXT' | 'NUMERICAL' | 'PHONE' | 'MONETORY' | 'CHECKBOX' | 'SINGLE_OPTIONS' | 'MULTIPLE_OPTIONS' | 'DATE' | 'TEXTBOX_LIST' | 'FILE_UPLOAD' | 'RADIO' | 'EMAIL'; + fieldKey: string; + objectKey: string; + maxFileLimit?: number; + allowCustomOption?: boolean; + parentId: string; +} + +export interface MCPV2UpdateCustomFieldParams { + id: string; + locationId?: string; + name?: string; + description?: string; + placeholder?: string; + showInForms?: boolean; + options?: GHLV2CustomFieldOption[]; + acceptedFormats?: '.pdf' | '.docx' | '.doc' | '.jpg' | '.jpeg' | '.png' | '.gif' | '.csv' | '.xlsx' | '.xls' | 'all'; + maxFileLimit?: number; +} + +export interface MCPV2GetCustomFieldByIdParams { + id: string; +} + +export interface MCPV2DeleteCustomFieldParams { + id: string; +} + +export interface MCPV2GetCustomFieldsByObjectKeyParams { + objectKey: string; + locationId?: string; +} + +export interface MCPV2CreateCustomFieldFolderParams { + objectKey: string; + name: string; + locationId?: string; +} + +export interface MCPV2UpdateCustomFieldFolderParams { + id: string; + name: string; + locationId?: string; +} + +export interface MCPV2DeleteCustomFieldFolderParams { + id: string; + locationId?: string; +} + +// ===== WORKFLOWS API TYPES ===== + +// Request Types +export interface GHLGetWorkflowsRequest { + locationId: string; +} + +// Response Types +export interface GHLGetWorkflowsResponse { + workflows: GHLWorkflow[]; +} + +// MCP Parameter Interfaces +export interface MCPGetWorkflowsParams { + locationId?: string; +} + +// ===== SURVEYS API TYPES ===== + +// Survey Types +export interface GHLSurvey { + id: string; + name: string; + locationId: string; +} + +// Survey Submission Types +export interface GHLSurveyPageDetails { + url: string; + title: string; +} + +export interface GHLSurveyContactSessionIds { + ids: string[] | null; +} + +export interface GHLSurveyEventData { + fbc?: string; + fbp?: string; + page?: GHLSurveyPageDetails; + type?: string; + domain?: string; + medium?: string; + source?: string; + version?: string; + adSource?: string; + mediumId?: string; + parentId?: string; + referrer?: string; + fbEventId?: string; + timestamp?: number; + parentName?: string; + fingerprint?: string; + pageVisitType?: string; + contactSessionIds?: GHLSurveyContactSessionIds | null; +} + +export interface GHLSurveySubmissionOthers { + __submissions_other_field__?: string; + __custom_field_id__?: string; + eventData?: GHLSurveyEventData; + fieldsOriSequance?: string[]; +} + +export interface GHLSurveySubmission { + id: string; + contactId: string; + createdAt: string; + surveyId: string; + name: string; + email: string; + others?: GHLSurveySubmissionOthers; +} + +export interface GHLSurveySubmissionMeta { + total: number; + currentPage: number; + nextPage: number | null; + prevPage: number | null; +} + +// Request Types +export interface GHLGetSurveysRequest { + locationId: string; + skip?: number; + limit?: number; + type?: string; +} + +export interface GHLGetSurveySubmissionsRequest { + locationId: string; + page?: number; + limit?: number; + surveyId?: string; + q?: string; + startAt?: string; + endAt?: string; +} + +// Response Types +export interface GHLGetSurveysResponse { + surveys: GHLSurvey[]; + total: number; +} + +export interface GHLGetSurveySubmissionsResponse { + submissions: GHLSurveySubmission[]; + meta: GHLSurveySubmissionMeta; +} + +// MCP Parameter Interfaces +export interface MCPGetSurveysParams { + locationId?: string; + skip?: number; + limit?: number; + type?: string; +} + +export interface MCPGetSurveySubmissionsParams { + locationId?: string; + page?: number; + limit?: number; + surveyId?: string; + q?: string; + startAt?: string; + endAt?: string; +} + +// ===== STORE API TYPES ===== + +// Country and State Types +export type GHLCountryCode = 'US' | 'CA' | 'AF' | 'AX' | 'AL' | 'DZ' | 'AS' | 'AD' | 'AO' | 'AI' | 'AQ' | 'AG' | 'AR' | 'AM' | 'AW' | 'AU' | 'AT' | 'AZ' | 'BS' | 'BH' | 'BD' | 'BB' | 'BY' | 'BE' | 'BZ' | 'BJ' | 'BM' | 'BT' | 'BO' | 'BA' | 'BW' | 'BV' | 'BR' | 'IO' | 'BN' | 'BG' | 'BF' | 'BI' | 'KH' | 'CM' | 'CV' | 'KY' | 'CF' | 'TD' | 'CL' | 'CN' | 'CX' | 'CC' | 'CO' | 'KM' | 'CG' | 'CD' | 'CK' | 'CR' | 'CI' | 'HR' | 'CU' | 'CY' | 'CZ' | 'DK' | 'DJ' | 'DM' | 'DO' | 'EC' | 'EG' | 'SV' | 'GQ' | 'ER' | 'EE' | 'ET' | 'FK' | 'FO' | 'FJ' | 'FI' | 'FR' | 'GF' | 'PF' | 'TF' | 'GA' | 'GM' | 'GE' | 'DE' | 'GH' | 'GI' | 'GR' | 'GL' | 'GD' | 'GP' | 'GU' | 'GT' | 'GG' | 'GN' | 'GW' | 'GY' | 'HT' | 'HM' | 'VA' | 'HN' | 'HK' | 'HU' | 'IS' | 'IN' | 'ID' | 'IR' | 'IQ' | 'IE' | 'IM' | 'IL' | 'IT' | 'JM' | 'JP' | 'JE' | 'JO' | 'KZ' | 'KE' | 'KI' | 'KP' | 'XK' | 'KW' | 'KG' | 'LA' | 'LV' | 'LB' | 'LS' | 'LR' | 'LY' | 'LI' | 'LT' | 'LU' | 'MO' | 'MK' | 'MG' | 'MW' | 'MY' | 'MV' | 'ML' | 'MT' | 'MH' | 'MQ' | 'MR' | 'MU' | 'YT' | 'MX' | 'FM' | 'MD' | 'MC' | 'MN' | 'ME' | 'MS' | 'MA' | 'MZ' | 'MM' | 'NA' | 'NR' | 'NP' | 'NL' | 'AN' | 'NC' | 'NZ' | 'NI' | 'NE' | 'NG' | 'NU' | 'NF' | 'MP' | 'NO' | 'OM' | 'PK' | 'PW' | 'PS' | 'PA' | 'PG' | 'PY' | 'PE' | 'PH' | 'PN' | 'PL' | 'PT' | 'PR' | 'QA' | 'RE' | 'RO' | 'RU' | 'RW' | 'SH' | 'KN' | 'LC' | 'MF' | 'PM' | 'VC' | 'WS' | 'SM' | 'ST' | 'SA' | 'SN' | 'RS' | 'SC' | 'SL' | 'SG' | 'SX' | 'SK' | 'SI' | 'SB' | 'SO' | 'ZA' | 'GS' | 'KR' | 'ES' | 'LK' | 'SD' | 'SR' | 'SJ' | 'SZ' | 'SE' | 'CH' | 'SY' | 'TW' | 'TJ' | 'TZ' | 'TH' | 'TL' | 'TG' | 'TK' | 'TO' | 'TT' | 'TN' | 'TR' | 'TM' | 'TC' | 'TV' | 'UG' | 'UA' | 'AE' | 'GB' | 'UM' | 'UY' | 'UZ' | 'VU' | 'VE' | 'VN' | 'VG' | 'VI' | 'WF' | 'EH' | 'YE' | 'ZM' | 'ZW'; + +export type GHLStateCode = 'AL' | 'AK' | 'AS' | 'AZ' | 'AR' | 'AA' | 'AE' | 'AP' | 'CA' | 'CO' | 'CT' | 'DE' | 'DC' | 'FM' | 'FL' | 'GA' | 'GU' | 'HI' | 'ID' | 'IL' | 'IN' | 'IA' | 'KS' | 'KY' | 'LA' | 'ME' | 'MH' | 'MD' | 'MA' | 'MI' | 'MN' | 'MS' | 'MO' | 'MT' | 'NE' | 'NV' | 'NH' | 'NJ' | 'NM' | 'NY' | 'NC' | 'ND' | 'MP' | 'OH' | 'OK' | 'OR' | 'PW' | 'PA' | 'PR' | 'RI' | 'SC' | 'SD' | 'TN' | 'TX' | 'UT' | 'VT' | 'VI' | 'VA' | 'WA' | 'WV' | 'WI' | 'WY' | 'AB' | 'BC' | 'MB' | 'NB' | 'NL' | 'NT' | 'NS' | 'NU' | 'ON' | 'PE' | 'QC' | 'SK' | 'YT'; + +// Shipping Zone Types +export interface GHLShippingZoneCountryState { + code: GHLStateCode; +} + +export interface GHLShippingZoneCountry { + code: GHLCountryCode; + states?: GHLShippingZoneCountryState[]; +} + +export interface GHLCreateShippingZoneRequest { + altId: string; + altType: 'location'; + name: string; + countries: GHLShippingZoneCountry[]; +} + +export interface GHLUpdateShippingZoneRequest { + altId?: string; + altType?: 'location'; + name?: string; + countries?: GHLShippingZoneCountry[]; +} + +export interface GHLGetShippingZonesRequest { + altId: string; + altType: 'location'; + limit?: number; + offset?: number; + withShippingRate?: boolean; +} + +export interface GHLDeleteShippingZoneRequest { + altId: string; + altType: 'location'; +} + +// Shipping Rate Types +export interface GHLShippingCarrierService { + name: string; + value: string; +} + +export type GHLShippingConditionType = 'none' | 'price' | 'weight'; + +export interface GHLCreateShippingRateRequest { + altId: string; + altType: 'location'; + name: string; + description?: string; + currency: string; + amount: number; + conditionType: GHLShippingConditionType; + minCondition?: number; + maxCondition?: number; + isCarrierRate?: boolean; + shippingCarrierId: string; + percentageOfRateFee?: number; + shippingCarrierServices?: GHLShippingCarrierService[]; +} + +export interface GHLUpdateShippingRateRequest { + altId?: string; + altType?: 'location'; + name?: string; + description?: string; + currency?: string; + amount?: number; + conditionType?: GHLShippingConditionType; + minCondition?: number; + maxCondition?: number; + isCarrierRate?: boolean; + shippingCarrierId?: string; + percentageOfRateFee?: number; + shippingCarrierServices?: GHLShippingCarrierService[]; +} + +export interface GHLGetShippingRatesRequest { + altId: string; + altType: 'location'; + limit?: number; + offset?: number; +} + +export interface GHLDeleteShippingRateRequest { + altId: string; + altType: 'location'; +} + +// Shipping Carrier Types +export interface GHLCreateShippingCarrierRequest { + altId: string; + altType: 'location'; + name: string; + callbackUrl: string; + services?: GHLShippingCarrierService[]; + allowsMultipleServiceSelection?: boolean; +} + +export interface GHLUpdateShippingCarrierRequest { + altId?: string; + altType?: 'location'; + name?: string; + callbackUrl?: string; + services?: GHLShippingCarrierService[]; + allowsMultipleServiceSelection?: boolean; +} + +export interface GHLGetShippingCarriersRequest { + altId: string; + altType: 'location'; +} + +export interface GHLDeleteShippingCarrierRequest { + altId: string; + altType: 'location'; +} + +// Available Shipping Rates Types +export interface GHLContactAddress { + name?: string; + companyName?: string; + addressLine1?: string; + country: GHLCountryCode; + state?: GHLStateCode; + city?: string; + zip?: string; + phone?: string; + email?: string; +} + +export interface GHLOrderSource { + type: 'funnel' | 'website' | 'invoice' | 'calendar' | 'text2Pay' | 'document_contracts' | 'membership' | 'mobile_app' | 'communities' | 'point_of_sale' | 'manual' | 'form' | 'survey' | 'payment_link' | 'external'; + subType?: 'one_step_order_form' | 'two_step_order_form' | 'upsell' | 'tap_to_pay' | 'card_payment' | 'store' | 'contact_view' | 'email_campaign' | 'payments_dashboard' | 'shopify' | 'subscription_view' | 'store_upsell' | 'woocommerce' | 'service' | 'meeting' | 'imported_csv' | 'qr_code'; +} + +export interface GHLProductItem { + id: string; + qty: number; +} + +export interface GHLGetAvailableShippingRatesRequest { + altId: string; + altType: 'location'; + country: GHLCountryCode; + address?: GHLContactAddress; + amountAvailable?: string; + totalOrderAmount: number; + weightAvailable?: boolean; + totalOrderWeight: number; + source: GHLOrderSource; + products: GHLProductItem[]; + couponCode?: string; +} + +// Response Types +export interface GHLShippingRate { + altId: string; + altType: 'location'; + name: string; + description?: string; + currency: string; + amount: number; + conditionType: GHLShippingConditionType; + minCondition?: number; + maxCondition?: number; + isCarrierRate?: boolean; + shippingCarrierId: string; + percentageOfRateFee?: number; + shippingCarrierServices?: GHLShippingCarrierService[]; + _id: string; + shippingZoneId: string; + createdAt: string; + updatedAt: string; +} + +export interface GHLShippingZone { + altId: string; + altType: 'location'; + name: string; + countries: GHLShippingZoneCountry[]; + _id: string; + shippingRates?: GHLShippingRate[]; + createdAt: string; + updatedAt: string; +} + +export interface GHLShippingCarrier { + altId: string; + altType: 'location'; + name: string; + callbackUrl: string; + services?: GHLShippingCarrierService[]; + allowsMultipleServiceSelection?: boolean; + _id: string; + marketplaceAppId: string; + createdAt: string; + updatedAt: string; +} + +export interface GHLAvailableShippingRate { + name: string; + description?: string; + currency: string; + amount: number; + isCarrierRate?: boolean; + shippingCarrierId: string; + percentageOfRateFee?: number; + shippingCarrierServices?: GHLShippingCarrierService[]; + _id: string; + shippingZoneId: string; +} + +export interface GHLCreateShippingZoneResponse { + status: boolean; + message?: string; + data: GHLShippingZone; +} + +export interface GHLListShippingZonesResponse { + total: number; + data: GHLShippingZone[]; +} + +export interface GHLGetShippingZoneResponse { + status: boolean; + message?: string; + data: GHLShippingZone; +} + +export interface GHLUpdateShippingZoneResponse { + status: boolean; + message?: string; + data: GHLShippingZone; +} + +export interface GHLDeleteShippingZoneResponse { + status: boolean; + message?: string; +} + +export interface GHLCreateShippingRateResponse { + status: boolean; + message?: string; + data: GHLShippingRate; +} + +export interface GHLListShippingRatesResponse { + total: number; + data: GHLShippingRate[]; +} + +export interface GHLGetShippingRateResponse { + status: boolean; + message?: string; + data: GHLShippingRate; +} + +export interface GHLUpdateShippingRateResponse { + status: boolean; + message?: string; + data: GHLShippingRate; +} + +export interface GHLDeleteShippingRateResponse { + status: boolean; + message?: string; +} + +export interface GHLCreateShippingCarrierResponse { + status: boolean; + message?: string; + data: GHLShippingCarrier; +} + +export interface GHLListShippingCarriersResponse { + status: boolean; + message?: string; + data: GHLShippingCarrier[]; +} + +export interface GHLGetShippingCarrierResponse { + status: boolean; + message?: string; + data: GHLShippingCarrier; +} + +export interface GHLUpdateShippingCarrierResponse { + status: boolean; + message?: string; + data: GHLShippingCarrier; +} + +export interface GHLDeleteShippingCarrierResponse { + status: boolean; + message?: string; +} + +export interface GHLGetAvailableShippingRatesResponse { + status: boolean; + message?: string; + data: GHLAvailableShippingRate[]; +} + +// Store Settings Types +export interface GHLStoreShippingOrigin { + name: string; + country: GHLCountryCode; + state?: GHLStateCode; + city: string; + street1: string; + street2?: string; + zip: string; + phone?: string; + email?: string; +} + +export interface GHLStoreOrderNotification { + enabled: boolean; + subject: string; + emailTemplateId: string; + defaultEmailTemplateId: string; +} + +export interface GHLStoreOrderFulfillmentNotification { + enabled: boolean; + subject: string; + emailTemplateId: string; + defaultEmailTemplateId: string; +} + +export interface GHLCreateStoreSettingRequest { + altId: string; + altType: 'location'; + shippingOrigin: GHLStoreShippingOrigin; + storeOrderNotification?: GHLStoreOrderNotification; + storeOrderFulfillmentNotification?: GHLStoreOrderFulfillmentNotification; +} + +export interface GHLGetStoreSettingRequest { + altId: string; + altType: 'location'; +} + +export interface GHLStoreSetting { + altId: string; + altType: 'location'; + shippingOrigin: GHLStoreShippingOrigin; + storeOrderNotification?: GHLStoreOrderNotification; + storeOrderFulfillmentNotification?: GHLStoreOrderFulfillmentNotification; + _id: string; + createdAt: string; + updatedAt: string; +} + +export interface GHLCreateStoreSettingResponse { + status: boolean; + message?: string; + data: GHLStoreSetting; +} + +export interface GHLGetStoreSettingResponse { + status: boolean; + message?: string; + data: GHLStoreSetting; +} + +// MCP Tool Parameters - Store API + +// Shipping Zone MCP Parameters +export interface MCPCreateShippingZoneParams { + locationId?: string; + name: string; + countries: GHLShippingZoneCountry[]; +} + +export interface MCPListShippingZonesParams { + locationId?: string; + limit?: number; + offset?: number; + withShippingRate?: boolean; +} + +export interface MCPGetShippingZoneParams { + shippingZoneId: string; + locationId?: string; + withShippingRate?: boolean; +} + +export interface MCPUpdateShippingZoneParams { + shippingZoneId: string; + locationId?: string; + name?: string; + countries?: GHLShippingZoneCountry[]; +} + +export interface MCPDeleteShippingZoneParams { + shippingZoneId: string; + locationId?: string; +} + +// Shipping Rate MCP Parameters +export interface MCPCreateShippingRateParams { + shippingZoneId: string; + locationId?: string; + name: string; + description?: string; + currency: string; + amount: number; + conditionType: GHLShippingConditionType; + minCondition?: number; + maxCondition?: number; + isCarrierRate?: boolean; + shippingCarrierId: string; + percentageOfRateFee?: number; + shippingCarrierServices?: GHLShippingCarrierService[]; +} + +export interface MCPListShippingRatesParams { + shippingZoneId: string; + locationId?: string; + limit?: number; + offset?: number; +} + +export interface MCPGetShippingRateParams { + shippingZoneId: string; + shippingRateId: string; + locationId?: string; +} + +export interface MCPUpdateShippingRateParams { + shippingZoneId: string; + shippingRateId: string; + locationId?: string; + name?: string; + description?: string; + currency?: string; + amount?: number; + conditionType?: GHLShippingConditionType; + minCondition?: number; + maxCondition?: number; + isCarrierRate?: boolean; + shippingCarrierId?: string; + percentageOfRateFee?: number; + shippingCarrierServices?: GHLShippingCarrierService[]; +} + +export interface MCPDeleteShippingRateParams { + shippingZoneId: string; + shippingRateId: string; + locationId?: string; +} + +export interface MCPGetAvailableShippingRatesParams { + locationId?: string; + country: GHLCountryCode; + address?: GHLContactAddress; + totalOrderAmount: number; + totalOrderWeight: number; + source: GHLOrderSource; + products: GHLProductItem[]; + couponCode?: string; +} + +// Shipping Carrier MCP Parameters +export interface MCPCreateShippingCarrierParams { + locationId?: string; + name: string; + callbackUrl: string; + services?: GHLShippingCarrierService[]; + allowsMultipleServiceSelection?: boolean; +} + +export interface MCPListShippingCarriersParams { + locationId?: string; +} + +export interface MCPGetShippingCarrierParams { + shippingCarrierId: string; + locationId?: string; +} + +export interface MCPUpdateShippingCarrierParams { + shippingCarrierId: string; + locationId?: string; + name?: string; + callbackUrl?: string; + services?: GHLShippingCarrierService[]; + allowsMultipleServiceSelection?: boolean; +} + +export interface MCPDeleteShippingCarrierParams { + shippingCarrierId: string; + locationId?: string; +} + +// Store Settings MCP Parameters +export interface MCPCreateStoreSettingParams { + locationId?: string; + shippingOrigin: GHLStoreShippingOrigin; + storeOrderNotification?: GHLStoreOrderNotification; + storeOrderFulfillmentNotification?: GHLStoreOrderFulfillmentNotification; +} + +export interface MCPGetStoreSettingParams { + locationId?: string; +} + +// Products API Types + +// Core Product Types +export type GHLProductType = 'DIGITAL' | 'PHYSICAL' | 'SERVICE' | 'PHYSICAL/DIGITAL'; +export type GHLPriceType = 'one_time' | 'recurring'; +export type GHLRecurringInterval = 'day' | 'month' | 'week' | 'year'; +export type GHLWeightUnit = 'kg' | 'lb' | 'g' | 'oz'; +export type GHLDimensionUnit = 'cm' | 'in' | 'm'; +export type GHLMediaType = 'image' | 'video'; +export type GHLSortOrder = 'asc' | 'desc'; +export type GHLReviewSortField = 'createdAt' | 'rating'; +export type GHLBulkUpdateType = 'bulk-update-price' | 'bulk-update-availability' | 'bulk-update-product-collection' | 'bulk-delete-products' | 'bulk-update-currency'; +export type GHLPriceUpdateType = 'INCREASE_BY_AMOUNT' | 'REDUCE_BY_AMOUNT' | 'SET_NEW_PRICE' | 'INCREASE_BY_PERCENTAGE' | 'REDUCE_BY_PERCENTAGE'; +export type GHLStoreAction = 'include' | 'exclude'; +export type GHLAltType = 'location'; + +// Product Variant Types +export interface GHLProductVariantOption { + id: string; + name: string; +} + +export interface GHLProductVariant { + id: string; + name: string; + options: GHLProductVariantOption[]; +} + +// Product Media Types +export interface GHLProductMedia { + id: string; + title?: string; + url: string; + type: GHLMediaType; + isFeatured?: boolean; + priceIds?: string[]; +} + +// Product Label Types +export interface GHLProductLabel { + title: string; + startDate?: string; + endDate?: string; +} + +// Product SEO Types +export interface GHLProductSEO { + title?: string; + description?: string; +} + +// Price Types +export interface GHLRecurring { + interval: GHLRecurringInterval; + intervalCount: number; +} + +export interface GHLMembershipOffer { + label: string; + value: string; + _id: string; +} + +export interface GHLPriceMeta { + source: 'stripe' | 'woocommerce' | 'shopify'; + sourceId?: string; + stripePriceId: string; + internalSource: 'agency_plan' | 'funnel' | 'membership' | 'communities' | 'gokollab'; +} + +export interface GHLWeightOptions { + value: number; + unit: GHLWeightUnit; +} + +export interface GHLPriceDimensions { + height: number; + width: number; + length: number; + unit: GHLDimensionUnit; +} + +export interface GHLShippingOptions { + weight?: GHLWeightOptions; + dimensions?: GHLPriceDimensions; +} + +// Collection Types +export interface GHLCollectionSEO { + title?: string; + description?: string; +} + +export interface GHLProductCollection { + _id: string; + altId: string; + name: string; + slug: string; + image?: string; + seo?: GHLCollectionSEO; + createdAt: string; +} + +// Review Types +export interface GHLUserDetails { + name: string; + email: string; + phone?: string; + isCustomer?: boolean; +} + +export interface GHLProductReview { + headline: string; + comment: string; + user: GHLUserDetails; +} + +// Inventory Types +export interface GHLInventoryItem { + _id: string; + name: string; + availableQuantity: number; + sku?: string; + allowOutOfStockPurchases: boolean; + product: string; + updatedAt: string; + image?: string; + productName?: string; +} + +// Core Product Interface +export interface GHLProduct { + _id: string; + description?: string; + variants?: GHLProductVariant[]; + medias?: GHLProductMedia[]; + locationId: string; + name: string; + productType: GHLProductType; + availableInStore?: boolean; + userId?: string; + createdAt: string; + updatedAt: string; + statementDescriptor?: string; + image?: string; + collectionIds?: string[]; + isTaxesEnabled?: boolean; + taxes?: string[]; + automaticTaxCategoryId?: string; + isLabelEnabled?: boolean; + label?: GHLProductLabel; + slug?: string; + seo?: GHLProductSEO; +} + +// Price Interface +export interface GHLPrice { + _id: string; + membershipOffers?: GHLMembershipOffer[]; + variantOptionIds?: string[]; + locationId: string; + product: string; + userId?: string; + name: string; + type: GHLPriceType; + currency: string; + amount: number; + recurring?: GHLRecurring; + description?: string; + trialPeriod?: number; + totalCycles?: number; + setupFee?: number; + compareAtPrice?: number; + createdAt: string; + updatedAt: string; + meta?: GHLPriceMeta; + trackInventory?: boolean; + availableQuantity?: number; + allowOutOfStockPurchases?: boolean; + sku?: string; + shippingOptions?: GHLShippingOptions; + isDigitalProduct?: boolean; + digitalDelivery?: string[]; +} + +// Request Types + +// Product Requests +export interface GHLCreateProductRequest { + name: string; + locationId: string; + description?: string; + productType: GHLProductType; + image?: string; + statementDescriptor?: string; + availableInStore?: boolean; + medias?: GHLProductMedia[]; + variants?: GHLProductVariant[]; + collectionIds?: string[]; + isTaxesEnabled?: boolean; + taxes?: string[]; + automaticTaxCategoryId?: string; + isLabelEnabled?: boolean; + label?: GHLProductLabel; + slug?: string; + seo?: GHLProductSEO; +} + +export interface GHLUpdateProductRequest { + name?: string; + locationId?: string; + description?: string; + productType?: GHLProductType; + image?: string; + statementDescriptor?: string; + availableInStore?: boolean; + medias?: GHLProductMedia[]; + variants?: GHLProductVariant[]; + collectionIds?: string[]; + isTaxesEnabled?: boolean; + taxes?: string[]; + automaticTaxCategoryId?: string; + isLabelEnabled?: boolean; + label?: GHLProductLabel; + slug?: string; + seo?: GHLProductSEO; +} + +export interface GHLListProductsRequest { + locationId: string; + limit?: number; + offset?: number; + search?: string; + collectionIds?: string[]; + collectionSlug?: string; + expand?: string[]; + productIds?: string[]; + storeId?: string; + includedInStore?: boolean; + availableInStore?: boolean; + sortOrder?: GHLSortOrder; +} + +export interface GHLGetProductRequest { + productId: string; + locationId: string; +} + +export interface GHLDeleteProductRequest { + productId: string; + locationId: string; +} + +// Price Requests +export interface GHLCreatePriceRequest { + name: string; + type: GHLPriceType; + currency: string; + amount: number; + recurring?: GHLRecurring; + description?: string; + membershipOffers?: GHLMembershipOffer[]; + trialPeriod?: number; + totalCycles?: number; + setupFee?: number; + variantOptionIds?: string[]; + compareAtPrice?: number; + locationId: string; + userId?: string; + meta?: GHLPriceMeta; + trackInventory?: boolean; + availableQuantity?: number; + allowOutOfStockPurchases?: boolean; + sku?: string; + shippingOptions?: GHLShippingOptions; + isDigitalProduct?: boolean; + digitalDelivery?: string[]; +} + +export interface GHLUpdatePriceRequest { + name?: string; + type?: GHLPriceType; + currency?: string; + amount?: number; + recurring?: GHLRecurring; + description?: string; + membershipOffers?: GHLMembershipOffer[]; + trialPeriod?: number; + totalCycles?: number; + setupFee?: number; + variantOptionIds?: string[]; + compareAtPrice?: number; + locationId?: string; + userId?: string; + meta?: GHLPriceMeta; + trackInventory?: boolean; + availableQuantity?: number; + allowOutOfStockPurchases?: boolean; + sku?: string; + shippingOptions?: GHLShippingOptions; + isDigitalProduct?: boolean; + digitalDelivery?: string[]; +} + +export interface GHLListPricesRequest { + productId: string; + locationId: string; + limit?: number; + offset?: number; + ids?: string; +} + +export interface GHLGetPriceRequest { + productId: string; + priceId: string; + locationId: string; +} + +export interface GHLDeletePriceRequest { + productId: string; + priceId: string; + locationId: string; +} + +// Bulk Update Requests +export interface GHLBulkUpdateFilters { + collectionIds?: string[]; + productType?: string; + availableInStore?: boolean; + search?: string; +} + +export interface GHLPriceUpdateField { + type: GHLPriceUpdateType; + value: number; + roundToWhole?: boolean; +} + +export interface GHLBulkUpdateRequest { + altId: string; + altType: GHLAltType; + type: GHLBulkUpdateType; + productIds: string[]; + filters?: GHLBulkUpdateFilters; + price?: GHLPriceUpdateField; + compareAtPrice?: GHLPriceUpdateField; + availability?: boolean; + collectionIds?: string[]; + currency?: string; +} + +// Inventory Requests +export interface GHLListInventoryRequest { + altId: string; + altType: GHLAltType; + limit?: number; + offset?: number; + search?: string; +} + +export interface GHLUpdateInventoryItem { + priceId: string; + availableQuantity?: number; + allowOutOfStockPurchases?: boolean; +} + +export interface GHLUpdateInventoryRequest { + altId: string; + altType: GHLAltType; + items: GHLUpdateInventoryItem[]; +} + +// Store Requests +export interface GHLGetProductStoreStatsRequest { + storeId: string; + altId: string; + altType: GHLAltType; + search?: string; + collectionIds?: string; +} + +export interface GHLUpdateProductStoreRequest { + action: GHLStoreAction; + productIds: string[]; +} + +// Collection Requests +export interface GHLCreateProductCollectionRequest { + altId: string; + altType: GHLAltType; + collectionId?: string; + name: string; + slug: string; + image?: string; + seo?: GHLCollectionSEO; +} + +export interface GHLUpdateProductCollectionRequest { + altId: string; + altType: GHLAltType; + name?: string; + slug?: string; + image?: string; + seo?: GHLCollectionSEO; +} + +export interface GHLListProductCollectionsRequest { + altId: string; + altType: GHLAltType; + limit?: number; + offset?: number; + collectionIds?: string; + name?: string; +} + +export interface GHLGetProductCollectionRequest { + collectionId: string; +} + +export interface GHLDeleteProductCollectionRequest { + collectionId: string; + altId: string; + altType: GHLAltType; +} + +// Review Requests +export interface GHLListProductReviewsRequest { + altId: string; + altType: GHLAltType; + limit?: number; + offset?: number; + sortField?: GHLReviewSortField; + sortOrder?: GHLSortOrder; + rating?: number; + startDate?: string; + endDate?: string; + productId?: string; + storeId?: string; +} + +export interface GHLGetReviewsCountRequest { + altId: string; + altType: GHLAltType; + rating?: number; + startDate?: string; + endDate?: string; + productId?: string; + storeId?: string; +} + +export interface GHLUpdateProductReviewRequest { + altId: string; + altType: GHLAltType; + productId: string; + status: string; + reply?: GHLProductReview[]; + rating?: number; + headline?: string; + detail?: string; +} + +export interface GHLDeleteProductReviewRequest { + reviewId: string; + altId: string; + altType: GHLAltType; + productId: string; +} + +export interface GHLUpdateProductReviewObject { + reviewId: string; + productId: string; + storeId: string; +} + +export interface GHLBulkUpdateProductReviewsRequest { + altId: string; + altType: GHLAltType; + reviews: GHLUpdateProductReviewObject[]; + status: any; +} + +// Response Types + +// Product Responses +export interface GHLCreateProductResponse { + _id: string; + description?: string; + variants?: GHLProductVariant[]; + medias?: GHLProductMedia[]; + locationId: string; + name: string; + productType: GHLProductType; + availableInStore?: boolean; + userId?: string; + createdAt: string; + updatedAt: string; + statementDescriptor?: string; + image?: string; + collectionIds?: string[]; + isTaxesEnabled?: boolean; + taxes?: string[]; + automaticTaxCategoryId?: string; + isLabelEnabled?: boolean; + label?: GHLProductLabel; + slug?: string; + seo?: GHLProductSEO; +} + +export interface GHLUpdateProductResponse extends GHLCreateProductResponse {} + +export interface GHLGetProductResponse extends GHLCreateProductResponse {} + +export interface GHLListProductsStats { + total: number; +} + +export interface GHLListProductsResponse { + products: GHLProduct[]; + total: GHLListProductsStats[]; +} + +export interface GHLDeleteProductResponse { + status: boolean; +} + +// Price Responses +export interface GHLCreatePriceResponse extends GHLPrice {} +export interface GHLUpdatePriceResponse extends GHLPrice {} +export interface GHLGetPriceResponse extends GHLPrice {} + +export interface GHLListPricesResponse { + prices: GHLPrice[]; + total: number; +} + +export interface GHLDeletePriceResponse { + status: boolean; +} + +// Bulk Update Response +export interface GHLBulkUpdateResponse { + status: boolean; + message?: string; +} + +// Inventory Responses +export interface GHLListInventoryResponse { + inventory: GHLInventoryItem[]; + total: { total: number }; +} + +export interface GHLUpdateInventoryResponse { + status: boolean; + message?: string; +} + +// Store Responses +export interface GHLGetProductStoreStatsResponse { + totalProducts: number; + includedInStore: number; + excludedFromStore: number; +} + +export interface GHLUpdateProductStoreResponse { + status: boolean; + message?: string; +} + +// Collection Responses +export interface GHLCreateCollectionResponse { + data: GHLProductCollection; +} + +export interface GHLUpdateProductCollectionResponse { + status: boolean; + message?: string; +} + +export interface GHLListCollectionResponse { + data: any[]; + total: number; +} + +export interface GHLDefaultCollectionResponse { + data: any; + status: boolean; +} + +export interface GHLDeleteProductCollectionResponse { + status: boolean; + message?: string; +} + +// Review Responses +export interface GHLListProductReviewsResponse { + data: any[]; + total: number; +} + +export interface GHLCountReviewsByStatusResponse { + data: any[]; +} + +export interface GHLUpdateProductReviewsResponse { + status: boolean; + message?: string; +} + +export interface GHLDeleteProductReviewResponse { + status: boolean; + message?: string; +} + +// MCP Tool Parameters - Products API + +// Product MCP Parameters +export interface MCPCreateProductParams { + locationId?: string; + name: string; + productType: GHLProductType; + description?: string; + image?: string; + statementDescriptor?: string; + availableInStore?: boolean; + medias?: GHLProductMedia[]; + variants?: GHLProductVariant[]; + collectionIds?: string[]; + isTaxesEnabled?: boolean; + taxes?: string[]; + automaticTaxCategoryId?: string; + isLabelEnabled?: boolean; + label?: GHLProductLabel; + slug?: string; + seo?: GHLProductSEO; +} + +export interface MCPUpdateProductParams { + productId: string; + locationId?: string; + name?: string; + productType?: GHLProductType; + description?: string; + image?: string; + statementDescriptor?: string; + availableInStore?: boolean; + medias?: GHLProductMedia[]; + variants?: GHLProductVariant[]; + collectionIds?: string[]; + isTaxesEnabled?: boolean; + taxes?: string[]; + automaticTaxCategoryId?: string; + isLabelEnabled?: boolean; + label?: GHLProductLabel; + slug?: string; + seo?: GHLProductSEO; +} + +export interface MCPListProductsParams { + locationId?: string; + limit?: number; + offset?: number; + search?: string; + collectionIds?: string[]; + collectionSlug?: string; + expand?: string[]; + productIds?: string[]; + storeId?: string; + includedInStore?: boolean; + availableInStore?: boolean; + sortOrder?: GHLSortOrder; +} + +export interface MCPGetProductParams { + productId: string; + locationId?: string; +} + +export interface MCPDeleteProductParams { + productId: string; + locationId?: string; +} + +// Price MCP Parameters +export interface MCPCreatePriceParams { + productId: string; + name: string; + type: GHLPriceType; + currency: string; + amount: number; + locationId?: string; + recurring?: GHLRecurring; + description?: string; + membershipOffers?: GHLMembershipOffer[]; + trialPeriod?: number; + totalCycles?: number; + setupFee?: number; + variantOptionIds?: string[]; + compareAtPrice?: number; + userId?: string; + meta?: GHLPriceMeta; + trackInventory?: boolean; + availableQuantity?: number; + allowOutOfStockPurchases?: boolean; + sku?: string; + shippingOptions?: GHLShippingOptions; + isDigitalProduct?: boolean; + digitalDelivery?: string[]; +} + +export interface MCPUpdatePriceParams { + productId: string; + priceId: string; + name?: string; + type?: GHLPriceType; + currency?: string; + amount?: number; + locationId?: string; + recurring?: GHLRecurring; + description?: string; + membershipOffers?: GHLMembershipOffer[]; + trialPeriod?: number; + totalCycles?: number; + setupFee?: number; + variantOptionIds?: string[]; + compareAtPrice?: number; + userId?: string; + meta?: GHLPriceMeta; + trackInventory?: boolean; + availableQuantity?: number; + allowOutOfStockPurchases?: boolean; + sku?: string; + shippingOptions?: GHLShippingOptions; + isDigitalProduct?: boolean; + digitalDelivery?: string[]; +} + +export interface MCPListPricesParams { + productId: string; + locationId?: string; + limit?: number; + offset?: number; + ids?: string; +} + +export interface MCPGetPriceParams { + productId: string; + priceId: string; + locationId?: string; +} + +export interface MCPDeletePriceParams { + productId: string; + priceId: string; + locationId?: string; +} + +// Bulk Update MCP Parameters +export interface MCPBulkUpdateProductsParams { + locationId?: string; + type: GHLBulkUpdateType; + productIds: string[]; + filters?: GHLBulkUpdateFilters; + price?: GHLPriceUpdateField; + compareAtPrice?: GHLPriceUpdateField; + availability?: boolean; + collectionIds?: string[]; + currency?: string; +} + +// Inventory MCP Parameters +export interface MCPListInventoryParams { + locationId?: string; + limit?: number; + offset?: number; + search?: string; +} + +export interface MCPUpdateInventoryParams { + locationId?: string; + items: GHLUpdateInventoryItem[]; +} + +// Store MCP Parameters +export interface MCPGetProductStoreStatsParams { + storeId: string; + locationId?: string; + search?: string; + collectionIds?: string; +} + +export interface MCPUpdateProductStoreParams { + storeId: string; + action: GHLStoreAction; + productIds: string[]; +} + +// Collection MCP Parameters +export interface MCPCreateProductCollectionParams { + locationId?: string; + collectionId?: string; + name: string; + slug: string; + image?: string; + seo?: GHLCollectionSEO; +} + +export interface MCPUpdateProductCollectionParams { + collectionId: string; + locationId?: string; + name?: string; + slug?: string; + image?: string; + seo?: GHLCollectionSEO; +} + +export interface MCPListProductCollectionsParams { + locationId?: string; + limit?: number; + offset?: number; + collectionIds?: string; + name?: string; +} + +export interface MCPGetProductCollectionParams { + collectionId: string; +} + +export interface MCPDeleteProductCollectionParams { + collectionId: string; + locationId?: string; +} + +// Review MCP Parameters +export interface MCPListProductReviewsParams { + locationId?: string; + limit?: number; + offset?: number; + sortField?: GHLReviewSortField; + sortOrder?: GHLSortOrder; + rating?: number; + startDate?: string; + endDate?: string; + productId?: string; + storeId?: string; +} + +export interface MCPGetReviewsCountParams { + locationId?: string; + rating?: number; + startDate?: string; + endDate?: string; + productId?: string; + storeId?: string; +} + +export interface MCPUpdateProductReviewParams { + reviewId: string; + locationId?: string; + productId: string; + status: string; + reply?: GHLProductReview[]; + rating?: number; + headline?: string; + detail?: string; +} + +export interface MCPDeleteProductReviewParams { + reviewId: string; + locationId?: string; + productId: string; +} + +export interface MCPBulkUpdateProductReviewsParams { + locationId?: string; + reviews: GHLUpdateProductReviewObject[]; + status: any; +} + +// ============================================================================= +// PAYMENTS API TYPES +// ============================================================================= + +// Integration Provider Types +export interface CreateWhiteLabelIntegrationProviderDto { + altId: string; + altType: 'location'; + uniqueName: string; + title: string; + provider: 'authorize-net' | 'nmi'; + description: string; + imageUrl: string; +} + +export interface IntegrationProvider { + _id: string; + altId: string; + altType: string; + title: string; + route: string; + provider: string; + description: string; + imageUrl: string; + createdAt: string; + updatedAt: string; +} + +export interface ListIntegrationProvidersResponse { + providers: IntegrationProvider[]; +} + +// Order Types +export interface OrderSource { + type: 'funnel' | 'website' | 'invoice' | 'calendar' | 'text2Pay' | 'document_contracts' | 'membership' | 'mobile_app' | 'communities' | 'point_of_sale' | 'manual' | 'form' | 'survey' | 'payment_link' | 'external'; + subType?: 'one_step_order_form' | 'two_step_order_form' | 'upsell' | 'tap_to_pay' | 'card_payment' | 'store' | 'contact_view' | 'email_campaign' | 'payments_dashboard' | 'shopify' | 'subscription_view' | 'store_upsell' | 'woocommerce' | 'service' | 'meeting' | 'imported_csv' | 'qr_code'; + id: string; + name?: string; + meta?: Record; +} + +export interface AmountSummary { + subtotal: number; + discount?: number; +} + +export interface Order { + _id: string; + altId: string; + altType: string; + contactId?: string; + contactName?: string; + contactEmail?: string; + currency?: string; + amount?: number; + subtotal?: number; + discount?: number; + status: string; + liveMode?: boolean; + totalProducts?: number; + sourceType?: string; + sourceName?: string; + sourceId?: string; + sourceMeta?: Record; + couponCode?: string; + createdAt: string; + updatedAt: string; + sourceSubType?: string; + fulfillmentStatus?: string; + onetimeProducts?: number; + recurringProducts?: number; + contactSnapshot?: Record; + amountSummary?: AmountSummary; + source?: OrderSource; + items?: string[]; + coupon?: Record; + trackingId?: string; + fingerprint?: string; + meta?: Record; + markAsTest?: boolean; + traceId?: string; +} + +export interface ListOrdersResponse { + data: Order[]; + totalCount: number; +} + +// Fulfillment Types +export interface FulfillmentTracking { + trackingNumber?: string; + shippingCarrier?: string; + trackingUrl?: string; +} + +export interface FulfillmentItems { + priceId: string; + qty: number; +} + +export interface CreateFulfillmentDto { + altId: string; + altType: 'location'; + trackings: FulfillmentTracking[]; + items: FulfillmentItems[]; + notifyCustomer: boolean; +} + +export interface ProductVariantOption { + id: string; + name: string; +} + +export interface ProductVariant { + id: string; + name: string; + options: ProductVariantOption[]; +} + +export interface ProductMedia { + id: string; + title?: string; + url: string; + type: 'image' | 'video'; + isFeatured?: boolean; + priceIds?: string[][]; +} + +export interface ProductLabel { + title: string; + startDate?: string; + endDate?: string; +} + +export interface ProductSEO { + title?: string; + description?: string; +} + +export interface DefaultProduct { + _id: string; + description?: string; + variants?: ProductVariant[]; + medias?: ProductMedia[]; + locationId: string; + name: string; + productType: string; + availableInStore?: boolean; + userId?: string; + createdAt: string; + updatedAt: string; + statementDescriptor?: string; + image?: string; + collectionIds?: string[]; + isTaxesEnabled?: boolean; + taxes?: string[]; + isLabelEnabled?: boolean; + label?: ProductLabel; + slug?: string; + seo?: ProductSEO; +} + +export interface MembershipOffer { + label: string; + value: string; + _id: string; +} + +export interface Recurring { + interval: 'day' | 'month' | 'week' | 'year'; + intervalCount: number; +} + +export interface DefaultPrice { + _id: string; + membershipOffers?: MembershipOffer[]; + variantOptionIds?: string[]; + locationId?: string; + product?: string; + userId?: string; + name: string; + type: 'one_time' | 'recurring'; + currency: string; + amount: number; + recurring?: Recurring; + createdAt?: string; + updatedAt?: string; + compareAtPrice?: number; + trackInventory?: boolean; + availableQuantity?: number; + allowOutOfStockPurchases?: boolean; +} + +export interface FulfilledItem { + _id: string; + name: string; + product: DefaultProduct; + price: DefaultPrice; + qty: number; +} + +export interface Fulfillment { + altId: string; + altType: 'location'; + trackings: FulfillmentTracking[]; + _id: string; + items: FulfilledItem[]; + createdAt: string; + updatedAt: string; +} + +export interface CreateFulfillmentResponse { + status: boolean; + data: Fulfillment; +} + +export interface ListFulfillmentResponse { + status: boolean; + data: Fulfillment[]; +} + +// Transaction Types +export interface Transaction { + _id: string; + altId: string; + altType: string; + contactId?: string; + contactName?: string; + contactEmail?: string; + currency?: string; + amount?: number; + status: Record; + liveMode?: boolean; + entityType?: string; + entityId?: string; + entitySourceType?: string; + entitySourceSubType?: string; + entitySourceName?: string; + entitySourceId?: string; + entitySourceMeta?: Record; + subscriptionId?: string; + chargeId?: string; + chargeSnapshot?: Record; + paymentProviderType?: string; + paymentProviderConnectedAccount?: string; + ipAddress?: string; + createdAt: string; + updatedAt: string; + amountRefunded?: number; + paymentMethod?: Record; + contactSnapshot?: Record; + entitySource?: OrderSource; + invoiceId?: string; + paymentProvider?: Record; + meta?: Record; + markAsTest?: boolean; + isParent?: boolean; + receiptId?: string; + qboSynced?: boolean; + qboResponse?: Record; + traceId?: string; +} + +export interface ListTransactionsResponse { + data: Transaction[]; + totalCount: number; +} + +// Subscription Types +export interface CustomRRuleOptions { + intervalType: 'yearly' | 'monthly' | 'weekly' | 'daily' | 'hourly' | 'minutely' | 'secondly'; + interval: number; + startDate: string; + startTime?: string; + endDate?: string; + endTime?: string; + dayOfMonth?: number; + dayOfWeek?: 'mo' | 'tu' | 'we' | 'th' | 'fr' | 'sa' | 'su'; + numOfWeek?: number; + monthOfYear?: 'jan' | 'feb' | 'mar' | 'apr' | 'may' | 'jun' | 'jul' | 'aug' | 'sep' | 'oct' | 'nov' | 'dec'; + count?: number; + daysBefore?: number; +} + +export interface ScheduleOptions { + executeAt?: string; + rrule?: CustomRRuleOptions; +} + +export interface Subscription { + _id: string; + altId: string; + altType: 'location'; + contactId?: string; + contactName?: string; + contactEmail?: string; + currency?: string; + amount?: number; + status: Record; + liveMode?: boolean; + entityType?: string; + entityId?: string; + entitySourceType?: string; + entitySourceName?: string; + entitySourceId?: string; + entitySourceMeta?: Record; + subscriptionId?: string; + subscriptionSnapshot?: Record; + paymentProviderType?: string; + paymentProviderConnectedAccount?: string; + ipAddress?: string; + createdAt: string; + updatedAt: string; + contactSnapshot?: Record; + coupon?: Record; + entitySource?: OrderSource; + paymentProvider?: Record; + meta?: Record; + markAsTest?: boolean; + schedule?: ScheduleOptions; + autoPayment?: Record; + recurringProduct?: Record; + canceledAt?: string; + canceledBy?: string; + traceId?: string; +} + +export interface ListSubscriptionsResponse { + data: Subscription[]; + totalCount: number; +} + +// Coupon Types +export interface ApplyToFuturePaymentsConfig { + type: 'forever' | 'fixed'; + duration?: number; + durationType?: 'months'; +} + +export interface Coupon { + _id: string; + usageCount: number; + hasAffiliateCoupon?: boolean; + deleted?: boolean; + limitPerCustomer: number; + altId: string; + altType: string; + name: string; + code: string; + discountType: 'percentage' | 'amount'; + discountValue: number; + status: 'scheduled' | 'active' | 'expired'; + startDate: string; + endDate?: string; + applyToFuturePayments: boolean; + applyToFuturePaymentsConfig: ApplyToFuturePaymentsConfig; + userId?: string; + createdAt: string; + updatedAt: string; +} + +export interface ListCouponsResponse { + data: Coupon[]; + totalCount: number; + traceId: string; +} + +export interface CreateCouponParams { + altId: string; + altType: 'location'; + name: string; + code: string; + discountType: 'percentage' | 'amount'; + discountValue: number; + startDate: string; + endDate?: string; + usageLimit?: number; + productIds?: string[]; + applyToFuturePayments?: boolean; + applyToFuturePaymentsConfig?: ApplyToFuturePaymentsConfig; + limitPerCustomer?: boolean; +} + +export interface UpdateCouponParams extends CreateCouponParams { + id: string; +} + +export interface DeleteCouponParams { + altId: string; + altType: 'location'; + id: string; +} + +export interface CreateCouponResponse extends Coupon { + traceId: string; +} + +export interface DeleteCouponResponse { + success: boolean; + traceId: string; +} + +// Custom Provider Types +export interface CreateCustomProviderDto { + name: string; + description: string; + paymentsUrl: string; + queryUrl: string; + imageUrl: string; +} + +export interface CustomProvider { + name: string; + description: string; + paymentsUrl: string; + queryUrl: string; + imageUrl: string; + _id: string; + locationId: string; + marketplaceAppId: string; + paymentProvider: Record; + deleted: boolean; + createdAt: string; + updatedAt: string; + traceId?: string; +} + +export interface CustomProviderKeys { + apiKey: string; + publishableKey: string; +} + +export interface ConnectCustomProviderConfigDto { + live: CustomProviderKeys; + test: CustomProviderKeys; +} + +export interface DeleteCustomProviderConfigDto { + liveMode: boolean; +} + +export interface DeleteCustomProviderResponse { + success: boolean; +} + +export interface DisconnectCustomProviderResponse { + success: boolean; +} + +// ============================================================================= +// INVOICES API TYPES +// ============================================================================= + +// Address and Business Details +export interface AddressDto { + addressLine1?: string; + addressLine2?: string; + city?: string; + state?: string; + countryCode?: string; + postalCode?: string; +} + +export interface BusinessDetailsDto { + logoUrl?: string; + name?: string; + phoneNo?: string; + address?: AddressDto; + website?: string; + customValues?: string[]; +} + +// Contact Details +export interface AdditionalEmailsDto { + email: string; +} + +export interface ContactDetailsDto { + id: string; + name: string; + phoneNo?: string; + email?: string; + additionalEmails?: AdditionalEmailsDto[]; + companyName?: string; + address?: AddressDto; + customFields?: string[]; +} + +// Invoice Items and Taxes +export interface ItemTaxDto { + _id: string; + name: string; + rate: number; + calculation: 'exclusive'; + description?: string; + taxId?: string; +} + +export interface InvoiceItemDto { + name: string; + description?: string; + productId?: string; + priceId?: string; + currency: string; + amount: number; + qty: number; + taxes?: ItemTaxDto[]; + automaticTaxCategoryId?: string; + isSetupFeeItem?: boolean; + type?: 'one_time' | 'recurring'; + taxInclusive?: boolean; +} + +// Discount +export interface DiscountDto { + value?: number; + type: 'percentage' | 'fixed'; + validOnProductIds?: string[]; +} + +// Tips Configuration +export interface TipsConfigurationDto { + tipsPercentage: string[]; + tipsEnabled: boolean; +} + +// Late Fees Configuration +export interface LateFeesFrequencyDto { + intervalCount?: number; + interval: 'minute' | 'hour' | 'day' | 'week' | 'month' | 'one_time'; +} + +export interface LateFeesGraceDto { + intervalCount: number; + interval: 'day'; +} + +export interface LateFeesMaxFeesDto { + type: 'fixed'; + value: number; +} + +export interface LateFeesConfigurationDto { + enable: boolean; + value: number; + type: 'fixed' | 'percentage'; + frequency: LateFeesFrequencyDto; + grace?: LateFeesGraceDto; + maxLateFees?: LateFeesMaxFeesDto; +} + +// Payment Methods +export interface StripePaymentMethodDto { + enableBankDebitOnly: boolean; +} + +export interface PaymentMethodDto { + stripe: StripePaymentMethodDto; +} + +// Invoice Template Types +export interface CreateInvoiceTemplateDto { + altId: string; + altType: 'location'; + internal?: boolean; + name: string; + businessDetails: BusinessDetailsDto; + currency: string; + items: InvoiceItemDto[]; + automaticTaxesEnabled?: boolean; + discount?: DiscountDto; + termsNotes?: string; + title?: string; + tipsConfiguration?: TipsConfigurationDto; + lateFeesConfiguration?: LateFeesConfigurationDto; + invoiceNumberPrefix?: string; + paymentMethods?: PaymentMethodDto; + attachments?: string[]; +} + +export interface UpdateInvoiceTemplateDto { + altId: string; + altType: 'location'; + internal?: boolean; + name: string; + businessDetails: BusinessDetailsDto; + currency: string; + items: InvoiceItemDto[]; + discount?: DiscountDto; + termsNotes?: string; + title?: string; +} + +export interface InvoiceTemplate { + _id: string; + altId: string; + altType: string; + name: string; + businessDetails: BusinessDetailsDto; + currency: string; + discount: DiscountDto; + items: any[]; + invoiceNumberPrefix?: string; + total: number; + createdAt: string; + updatedAt: string; +} + +export interface ListTemplatesResponse { + data: InvoiceTemplate[]; + totalCount: number; +} + +export interface UpdateInvoiceLateFeesConfigurationDto { + altId: string; + altType: 'location'; + lateFeesConfiguration: LateFeesConfigurationDto; +} + +export interface UpdatePaymentMethodsConfigurationDto { + altId: string; + altType: 'location'; + paymentMethods?: PaymentMethodDto; +} + +// Schedule Types +export interface CustomRRuleOptionsDto { + intervalType: 'yearly' | 'monthly' | 'weekly' | 'daily' | 'hourly' | 'minutely' | 'secondly'; + interval: number; + startDate: string; + startTime?: string; + endDate?: string; + endTime?: string; + dayOfMonth?: number; + dayOfWeek?: 'mo' | 'tu' | 'we' | 'th' | 'fr' | 'sa' | 'su'; + numOfWeek?: number; + monthOfYear?: 'jan' | 'feb' | 'mar' | 'apr' | 'may' | 'jun' | 'jul' | 'aug' | 'sep' | 'oct' | 'nov' | 'dec'; + count?: number; + daysBefore?: number; + useStartAsPrimaryUserAccepted?: boolean; + endType?: string; +} + +export interface ScheduleOptionsDto { + executeAt?: string; + rrule?: CustomRRuleOptionsDto; +} + +export interface AttachmentsDto { + id: string; + name: string; + url: string; + type: string; + size: number; +} + +export interface CreateInvoiceScheduleDto { + altId: string; + altType: 'location'; + name: string; + contactDetails: ContactDetailsDto; + schedule: ScheduleOptionsDto; + liveMode: boolean; + businessDetails: BusinessDetailsDto; + currency: string; + items: InvoiceItemDto[]; + automaticTaxesEnabled?: boolean; + discount: DiscountDto; + termsNotes?: string; + title?: string; + tipsConfiguration?: TipsConfigurationDto; + lateFeesConfiguration?: LateFeesConfigurationDto; + invoiceNumberPrefix?: string; + paymentMethods?: PaymentMethodDto; + attachments?: AttachmentsDto[]; +} + +export interface UpdateInvoiceScheduleDto { + altId: string; + altType: 'location'; + name: string; + contactDetails: ContactDetailsDto; + schedule: ScheduleOptionsDto; + liveMode: boolean; + businessDetails: BusinessDetailsDto; + currency: string; + items: InvoiceItemDto[]; + discount: DiscountDto; + termsNotes?: string; + title?: string; + attachments?: AttachmentsDto[]; +} + +// Auto Payment Details +export interface CardDto { + brand: string; + last4: string; +} + +export interface USBankAccountDto { + bank_name: string; + last4: string; +} + +export interface SepaDirectDebitDto { + bank_code: string; + last4: string; + branch_code: string; +} + +export interface BacsDirectDebitDto { + sort_code: string; + last4: string; +} + +export interface BecsDirectDebitDto { + bsb_number: string; + last4: string; +} + +export interface AutoPaymentDetailsDto { + enable: boolean; + type?: string; + paymentMethodId?: string; + customerId?: string; + card?: CardDto; + usBankAccount?: USBankAccountDto; + sepaDirectDebit?: SepaDirectDebitDto; + bacsDirectDebit?: BacsDirectDebitDto; + becsDirectDebit?: BecsDirectDebitDto; + cardId?: string; +} + +export interface ScheduleInvoiceScheduleDto { + altId: string; + altType: 'location'; + liveMode: boolean; + autoPayment?: AutoPaymentDetailsDto; +} + +export interface AutoPaymentScheduleDto { + altId: string; + altType: 'location'; + id: string; + autoPayment: AutoPaymentDetailsDto; +} + +export interface CancelInvoiceScheduleDto { + altId: string; + altType: 'location'; +} + +// Invoice Types +export interface DefaultInvoiceResponseDto { + _id: string; + status: 'draft' | 'sent' | 'payment_processing' | 'paid' | 'void' | 'partially_paid'; + liveMode: boolean; + amountPaid: number; + altId: string; + altType: string; + name: string; + businessDetails: any; + invoiceNumber: string; + currency: string; + contactDetails: any; + issueDate: string; + dueDate: string; + discount: any; + invoiceItems: any[]; + total: number; + title: string; + amountDue: number; + createdAt: string; + updatedAt: string; + automaticTaxesEnabled?: boolean; + automaticTaxesCalculated?: boolean; + paymentSchedule?: any; +} + +export interface InvoiceSchedule { + _id: string; + status: any; + liveMode: boolean; + altId: string; + altType: string; + name: string; + schedule?: ScheduleOptionsDto; + invoices: DefaultInvoiceResponseDto[]; + businessDetails: BusinessDetailsDto; + currency: string; + contactDetails: ContactDetailsDto; + discount: DiscountDto; + items: any[]; + total: number; + title: string; + termsNotes: string; + compiledTermsNotes: string; + createdAt: string; + updatedAt: string; +} + +export interface ListSchedulesResponse { + schedules: InvoiceSchedule[]; + total: number; +} + +// Text2Pay Types +export interface SentToDto { + email: string[]; + emailCc?: string[]; + emailBcc?: string[]; + phoneNo?: string[]; +} + +export interface PaymentScheduleDto { + type: 'fixed' | 'percentage'; + schedules: string[]; +} + +export interface Text2PayDto { + altId: string; + altType: 'location'; + name: string; + currency: string; + items: InvoiceItemDto[]; + termsNotes?: string; + title?: string; + contactDetails: ContactDetailsDto; + invoiceNumber?: string; + issueDate: string; + dueDate?: string; + sentTo: SentToDto; + liveMode: boolean; + automaticTaxesEnabled?: boolean; + paymentSchedule?: PaymentScheduleDto; + lateFeesConfiguration?: LateFeesConfigurationDto; + tipsConfiguration?: TipsConfigurationDto; + invoiceNumberPrefix?: string; + paymentMethods?: PaymentMethodDto; + attachments?: AttachmentsDto[]; + id?: string; + includeTermsNote?: boolean; + action: 'draft' | 'send'; + userId: string; + discount?: DiscountDto; + businessDetails?: BusinessDetailsDto; +} + +export interface Text2PayInvoiceResponseDto { + invoice: DefaultInvoiceResponseDto; + invoiceUrl: string; +} + +// Invoice Management Types +export interface GenerateInvoiceNumberResponse { + invoiceNumber: number; +} + +export interface CreateInvoiceDto { + altId: string; + altType: 'location'; + name: string; + businessDetails: BusinessDetailsDto; + currency: string; + items: InvoiceItemDto[]; + discount: DiscountDto; + termsNotes?: string; + title?: string; + contactDetails: ContactDetailsDto; + invoiceNumber?: string; + issueDate: string; + dueDate?: string; + sentTo: SentToDto; + liveMode: boolean; + automaticTaxesEnabled?: boolean; + paymentSchedule?: PaymentScheduleDto; + lateFeesConfiguration?: LateFeesConfigurationDto; + tipsConfiguration?: TipsConfigurationDto; + invoiceNumberPrefix?: string; + paymentMethods?: PaymentMethodDto; + attachments?: AttachmentsDto[]; +} + +export interface UpdateInvoiceDto { + altId: string; + altType: 'location'; + name: string; + title?: string; + currency: string; + description?: string; + businessDetails?: BusinessDetailsDto; + invoiceNumber?: string; + contactId?: string; + contactDetails?: ContactDetailsDto; + termsNotes?: string; + discount?: DiscountDto; + invoiceItems: InvoiceItemDto[]; + automaticTaxesEnabled?: boolean; + liveMode?: boolean; + issueDate: string; + dueDate: string; + paymentSchedule?: PaymentScheduleDto; + tipsConfiguration?: TipsConfigurationDto; + xeroDetails?: any; + invoiceNumberPrefix?: string; + paymentMethods?: PaymentMethodDto; + attachments?: AttachmentsDto[]; +} + +export interface VoidInvoiceDto { + altId: string; + altType: 'location'; +} + +export interface InvoiceSettingsSenderConfigurationDto { + fromName?: string; + fromEmail?: string; +} + +export interface SendInvoiceDto { + altId: string; + altType: 'location'; + userId: string; + action: 'sms_and_email' | 'send_manually' | 'email' | 'sms'; + liveMode: boolean; + sentFrom?: InvoiceSettingsSenderConfigurationDto; + autoPayment?: AutoPaymentDetailsDto; +} + +export interface SendInvoicesResponseDto { + invoice: DefaultInvoiceResponseDto; + smsData: any; + emailData: any; +} + +// Record Payment Types +export interface ChequeDto { + number: string; +} + +export interface RecordPaymentDto { + altId: string; + altType: 'location'; + mode: 'cash' | 'card' | 'cheque' | 'bank_transfer' | 'other'; + card: CardDto; + cheque: ChequeDto; + notes: string; + amount?: number; + meta?: any; + paymentScheduleIds?: string[]; +} + +export interface RecordPaymentResponseDto { + success: boolean; + invoice: DefaultInvoiceResponseDto; +} + +// Invoice Stats Types +export interface PatchInvoiceStatsLastViewedDto { + invoiceId: string; +} + +// Estimate Types +export interface SendEstimateDto { + altId: string; + altType: 'location'; + action: 'sms_and_email' | 'send_manually' | 'email' | 'sms'; + liveMode: boolean; + userId: string; + sentFrom?: InvoiceSettingsSenderConfigurationDto; + estimateName?: string; +} + +export interface FrequencySettingsDto { + enabled: boolean; + schedule?: ScheduleOptionsDto; +} + +export interface AutoInvoicingDto { + enabled: boolean; + directPayments?: boolean; +} + +export interface CreateEstimatesDto { + altId: string; + altType: 'location'; + name: string; + businessDetails: BusinessDetailsDto; + currency: string; + items: InvoiceItemDto[]; + liveMode?: boolean; + discount: DiscountDto; + termsNotes?: string; + title?: string; + contactDetails: ContactDetailsDto; + estimateNumber?: number; + issueDate?: string; + expiryDate?: string; + sentTo?: SentToDto; + automaticTaxesEnabled?: boolean; + meta?: any; + sendEstimateDetails?: SendEstimateDto; + frequencySettings: FrequencySettingsDto; + estimateNumberPrefix?: string; + userId?: string; + attachments?: AttachmentsDto[]; + autoInvoice?: AutoInvoicingDto; +} + +export interface UpdateEstimateDto { + altId: string; + altType: 'location'; + name: string; + businessDetails: BusinessDetailsDto; + currency: string; + items: InvoiceItemDto[]; + liveMode?: boolean; + discount: DiscountDto; + termsNotes?: string; + title?: string; + contactDetails: ContactDetailsDto; + estimateNumber?: number; + issueDate?: string; + expiryDate?: string; + sentTo?: SentToDto; + automaticTaxesEnabled?: boolean; + meta?: any; + sendEstimateDetails?: SendEstimateDto; + frequencySettings: FrequencySettingsDto; + estimateNumberPrefix?: string; + userId?: string; + attachments?: AttachmentsDto[]; + autoInvoice?: AutoInvoicingDto; + estimateStatus?: 'all' | 'draft' | 'sent' | 'accepted' | 'declined' | 'invoiced' | 'viewed'; +} + +export interface EstimateResponseDto { + altId: string; + altType: string; + _id: string; + liveMode: boolean; + deleted: boolean; + name: string; + currency: string; + businessDetails: any; + items: any[]; + discount: DiscountDto; + title: string; + estimateNumberPrefix?: string; + attachments?: AttachmentsDto[]; + updatedBy?: string; + total: number; + createdAt: string; + updatedAt: string; + __v: number; + automaticTaxesEnabled: boolean; + termsNotes?: string; + companyId?: string; + contactDetails?: any; + issueDate?: string; + expiryDate?: string; + sentBy?: string; + automaticTaxesCalculated?: boolean; + meta?: any; + estimateActionHistory?: string[]; + sentTo?: any; + frequencySettings?: FrequencySettingsDto; + lastVisitedAt?: string; + totalamountInUSD?: number; + autoInvoice?: any; + traceId?: string; +} + +export interface GenerateEstimateNumberResponse { + estimateNumber: number; + traceId: string; +} + +export interface AltDto { + altId: string; + altType: 'location'; +} + +export interface CreateInvoiceFromEstimateDto { + altId: string; + altType: 'location'; + markAsInvoiced: boolean; + version?: 'v1' | 'v2'; +} + +export interface CreateInvoiceFromEstimateResponseDto { + estimate: EstimateResponseDto; + invoice: DefaultInvoiceResponseDto; +} + +export interface ListEstimatesResponseDto { + estimates: string[]; + total: number; + traceId: string; +} + +export interface EstimateIdParam { + estimateId: string; +} + +// Estimate Template Types +export interface EstimateTemplatesDto { + altId: string; + altType: 'location'; + name: string; + businessDetails: BusinessDetailsDto; + currency: string; + items: any[]; + liveMode?: boolean; + discount: DiscountDto; + termsNotes?: string; + title?: string; + automaticTaxesEnabled?: boolean; + meta?: any; + sendEstimateDetails?: SendEstimateDto; + estimateNumberPrefix?: string; + attachments?: AttachmentsDto[]; +} + +export interface EstimateTemplateResponseDto { + altId: string; + altType: string; + _id: string; + liveMode: boolean; + deleted: boolean; + name: string; + currency: string; + businessDetails: any; + items: any[]; + discount: DiscountDto; + title: string; + estimateNumberPrefix?: string; + attachments?: AttachmentsDto[]; + updatedBy?: string; + total: number; + createdAt: string; + updatedAt: string; + __v: number; + automaticTaxesEnabled: boolean; + termsNotes?: string; +} + +export interface ListEstimateTemplateResponseDto { + data: string[]; + totalCount: number; + traceId: string; +} + +// Invoice List Types +export interface TotalSummaryDto { + subTotal: number; + discount: number; + tax: number; +} + +export interface ReminderDto { + enabled: boolean; + emailTemplate: string; + smsTemplate: string; + emailSubject: string; + reminderId: string; + reminderName: string; + reminderTime: 'before' | 'after'; + intervalType: 'yearly' | 'monthly' | 'weekly' | 'daily' | 'hourly' | 'minutely' | 'secondly'; + maxReminders: number; + reminderInvoiceCondition: 'invoice_sent' | 'invoice_overdue'; + reminderNumber: number; + startTime?: string; + endTime?: string; + timezone?: string; +} + +export interface ReminderSettingsDto { + defaultEmailTemplateId: string; + reminders: ReminderDto[]; +} + +export interface RemindersConfigurationDto { + reminderExecutionDetailsList: any; + reminderSettings: ReminderSettingsDto; +} + +export interface GetInvoiceResponseDto { + _id: string; + status: 'draft' | 'sent' | 'payment_processing' | 'paid' | 'void' | 'partially_paid'; + liveMode: boolean; + amountPaid: number; + altId: string; + altType: string; + name: string; + businessDetails: any; + invoiceNumber: string; + currency: string; + contactDetails: any; + issueDate: string; + dueDate: string; + discount: any; + invoiceItems: any[]; + total: number; + title: string; + amountDue: number; + createdAt: string; + updatedAt: string; + automaticTaxesEnabled?: boolean; + automaticTaxesCalculated?: boolean; + paymentSchedule?: any; + totalSummary: TotalSummaryDto; + remindersConfiguration?: RemindersConfigurationDto; +} + +export interface ListInvoicesResponseDto { + invoices: GetInvoiceResponseDto[]; + total: number; +} + +// Response Types that extend base types +export interface CreateInvoiceTemplateResponseDto extends InvoiceTemplate {} +export interface UpdateInvoiceTemplateResponseDto extends InvoiceTemplate {} +export interface DeleteInvoiceTemplateResponseDto { + success: boolean; +} + +export interface CreateInvoiceScheduleResponseDto extends InvoiceSchedule {} +export interface UpdateInvoiceScheduleResponseDto extends InvoiceSchedule {} +export interface GetScheduleResponseDto extends InvoiceSchedule {} +export interface UpdateAndScheduleInvoiceScheduleResponseDto extends InvoiceSchedule {} +export interface ScheduleInvoiceScheduleResponseDto extends InvoiceSchedule {} +export interface AutoPaymentInvoiceScheduleResponseDto extends InvoiceSchedule {} +export interface CancelInvoiceScheduleResponseDto extends InvoiceSchedule {} +export interface DeleteInvoiceScheduleResponseDto { + success: boolean; +} + +export interface CreateInvoiceResponseDto extends DefaultInvoiceResponseDto {} +export interface UpdateInvoiceResponseDto extends DefaultInvoiceResponseDto {} +export interface DeleteInvoiceResponseDto extends DefaultInvoiceResponseDto {} +export interface VoidInvoiceResponseDto extends DefaultInvoiceResponseDto {} diff --git a/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/calendar-widget-CUbShwNj.js b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/calendar-widget-CUbShwNj.js new file mode 100644 index 0000000..d5bfbd1 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/calendar-widget-CUbShwNj.js @@ -0,0 +1 @@ +import{j as t,A as k,r as j,f as M,R as C,a as A}from"./styles-CphAgR3l.js";function R({data:c}){const{calendar:g,events:a,startDate:p}=c,[d,s]=j.useState("month"),[i,n]=j.useState(new Date(p)),y=e=>{const r=e.getFullYear(),l=e.getMonth(),x=new Date(r,l,1),m=new Date(r,l+1,0),h=[],N=x.getDay();for(let o=N-1;o>=0;o--)h.push(new Date(r,l,-o));for(let o=1;o<=m.getDate();o++)h.push(new Date(r,l,o));const T=42-h.length;for(let o=1;o<=T;o++)h.push(new Date(r,l+1,o));return h},v=e=>{const r=e.toISOString().split("T")[0];return a.filter(l=>l.startTime?l.startTime.split("T")[0]===r:!1)},b=e=>{const r=new Date;return e.toDateString()===r.toDateString()},D=e=>e.getMonth()===i.getMonth(),f=e=>{const r=new Date(i);r.setMonth(r.getMonth()+e),n(r)},w=y(i),S=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];return t.jsxs("div",{className:"ghl-app",children:[t.jsxs("div",{className:"ghl-card",children:[t.jsx("div",{className:"ghl-card-header",children:t.jsxs("div",{className:"ghl-flex ghl-justify-between ghl-items-center",children:[t.jsxs("div",{children:[t.jsx("h2",{style:{fontSize:18,fontWeight:600},children:g?.name||"Calendar"}),t.jsxs("p",{className:"ghl-text-sm ghl-text-muted",children:[a.length," events"]})]}),t.jsx("div",{className:"ghl-flex ghl-gap-2",children:["month","week","day"].map(e=>t.jsx("button",{className:`ghl-btn ghl-btn-sm ${d===e?"ghl-btn-primary":"ghl-btn-secondary"}`,onClick:()=>s(e),children:e.charAt(0).toUpperCase()+e.slice(1)},e))})]})}),t.jsx("div",{style:{padding:"12px 16px",borderBottom:"1px solid var(--ghl-border)"},children:t.jsxs("div",{className:"ghl-flex ghl-justify-between ghl-items-center",children:[t.jsx("button",{className:"ghl-btn ghl-btn-secondary ghl-btn-sm",onClick:()=>f(-1),children:"← Prev"}),t.jsx("span",{className:"ghl-font-semibold",children:i.toLocaleDateString("en-US",{month:"long",year:"numeric"})}),t.jsx("button",{className:"ghl-btn ghl-btn-secondary ghl-btn-sm",onClick:()=>f(1),children:"Next →"})]})}),t.jsxs("div",{className:"ghl-card-body",style:{padding:0},children:[d==="month"&&t.jsxs("div",{children:[t.jsx("div",{style:{display:"grid",gridTemplateColumns:"repeat(7, 1fr)",borderBottom:"1px solid var(--ghl-border)"},children:S.map(e=>t.jsx("div",{style:{padding:8,textAlign:"center",fontWeight:600,fontSize:12,color:"var(--ghl-text-secondary)",background:"var(--ghl-bg-secondary)"},children:e},e))}),t.jsx("div",{style:{display:"grid",gridTemplateColumns:"repeat(7, 1fr)"},children:w.map((e,r)=>{const l=v(e),x=D(e),m=b(e);return t.jsxs("div",{style:{minHeight:80,padding:4,borderRight:(r+1)%7!==0?"1px solid var(--ghl-border)":"none",borderBottom:"1px solid var(--ghl-border)",background:m?"rgba(79, 70, 229, 0.05)":"transparent",opacity:x?1:.4},children:[t.jsx("div",{style:{width:24,height:24,borderRadius:"50%",display:"flex",alignItems:"center",justifyContent:"center",fontSize:12,fontWeight:m?600:400,background:m?"var(--ghl-primary)":"transparent",color:m?"white":"inherit",marginBottom:4},children:e.getDate()}),l.slice(0,2).map(h=>t.jsx("div",{style:{fontSize:10,padding:"2px 4px",marginBottom:2,borderRadius:3,background:u(h.appointmentStatus),color:"white",whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"},children:h.title||"Appointment"},h.id)),l.length>2&&t.jsxs("div",{className:"ghl-text-sm ghl-text-muted",style:{fontSize:10},children:["+",l.length-2," more"]})]},r)})})]}),d==="week"&&t.jsx(B,{events:a,currentDate:i}),d==="day"&&t.jsx(E,{events:a,currentDate:i})]})]}),t.jsxs("div",{className:"ghl-card",style:{marginTop:16},children:[t.jsx("div",{className:"ghl-card-header",children:t.jsx("h3",{className:"ghl-font-semibold",children:"Upcoming Events"})}),t.jsx("div",{className:"ghl-card-body",style:{padding:0},children:a.length===0?t.jsx("div",{className:"ghl-empty",children:t.jsx("p",{children:"No upcoming events"})}):t.jsx("div",{children:a.slice(0,5).map(e=>t.jsxs("div",{style:{padding:"12px 16px",borderBottom:"1px solid var(--ghl-border)",display:"flex",alignItems:"center",gap:12},children:[t.jsx("div",{style:{width:4,height:40,borderRadius:2,background:u(e.appointmentStatus)}}),t.jsxs("div",{style:{flex:1},children:[t.jsx("div",{className:"ghl-font-medium",children:e.title||"Appointment"}),e.contact&&t.jsx("div",{className:"ghl-text-sm ghl-text-muted",children:e.contact.name||`${e.contact.firstName||""} ${e.contact.lastName||""}`})]}),t.jsxs("div",{style:{textAlign:"right"},children:[t.jsx("div",{className:"ghl-text-sm",children:M(e.startTime)}),e.startTime&&t.jsx("div",{className:"ghl-text-sm ghl-text-muted",children:new Date(e.startTime).toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit"})})]})]},e.id))})})]})]})}function B({events:c,currentDate:g}){const a=new Date(g);a.setDate(g.getDate()-g.getDay());const p=Array.from({length:7},(d,s)=>{const i=new Date(a);return i.setDate(a.getDate()+s),i});return t.jsx("div",{style:{display:"grid",gridTemplateColumns:"repeat(7, 1fr)"},children:p.map((d,s)=>{const i=c.filter(n=>n.startTime?n.startTime.split("T")[0]===d.toISOString().split("T")[0]:!1);return t.jsxs("div",{style:{borderRight:s<6?"1px solid var(--ghl-border)":"none",minHeight:200},children:[t.jsxs("div",{style:{padding:8,textAlign:"center",borderBottom:"1px solid var(--ghl-border)",background:"var(--ghl-bg-secondary)"},children:[t.jsx("div",{className:"ghl-text-sm ghl-text-muted",children:d.toLocaleDateString("en-US",{weekday:"short"})}),t.jsx("div",{className:"ghl-font-semibold",children:d.getDate()})]}),t.jsx("div",{style:{padding:4},children:i.map(n=>t.jsxs("div",{style:{padding:6,marginBottom:4,borderRadius:4,background:u(n.appointmentStatus),color:"white",fontSize:11},children:[t.jsx("div",{style:{fontWeight:500},children:n.title||"Appointment"}),n.startTime&&t.jsx("div",{style:{opacity:.8},children:new Date(n.startTime).toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit"})})]},n.id))})]},s)})})}function E({events:c,currentDate:g}){const a=Array.from({length:24},(s,i)=>i),p=g.toISOString().split("T")[0],d=c.filter(s=>s.startTime?.startsWith(p));return t.jsx("div",{style:{maxHeight:400,overflowY:"auto"},children:a.map(s=>{const i=d.filter(n=>n.startTime?new Date(n.startTime).getHours()===s:!1);return t.jsxs("div",{style:{display:"flex",borderBottom:"1px solid var(--ghl-border)",minHeight:48},children:[t.jsx("div",{style:{width:60,padding:8,fontSize:12,color:"var(--ghl-text-muted)",borderRight:"1px solid var(--ghl-border)"},children:s===0?"12 AM":s<12?`${s} AM`:s===12?"12 PM":`${s-12} PM`}),t.jsx("div",{style:{flex:1,padding:4},children:i.map(n=>t.jsx("div",{style:{padding:6,borderRadius:4,background:u(n.appointmentStatus),color:"white",fontSize:12,marginBottom:2},children:n.title||"Appointment"},n.id))})]},s)})})}function u(c){return{confirmed:"#22c55e",cancelled:"#ef4444",pending:"#f59e0b",completed:"#3b82f6","no-show":"#9ca3af"}[c||""]||"#4f46e5"}function W(){return t.jsx(k,{children:c=>t.jsx(R,{data:c})})}C.createRoot(document.getElementById("root")).render(A.createElement(W)); diff --git a/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/contact-card-CFJe96SR.js b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/contact-card-CFJe96SR.js new file mode 100644 index 0000000..51518e5 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/contact-card-CFJe96SR.js @@ -0,0 +1 @@ +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;re.jsx(o,{contact:s})})}m.createRoot(document.getElementById("root")).render(g.createElement(c)); diff --git a/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/contact-grid-C_Uxn-WJ.js b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/contact-grid-C_Uxn-WJ.js new file mode 100644 index 0000000..fc803fa --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/contact-grid-C_Uxn-WJ.js @@ -0,0 +1 @@ +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;ne.jsx(D,{data:a})})}$.createRoot(document.getElementById("root")).render(z.createElement(E)); diff --git a/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/conversation-thread-DBGTC45D.js b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/conversation-thread-DBGTC45D.js new file mode 100644 index 0000000..0931e30 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/conversation-thread-DBGTC45D.js @@ -0,0 +1 @@ +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)); diff --git a/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/invoice-preview-DphRvjHB.js b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/invoice-preview-DphRvjHB.js new file mode 100644 index 0000000..5394987 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/invoice-preview-DphRvjHB.js @@ -0,0 +1 @@ +import{j as s,A as c,c as i,f as d,b as a,R as m,a as x}from"./styles-CphAgR3l.js";function g({invoice:e}){const h=i(e.status||""),l=e.currency||"USD",n=t=>{if(!t)return null;const r=[t.address,t.city,t.state,t.postalCode,t.country].filter(Boolean);return r.length>0?r.join(", "):null};return s.jsx("div",{className:"ghl-app",children:s.jsxs("div",{className:"ghl-card",children:[s.jsx("div",{className:"ghl-card-header",style:{background:"var(--ghl-bg)"},children:s.jsxs("div",{className:"ghl-flex ghl-justify-between ghl-items-center",children:[s.jsxs("div",{className:"ghl-flex ghl-items-center ghl-gap-4",children:[e.businessDetails?.logoUrl&&s.jsx("img",{src:e.businessDetails.logoUrl,alt:"Logo",style:{height:48,width:"auto",objectFit:"contain"}}),s.jsxs("div",{children:[s.jsx("h1",{style:{fontSize:24,fontWeight:700,color:"var(--ghl-text)"},children:"INVOICE"}),s.jsxs("p",{className:"ghl-text-muted",children:["#",e.invoiceNumber||e._id||e.id]})]})]}),s.jsx("div",{className:`ghl-badge ghl-badge-${h}`,style:{fontSize:14,padding:"6px 16px"},children:e.status?.toUpperCase()||"DRAFT"})]})}),s.jsxs("div",{className:"ghl-card-body",children:[s.jsxs("div",{className:"ghl-grid ghl-grid-2",style:{marginBottom:24},children:[s.jsxs("div",{children:[s.jsx("h3",{className:"ghl-text-sm ghl-text-muted",style:{marginBottom:8},children:"FROM"}),e.businessDetails?s.jsxs("div",{children:[s.jsx("div",{className:"ghl-font-semibold",children:e.businessDetails.name}),n(e.businessDetails)&&s.jsx("div",{className:"ghl-text-sm ghl-text-secondary",style:{marginTop:4},children:n(e.businessDetails)}),e.businessDetails.email&&s.jsx("div",{className:"ghl-text-sm ghl-text-secondary",children:e.businessDetails.email}),e.businessDetails.phone&&s.jsx("div",{className:"ghl-text-sm ghl-text-secondary",children:e.businessDetails.phone})]}):s.jsx("span",{className:"ghl-text-muted",children:"Not specified"})]}),s.jsxs("div",{children:[s.jsx("h3",{className:"ghl-text-sm ghl-text-muted",style:{marginBottom:8},children:"BILL TO"}),e.contactDetails?s.jsxs("div",{children:[s.jsx("div",{className:"ghl-font-semibold",children:e.contactDetails.name||e.contactDetails.companyName}),e.contactDetails.companyName&&e.contactDetails.name&&s.jsx("div",{className:"ghl-text-sm ghl-text-secondary",children:e.contactDetails.companyName}),n(e.contactDetails)&&s.jsx("div",{className:"ghl-text-sm ghl-text-secondary",style:{marginTop:4},children:n(e.contactDetails)}),e.contactDetails.email&&s.jsx("div",{className:"ghl-text-sm ghl-text-secondary",children:e.contactDetails.email})]}):s.jsx("span",{className:"ghl-text-muted",children:"Not specified"})]})]}),s.jsxs("div",{className:"ghl-grid ghl-grid-3",style:{marginBottom:24},children:[s.jsxs("div",{children:[s.jsx("span",{className:"ghl-text-sm ghl-text-muted",children:"Issue Date"}),s.jsx("div",{className:"ghl-font-medium",children:d(e.issueDate)})]}),s.jsxs("div",{children:[s.jsx("span",{className:"ghl-text-sm ghl-text-muted",children:"Due Date"}),s.jsx("div",{className:"ghl-font-medium",children:d(e.dueDate)})]}),e.paidAt&&s.jsxs("div",{children:[s.jsx("span",{className:"ghl-text-sm ghl-text-muted",children:"Paid Date"}),s.jsx("div",{className:"ghl-font-medium",style:{color:"var(--ghl-success)"},children:d(e.paidAt)})]})]}),s.jsx("div",{style:{marginBottom:24},children:s.jsxs("table",{className:"ghl-table",children:[s.jsx("thead",{children:s.jsxs("tr",{children:[s.jsx("th",{style:{width:"50%"},children:"Item"}),s.jsx("th",{style:{textAlign:"center"},children:"Qty"}),s.jsx("th",{style:{textAlign:"right"},children:"Price"}),s.jsx("th",{style:{textAlign:"right"},children:"Amount"})]})}),s.jsx("tbody",{children:e.invoiceItems&&e.invoiceItems.length>0?e.invoiceItems.map((t,r)=>s.jsxs("tr",{children:[s.jsxs("td",{children:[s.jsx("div",{className:"ghl-font-medium",children:t.name||"Item"}),t.description&&s.jsx("div",{className:"ghl-text-sm ghl-text-muted",children:t.description})]}),s.jsx("td",{style:{textAlign:"center"},children:t.quantity||1}),s.jsx("td",{style:{textAlign:"right"},children:a(t.price,l)}),s.jsx("td",{style:{textAlign:"right"},children:a(t.amount,l)})]},r)):s.jsx("tr",{children:s.jsx("td",{colSpan:4,className:"ghl-text-muted",style:{textAlign:"center"},children:"No items"})})})]})}),s.jsx("div",{style:{display:"flex",justifyContent:"flex-end"},children:s.jsxs("div",{style:{width:280},children:[s.jsxs("div",{className:"ghl-flex ghl-justify-between",style:{padding:"8px 0"},children:[s.jsx("span",{className:"ghl-text-muted",children:"Subtotal"}),s.jsx("span",{children:a(e.subTotal,l)})]}),e.discount&&e.discount>0&&s.jsxs("div",{className:"ghl-flex ghl-justify-between",style:{padding:"8px 0"},children:[s.jsx("span",{className:"ghl-text-muted",children:"Discount"}),s.jsxs("span",{style:{color:"var(--ghl-success)"},children:["-",a(e.discount,l)]})]}),e.taxAmount&&e.taxAmount>0&&s.jsxs("div",{className:"ghl-flex ghl-justify-between",style:{padding:"8px 0"},children:[s.jsx("span",{className:"ghl-text-muted",children:"Tax"}),s.jsx("span",{children:a(e.taxAmount,l)})]}),s.jsxs("div",{className:"ghl-flex ghl-justify-between",style:{padding:"12px 0",marginTop:8,borderTop:"2px solid var(--ghl-border)"},children:[s.jsx("span",{className:"ghl-font-semibold",children:"Total"}),s.jsx("span",{className:"ghl-font-semibold",style:{fontSize:18},children:a(e.totalAmount,l)})]}),e.amountDue!==void 0&&e.amountDue!==e.totalAmount&&s.jsxs("div",{className:"ghl-flex ghl-justify-between",style:{padding:"12px 16px",marginTop:8,background:e.amountDue===0?"rgba(34, 197, 94, 0.1)":"rgba(239, 68, 68, 0.1)",borderRadius:"var(--ghl-radius)"},children:[s.jsx("span",{className:"ghl-font-semibold",children:"Amount Due"}),s.jsx("span",{className:"ghl-font-semibold",style:{fontSize:18,color:e.amountDue===0?"var(--ghl-success)":"var(--ghl-danger)"},children:a(e.amountDue,l)})]}),e.amountPaid!==void 0&&e.amountPaid>0&&s.jsxs("div",{className:"ghl-flex ghl-justify-between",style:{padding:"8px 0",marginTop:8},children:[s.jsx("span",{className:"ghl-text-muted",children:"Amount Paid"}),s.jsx("span",{style:{color:"var(--ghl-success)"},children:a(e.amountPaid,l)})]})]})})]}),s.jsx("div",{className:"ghl-card-footer",children:s.jsxs("div",{className:"ghl-flex ghl-justify-between ghl-items-center ghl-text-sm ghl-text-muted",children:[s.jsxs("span",{children:["Created: ",d(e.createdAt),e.updatedAt&&` | Updated: ${d(e.updatedAt)}`]}),e.sentAt&&s.jsxs("span",{children:["Sent: ",d(e.sentAt)]})]})})]})})}function o(){return s.jsx(c,{children:e=>s.jsx(g,{invoice:e})})}m.createRoot(document.getElementById("root")).render(x.createElement(o)); diff --git a/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/opportunity-kanban-CSzRcmdW.js b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/opportunity-kanban-CSzRcmdW.js new file mode 100644 index 0000000..e71da81 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/opportunity-kanban-CSzRcmdW.js @@ -0,0 +1 @@ +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)); diff --git a/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/style-BaFxk78P.css b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/style-BaFxk78P.css new file mode 100644 index 0000000..e8750e8 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/style-BaFxk78P.css @@ -0,0 +1 @@ +: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} diff --git a/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/styles-CphAgR3l.js b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/styles-CphAgR3l.js new file mode 100644 index 0000000..356d2c8 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/assets/styles-CphAgR3l.js @@ -0,0 +1,9 @@ +(function(){const P=document.createElement("link").relList;if(P&&P.supports&&P.supports("modulepreload"))return;for(const X of document.querySelectorAll('link[rel="modulepreload"]'))o(X);new MutationObserver(X=>{for(const L of X)if(L.type==="childList")for(const vl of L.addedNodes)vl.tagName==="LINK"&&vl.rel==="modulepreload"&&o(vl)}).observe(document,{childList:!0,subtree:!0});function w(X){const L={};return X.integrity&&(L.integrity=X.integrity),X.referrerPolicy&&(L.referrerPolicy=X.referrerPolicy),X.crossOrigin==="use-credentials"?L.credentials="include":X.crossOrigin==="anonymous"?L.credentials="omit":L.credentials="same-origin",L}function o(X){if(X.ep)return;X.ep=!0;const L=w(X);fetch(X.href,L)}})();function rs(r){return r&&r.__esModule&&Object.prototype.hasOwnProperty.call(r,"default")?r.default:r}var ni={exports:{}},C={};var ds;function wd(){if(ds)return C;ds=1;var r=Symbol.for("react.transitional.element"),P=Symbol.for("react.portal"),w=Symbol.for("react.fragment"),o=Symbol.for("react.strict_mode"),X=Symbol.for("react.profiler"),L=Symbol.for("react.consumer"),vl=Symbol.for("react.context"),Ol=Symbol.for("react.forward_ref"),p=Symbol.for("react.suspense"),A=Symbol.for("react.memo"),W=Symbol.for("react.lazy"),R=Symbol.for("react.activity"),sl=Symbol.iterator;function wl(v){return v===null||typeof v!="object"?null:(v=sl&&v[sl]||v["@@iterator"],typeof v=="function"?v:null)}var Bl={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},ql=Object.assign,Dt={};function Wl(v,E,O){this.props=v,this.context=E,this.refs=Dt,this.updater=O||Bl}Wl.prototype.isReactComponent={},Wl.prototype.setState=function(v,E){if(typeof v!="object"&&typeof v!="function"&&v!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,v,E,"setState")},Wl.prototype.forceUpdate=function(v){this.updater.enqueueForceUpdate(this,v,"forceUpdate")};function Wt(){}Wt.prototype=Wl.prototype;function Nl(v,E,O){this.props=v,this.context=E,this.refs=Dt,this.updater=O||Bl}var nt=Nl.prototype=new Wt;nt.constructor=Nl,ql(nt,Wl.prototype),nt.isPureReactComponent=!0;var Tt=Array.isArray;function Gl(){}var K={H:null,A:null,T:null,S:null},Xl=Object.prototype.hasOwnProperty;function Et(v,E,O){var D=O.ref;return{$$typeof:r,type:v,key:E,ref:D!==void 0?D:null,props:O}}function ju(v,E){return Et(v.type,E,v.props)}function At(v){return typeof v=="object"&&v!==null&&v.$$typeof===r}function Ql(v){var E={"=":"=0",":":"=2"};return"$"+v.replace(/[=:]/g,function(O){return E[O]})}var Tu=/\/+/g;function Ut(v,E){return typeof v=="object"&&v!==null&&v.key!=null?Ql(""+v.key):E.toString(36)}function St(v){switch(v.status){case"fulfilled":return v.value;case"rejected":throw v.reason;default:switch(typeof v.status=="string"?v.then(Gl,Gl):(v.status="pending",v.then(function(E){v.status==="pending"&&(v.status="fulfilled",v.value=E)},function(E){v.status==="pending"&&(v.status="rejected",v.reason=E)})),v.status){case"fulfilled":return v.value;case"rejected":throw v.reason}}throw v}function b(v,E,O,D,Y){var Q=typeof v;(Q==="undefined"||Q==="boolean")&&(v=null);var I=!1;if(v===null)I=!0;else switch(Q){case"bigint":case"string":case"number":I=!0;break;case"object":switch(v.$$typeof){case r:case P:I=!0;break;case W:return I=v._init,b(I(v._payload),E,O,D,Y)}}if(I)return Y=Y(v),I=D===""?"."+Ut(v,0):D,Tt(Y)?(O="",I!=null&&(O=I.replace(Tu,"$&/")+"/"),b(Y,E,O,"",function(Oa){return Oa})):Y!=null&&(At(Y)&&(Y=ju(Y,O+(Y.key==null||v&&v.key===Y.key?"":(""+Y.key).replace(Tu,"$&/")+"/")+I)),E.push(Y)),1;I=0;var Cl=D===""?".":D+":";if(Tt(v))for(var ol=0;ol>>1,fl=b[ul];if(0>>1;ulX(O,q))DX(Y,O)?(b[ul]=Y,b[D]=q,ul=D):(b[ul]=O,b[E]=q,ul=E);else if(DX(Y,q))b[ul]=Y,b[D]=q,ul=D;else break l}}return _}function X(b,_){var q=b.sortIndex-_.sortIndex;return q!==0?q:b.id-_.id}if(r.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var L=performance;r.unstable_now=function(){return L.now()}}else{var vl=Date,Ol=vl.now();r.unstable_now=function(){return vl.now()-Ol}}var p=[],A=[],W=1,R=null,sl=3,wl=!1,Bl=!1,ql=!1,Dt=!1,Wl=typeof setTimeout=="function"?setTimeout:null,Wt=typeof clearTimeout=="function"?clearTimeout:null,Nl=typeof setImmediate<"u"?setImmediate:null;function nt(b){for(var _=w(A);_!==null;){if(_.callback===null)o(A);else if(_.startTime<=b)o(A),_.sortIndex=_.expirationTime,P(p,_);else break;_=w(A)}}function Tt(b){if(ql=!1,nt(b),!Bl)if(w(p)!==null)Bl=!0,Gl||(Gl=!0,Ql());else{var _=w(A);_!==null&&St(Tt,_.startTime-b)}}var Gl=!1,K=-1,Xl=5,Et=-1;function ju(){return Dt?!0:!(r.unstable_now()-Etb&&ju());){var ul=R.callback;if(typeof ul=="function"){R.callback=null,sl=R.priorityLevel;var fl=ul(R.expirationTime<=b);if(b=r.unstable_now(),typeof fl=="function"){R.callback=fl,nt(b),_=!0;break t}R===w(p)&&o(p),nt(b)}else o(p);R=w(p)}if(R!==null)_=!0;else{var v=w(A);v!==null&&St(Tt,v.startTime-b),_=!1}}break l}finally{R=null,sl=q,wl=!1}_=void 0}}finally{_?Ql():Gl=!1}}}var Ql;if(typeof Nl=="function")Ql=function(){Nl(At)};else if(typeof MessageChannel<"u"){var Tu=new MessageChannel,Ut=Tu.port2;Tu.port1.onmessage=At,Ql=function(){Ut.postMessage(null)}}else Ql=function(){Wl(At,0)};function St(b,_){K=Wl(function(){b(r.unstable_now())},_)}r.unstable_IdlePriority=5,r.unstable_ImmediatePriority=1,r.unstable_LowPriority=4,r.unstable_NormalPriority=3,r.unstable_Profiling=null,r.unstable_UserBlockingPriority=2,r.unstable_cancelCallback=function(b){b.callback=null},r.unstable_forceFrameRate=function(b){0>b||125ul?(b.sortIndex=q,P(A,b),w(p)===null&&b===w(A)&&(ql?(Wt(K),K=-1):ql=!0,St(Tt,q-ul))):(b.sortIndex=fl,P(p,b),Bl||wl||(Bl=!0,Gl||(Gl=!0,Ql()))),b},r.unstable_shouldYield=ju,r.unstable_wrapCallback=function(b){var _=sl;return function(){var q=sl;sl=_;try{return b.apply(this,arguments)}finally{sl=q}}}})(ii)),ii}var Ss;function $d(){return Ss||(Ss=1,ci.exports=Wd()),ci.exports}var yi={exports:{}},Rl={};var gs;function Fd(){if(gs)return Rl;gs=1;var r=si();function P(p){var A="https://react.dev/errors/"+p;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(r)}catch(P){console.error(P)}}return r(),yi.exports=Fd(),yi.exports}var zs;function Id(){if(zs)return be;zs=1;var r=$d(),P=si(),w=kd();function o(l){var t="https://react.dev/errors/"+l;if(1fl||(l.current=ul[fl],ul[fl]=null,fl--)}function O(l,t){fl++,ul[fl]=l.current,l.current=t}var D=v(null),Y=v(null),Q=v(null),I=v(null);function Cl(l,t){switch(O(Q,t),O(Y,l),O(D,null),t.nodeType){case 9:case 11:l=(l=t.documentElement)&&(l=l.namespaceURI)?Bv(l):0;break;default:if(l=t.tagName,t=t.namespaceURI)t=Bv(t),l=Gv(t,l);else switch(l){case"svg":l=1;break;case"math":l=2;break;default:l=0}}E(D),O(D,l)}function ol(){E(D),E(Y),E(Q)}function Oa(l){l.memoizedState!==null&&O(I,l);var t=D.current,u=Gv(t,l.type);t!==u&&(O(Y,l),O(D,u))}function Ee(l){Y.current===l&&(E(D),E(Y)),I.current===l&&(E(I),he._currentValue=q)}var jn,mi;function Eu(l){if(jn===void 0)try{throw Error()}catch(u){var t=u.stack.trim().match(/\n( *(at )?)/);jn=t&&t[1]||"",mi=-1)":-1e||i[a]!==d[e]){var g=` +`+i[a].replace(" at new "," at ");return l.displayName&&g.includes("")&&(g=g.replace("",l.displayName)),g}while(1<=a&&0<=e);break}}}finally{Zn=!1,Error.prepareStackTrace=u}return(u=l?l.displayName||l.name:"")?Eu(u):""}function _s(l,t){switch(l.tag){case 26:case 27:case 5:return Eu(l.type);case 16:return Eu("Lazy");case 13:return l.child!==t&&t!==null?Eu("Suspense Fallback"):Eu("Suspense");case 19:return Eu("SuspenseList");case 0:case 15:return Vn(l.type,!1);case 11:return Vn(l.type.render,!1);case 1:return Vn(l.type,!0);case 31:return Eu("Activity");default:return""}}function di(l){try{var t="",u=null;do t+=_s(l,u),u=l,l=l.return;while(l);return t}catch(a){return` +Error generating stack: `+a.message+` +`+a.stack}}var xn=Object.prototype.hasOwnProperty,Ln=r.unstable_scheduleCallback,Kn=r.unstable_cancelCallback,Os=r.unstable_shouldYield,Ms=r.unstable_requestPaint,$l=r.unstable_now,Ds=r.unstable_getCurrentPriorityLevel,hi=r.unstable_ImmediatePriority,oi=r.unstable_UserBlockingPriority,Ae=r.unstable_NormalPriority,Us=r.unstable_LowPriority,Si=r.unstable_IdlePriority,ps=r.log,Hs=r.unstable_setDisableYieldValue,Ma=null,Fl=null;function $t(l){if(typeof ps=="function"&&Hs(l),Fl&&typeof Fl.setStrictMode=="function")try{Fl.setStrictMode(Ma,l)}catch{}}var kl=Math.clz32?Math.clz32:qs,Ns=Math.log,Rs=Math.LN2;function qs(l){return l>>>=0,l===0?32:31-(Ns(l)/Rs|0)|0}var re=256,_e=262144,Oe=4194304;function Au(l){var t=l&42;if(t!==0)return t;switch(l&-l){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return l&261888;case 262144:case 524288:case 1048576:case 2097152:return l&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return l&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return l}}function Me(l,t,u){var a=l.pendingLanes;if(a===0)return 0;var e=0,n=l.suspendedLanes,f=l.pingedLanes;l=l.warmLanes;var c=a&134217727;return c!==0?(a=c&~n,a!==0?e=Au(a):(f&=c,f!==0?e=Au(f):u||(u=c&~l,u!==0&&(e=Au(u))))):(c=a&~n,c!==0?e=Au(c):f!==0?e=Au(f):u||(u=a&~l,u!==0&&(e=Au(u)))),e===0?0:t!==0&&t!==e&&(t&n)===0&&(n=e&-e,u=t&-t,n>=u||n===32&&(u&4194048)!==0)?t:e}function Da(l,t){return(l.pendingLanes&~(l.suspendedLanes&~l.pingedLanes)&t)===0}function Cs(l,t){switch(l){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function gi(){var l=Oe;return Oe<<=1,(Oe&62914560)===0&&(Oe=4194304),l}function Jn(l){for(var t=[],u=0;31>u;u++)t.push(l);return t}function Ua(l,t){l.pendingLanes|=t,t!==268435456&&(l.suspendedLanes=0,l.pingedLanes=0,l.warmLanes=0)}function Ys(l,t,u,a,e,n){var f=l.pendingLanes;l.pendingLanes=u,l.suspendedLanes=0,l.pingedLanes=0,l.warmLanes=0,l.expiredLanes&=u,l.entangledLanes&=u,l.errorRecoveryDisabledLanes&=u,l.shellSuspendCounter=0;var c=l.entanglements,i=l.expirationTimes,d=l.hiddenUpdates;for(u=f&~u;0"u")return null;try{return l.activeElement||l.body}catch{return l.body}}var Zs=/[\n"\\]/g;function ct(l){return l.replace(Zs,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function In(l,t,u,a,e,n,f,c){l.name="",f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"?l.type=f:l.removeAttribute("type"),t!=null?f==="number"?(t===0&&l.value===""||l.value!=t)&&(l.value=""+ft(t)):l.value!==""+ft(t)&&(l.value=""+ft(t)):f!=="submit"&&f!=="reset"||l.removeAttribute("value"),t!=null?Pn(l,f,ft(t)):u!=null?Pn(l,f,ft(u)):a!=null&&l.removeAttribute("value"),e==null&&n!=null&&(l.defaultChecked=!!n),e!=null&&(l.checked=e&&typeof e!="function"&&typeof e!="symbol"),c!=null&&typeof c!="function"&&typeof c!="symbol"&&typeof c!="boolean"?l.name=""+ft(c):l.removeAttribute("name")}function Hi(l,t,u,a,e,n,f,c){if(n!=null&&typeof n!="function"&&typeof n!="symbol"&&typeof n!="boolean"&&(l.type=n),t!=null||u!=null){if(!(n!=="submit"&&n!=="reset"||t!=null)){kn(l);return}u=u!=null?""+ft(u):"",t=t!=null?""+ft(t):u,c||t===l.value||(l.value=t),l.defaultValue=t}a=a??e,a=typeof a!="function"&&typeof a!="symbol"&&!!a,l.checked=c?l.checked:!!a,l.defaultChecked=!!a,f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"&&(l.name=f),kn(l)}function Pn(l,t,u){t==="number"&&pe(l.ownerDocument)===l||l.defaultValue===""+u||(l.defaultValue=""+u)}function Ju(l,t,u,a){if(l=l.options,t){t={};for(var e=0;e"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),ef=!1;if(Nt)try{var Ra={};Object.defineProperty(Ra,"passive",{get:function(){ef=!0}}),window.addEventListener("test",Ra,Ra),window.removeEventListener("test",Ra,Ra)}catch{ef=!1}var kt=null,nf=null,Ne=null;function Gi(){if(Ne)return Ne;var l,t=nf,u=t.length,a,e="value"in kt?kt.value:kt.textContent,n=e.length;for(l=0;l=Ya),xi=" ",Li=!1;function Ki(l,t){switch(l){case"keyup":return om.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Ji(l){return l=l.detail,typeof l=="object"&&"data"in l?l.data:null}var Fu=!1;function gm(l,t){switch(l){case"compositionend":return Ji(t);case"keypress":return t.which!==32?null:(Li=!0,xi);case"textInput":return l=t.data,l===xi&&Li?null:l;default:return null}}function bm(l,t){if(Fu)return l==="compositionend"||!sf&&Ki(l,t)?(l=Gi(),Ne=nf=kt=null,Fu=!1,l):null;switch(l){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:u,offset:t-l};l=a}l:{for(;u;){if(u.nextSibling){u=u.nextSibling;break l}u=u.parentNode}u=void 0}u=l0(u)}}function u0(l,t){return l&&t?l===t?!0:l&&l.nodeType===3?!1:t&&t.nodeType===3?u0(l,t.parentNode):"contains"in l?l.contains(t):l.compareDocumentPosition?!!(l.compareDocumentPosition(t)&16):!1:!1}function a0(l){l=l!=null&&l.ownerDocument!=null&&l.ownerDocument.defaultView!=null?l.ownerDocument.defaultView:window;for(var t=pe(l.document);t instanceof l.HTMLIFrameElement;){try{var u=typeof t.contentWindow.location.href=="string"}catch{u=!1}if(u)l=t.contentWindow;else break;t=pe(l.document)}return t}function hf(l){var t=l&&l.nodeName&&l.nodeName.toLowerCase();return t&&(t==="input"&&(l.type==="text"||l.type==="search"||l.type==="tel"||l.type==="url"||l.type==="password")||t==="textarea"||l.contentEditable==="true")}var Mm=Nt&&"documentMode"in document&&11>=document.documentMode,ku=null,of=null,Qa=null,Sf=!1;function e0(l,t,u){var a=u.window===u?u.document:u.nodeType===9?u:u.ownerDocument;Sf||ku==null||ku!==pe(a)||(a=ku,"selectionStart"in a&&hf(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),Qa&&Xa(Qa,a)||(Qa=a,a=On(of,"onSelect"),0>=f,e-=f,rt=1<<32-kl(t)+e|u<G?(x=U,U=null):x=U.sibling;var F=h(s,U,m[G],z);if(F===null){U===null&&(U=x);break}l&&U&&F.alternate===null&&t(s,U),y=n(F,y,G),$===null?H=F:$.sibling=F,$=F,U=x}if(G===m.length)return u(s,U),J&&qt(s,G),H;if(U===null){for(;GG?(x=U,U=null):x=U.sibling;var zu=h(s,U,F.value,z);if(zu===null){U===null&&(U=x);break}l&&U&&zu.alternate===null&&t(s,U),y=n(zu,y,G),$===null?H=zu:$.sibling=zu,$=zu,U=x}if(F.done)return u(s,U),J&&qt(s,G),H;if(U===null){for(;!F.done;G++,F=m.next())F=T(s,F.value,z),F!==null&&(y=n(F,y,G),$===null?H=F:$.sibling=F,$=F);return J&&qt(s,G),H}for(U=a(U);!F.done;G++,F=m.next())F=S(U,s,G,F.value,z),F!==null&&(l&&F.alternate!==null&&U.delete(F.key===null?G:F.key),y=n(F,y,G),$===null?H=F:$.sibling=F,$=F);return l&&U.forEach(function(Jd){return t(s,Jd)}),J&&qt(s,G),H}function nl(s,y,m,z){if(typeof m=="object"&&m!==null&&m.type===ql&&m.key===null&&(m=m.props.children),typeof m=="object"&&m!==null){switch(m.$$typeof){case wl:l:{for(var H=m.key;y!==null;){if(y.key===H){if(H=m.type,H===ql){if(y.tag===7){u(s,y.sibling),z=e(y,m.props.children),z.return=s,s=z;break l}}else if(y.elementType===H||typeof H=="object"&&H!==null&&H.$$typeof===Xl&&qu(H)===y.type){u(s,y.sibling),z=e(y,m.props),Ka(z,m),z.return=s,s=z;break l}u(s,y);break}else t(s,y);y=y.sibling}m.type===ql?(z=Uu(m.props.children,s.mode,z,m.key),z.return=s,s=z):(z=Ze(m.type,m.key,m.props,null,s.mode,z),Ka(z,m),z.return=s,s=z)}return f(s);case Bl:l:{for(H=m.key;y!==null;){if(y.key===H)if(y.tag===4&&y.stateNode.containerInfo===m.containerInfo&&y.stateNode.implementation===m.implementation){u(s,y.sibling),z=e(y,m.children||[]),z.return=s,s=z;break l}else{u(s,y);break}else t(s,y);y=y.sibling}z=rf(m,s.mode,z),z.return=s,s=z}return f(s);case Xl:return m=qu(m),nl(s,y,m,z)}if(St(m))return M(s,y,m,z);if(Ql(m)){if(H=Ql(m),typeof H!="function")throw Error(o(150));return m=H.call(m),N(s,y,m,z)}if(typeof m.then=="function")return nl(s,y,We(m),z);if(m.$$typeof===Nl)return nl(s,y,Le(s,m),z);$e(s,m)}return typeof m=="string"&&m!==""||typeof m=="number"||typeof m=="bigint"?(m=""+m,y!==null&&y.tag===6?(u(s,y.sibling),z=e(y,m),z.return=s,s=z):(u(s,y),z=Af(m,s.mode,z),z.return=s,s=z),f(s)):u(s,y)}return function(s,y,m,z){try{La=0;var H=nl(s,y,m,z);return ia=null,H}catch(U){if(U===ca||U===Je)throw U;var $=Pl(29,U,null,s.mode);return $.lanes=z,$.return=s,$}}}var Yu=D0(!0),U0=D0(!1),uu=!1;function Yf(l){l.updateQueue={baseState:l.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Bf(l,t){l=l.updateQueue,t.updateQueue===l&&(t.updateQueue={baseState:l.baseState,firstBaseUpdate:l.firstBaseUpdate,lastBaseUpdate:l.lastBaseUpdate,shared:l.shared,callbacks:null})}function au(l){return{lane:l,tag:0,payload:null,callback:null,next:null}}function eu(l,t,u){var a=l.updateQueue;if(a===null)return null;if(a=a.shared,(k&2)!==0){var e=a.pending;return e===null?t.next=t:(t.next=e.next,e.next=t),a.pending=t,t=je(l),s0(l,null,u),t}return Qe(l,a,t,u),je(l)}function Ja(l,t,u){if(t=t.updateQueue,t!==null&&(t=t.shared,(u&4194048)!==0)){var a=t.lanes;a&=l.pendingLanes,u|=a,t.lanes=u,zi(l,u)}}function Gf(l,t){var u=l.updateQueue,a=l.alternate;if(a!==null&&(a=a.updateQueue,u===a)){var e=null,n=null;if(u=u.firstBaseUpdate,u!==null){do{var f={lane:u.lane,tag:u.tag,payload:u.payload,callback:null,next:null};n===null?e=n=f:n=n.next=f,u=u.next}while(u!==null);n===null?e=n=t:n=n.next=t}else e=n=t;u={baseState:a.baseState,firstBaseUpdate:e,lastBaseUpdate:n,shared:a.shared,callbacks:a.callbacks},l.updateQueue=u;return}l=u.lastBaseUpdate,l===null?u.firstBaseUpdate=t:l.next=t,u.lastBaseUpdate=t}var Xf=!1;function wa(){if(Xf){var l=fa;if(l!==null)throw l}}function Wa(l,t,u,a){Xf=!1;var e=l.updateQueue;uu=!1;var n=e.firstBaseUpdate,f=e.lastBaseUpdate,c=e.shared.pending;if(c!==null){e.shared.pending=null;var i=c,d=i.next;i.next=null,f===null?n=d:f.next=d,f=i;var g=l.alternate;g!==null&&(g=g.updateQueue,c=g.lastBaseUpdate,c!==f&&(c===null?g.firstBaseUpdate=d:c.next=d,g.lastBaseUpdate=i))}if(n!==null){var T=e.baseState;f=0,g=d=i=null,c=n;do{var h=c.lane&-536870913,S=h!==c.lane;if(S?(V&h)===h:(a&h)===h){h!==0&&h===na&&(Xf=!0),g!==null&&(g=g.next={lane:0,tag:c.tag,payload:c.payload,callback:null,next:null});l:{var M=l,N=c;h=t;var nl=u;switch(N.tag){case 1:if(M=N.payload,typeof M=="function"){T=M.call(nl,T,h);break l}T=M;break l;case 3:M.flags=M.flags&-65537|128;case 0:if(M=N.payload,h=typeof M=="function"?M.call(nl,T,h):M,h==null)break l;T=R({},T,h);break l;case 2:uu=!0}}h=c.callback,h!==null&&(l.flags|=64,S&&(l.flags|=8192),S=e.callbacks,S===null?e.callbacks=[h]:S.push(h))}else S={lane:h,tag:c.tag,payload:c.payload,callback:c.callback,next:null},g===null?(d=g=S,i=T):g=g.next=S,f|=h;if(c=c.next,c===null){if(c=e.shared.pending,c===null)break;S=c,c=S.next,S.next=null,e.lastBaseUpdate=S,e.shared.pending=null}}while(!0);g===null&&(i=T),e.baseState=i,e.firstBaseUpdate=d,e.lastBaseUpdate=g,n===null&&(e.shared.lanes=0),yu|=f,l.lanes=f,l.memoizedState=T}}function p0(l,t){if(typeof l!="function")throw Error(o(191,l));l.call(t)}function H0(l,t){var u=l.callbacks;if(u!==null)for(l.callbacks=null,l=0;ln?n:8;var f=b.T,c={};b.T=c,ac(l,!1,t,u);try{var i=e(),d=b.S;if(d!==null&&d(c,i),i!==null&&typeof i=="object"&&typeof i.then=="function"){var g=Ym(i,a);ka(l,t,g,et(l))}else ka(l,t,a,et(l))}catch(T){ka(l,t,{then:function(){},status:"rejected",reason:T},et())}finally{_.p=n,f!==null&&c.types!==null&&(f.types=c.types),b.T=f}}function Zm(){}function tc(l,t,u,a){if(l.tag!==5)throw Error(o(476));var e=iy(l).queue;cy(l,e,t,q,u===null?Zm:function(){return yy(l),u(a)})}function iy(l){var t=l.memoizedState;if(t!==null)return t;t={memoizedState:q,baseState:q,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Gt,lastRenderedState:q},next:null};var u={};return t.next={memoizedState:u,baseState:u,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Gt,lastRenderedState:u},next:null},l.memoizedState=t,l=l.alternate,l!==null&&(l.memoizedState=t),t}function yy(l){var t=iy(l);t.next===null&&(t=l.alternate.memoizedState),ka(l,t.next.queue,{},et())}function uc(){return Ul(he)}function vy(){return gl().memoizedState}function sy(){return gl().memoizedState}function Vm(l){for(var t=l.return;t!==null;){switch(t.tag){case 24:case 3:var u=et();l=au(u);var a=eu(t,l,u);a!==null&&(Jl(a,t,u),Ja(a,t,u)),t={cache:Nf()},l.payload=t;return}t=t.return}}function xm(l,t,u){var a=et();u={lane:a,revertLane:0,gesture:null,action:u,hasEagerState:!1,eagerState:null,next:null},nn(l)?dy(t,u):(u=Tf(l,t,u,a),u!==null&&(Jl(u,l,a),hy(u,t,a)))}function my(l,t,u){var a=et();ka(l,t,u,a)}function ka(l,t,u,a){var e={lane:a,revertLane:0,gesture:null,action:u,hasEagerState:!1,eagerState:null,next:null};if(nn(l))dy(t,e);else{var n=l.alternate;if(l.lanes===0&&(n===null||n.lanes===0)&&(n=t.lastRenderedReducer,n!==null))try{var f=t.lastRenderedState,c=n(f,u);if(e.hasEagerState=!0,e.eagerState=c,Il(c,f))return Qe(l,t,e,0),cl===null&&Xe(),!1}catch{}if(u=Tf(l,t,e,a),u!==null)return Jl(u,l,a),hy(u,t,a),!0}return!1}function ac(l,t,u,a){if(a={lane:2,revertLane:Yc(),gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},nn(l)){if(t)throw Error(o(479))}else t=Tf(l,u,a,2),t!==null&&Jl(t,l,2)}function nn(l){var t=l.alternate;return l===B||t!==null&&t===B}function dy(l,t){va=Ie=!0;var u=l.pending;u===null?t.next=t:(t.next=u.next,u.next=t),l.pending=t}function hy(l,t,u){if((u&4194048)!==0){var a=t.lanes;a&=l.pendingLanes,u|=a,t.lanes=u,zi(l,u)}}var Ia={readContext:Ul,use:tn,useCallback:dl,useContext:dl,useEffect:dl,useImperativeHandle:dl,useLayoutEffect:dl,useInsertionEffect:dl,useMemo:dl,useReducer:dl,useRef:dl,useState:dl,useDebugValue:dl,useDeferredValue:dl,useTransition:dl,useSyncExternalStore:dl,useId:dl,useHostTransitionStatus:dl,useFormState:dl,useActionState:dl,useOptimistic:dl,useMemoCache:dl,useCacheRefresh:dl};Ia.useEffectEvent=dl;var oy={readContext:Ul,use:tn,useCallback:function(l,t){return Yl().memoizedState=[l,t===void 0?null:t],l},useContext:Ul,useEffect:I0,useImperativeHandle:function(l,t,u){u=u!=null?u.concat([l]):null,an(4194308,4,uy.bind(null,t,l),u)},useLayoutEffect:function(l,t){return an(4194308,4,l,t)},useInsertionEffect:function(l,t){an(4,2,l,t)},useMemo:function(l,t){var u=Yl();t=t===void 0?null:t;var a=l();if(Bu){$t(!0);try{l()}finally{$t(!1)}}return u.memoizedState=[a,t],a},useReducer:function(l,t,u){var a=Yl();if(u!==void 0){var e=u(t);if(Bu){$t(!0);try{u(t)}finally{$t(!1)}}}else e=t;return a.memoizedState=a.baseState=e,l={pending:null,lanes:0,dispatch:null,lastRenderedReducer:l,lastRenderedState:e},a.queue=l,l=l.dispatch=xm.bind(null,B,l),[a.memoizedState,l]},useRef:function(l){var t=Yl();return l={current:l},t.memoizedState=l},useState:function(l){l=Ff(l);var t=l.queue,u=my.bind(null,B,t);return t.dispatch=u,[l.memoizedState,u]},useDebugValue:Pf,useDeferredValue:function(l,t){var u=Yl();return lc(u,l,t)},useTransition:function(){var l=Ff(!1);return l=cy.bind(null,B,l.queue,!0,!1),Yl().memoizedState=l,[!1,l]},useSyncExternalStore:function(l,t,u){var a=B,e=Yl();if(J){if(u===void 0)throw Error(o(407));u=u()}else{if(u=t(),cl===null)throw Error(o(349));(V&127)!==0||B0(a,t,u)}e.memoizedState=u;var n={value:u,getSnapshot:t};return e.queue=n,I0(X0.bind(null,a,n,l),[l]),a.flags|=2048,ma(9,{destroy:void 0},G0.bind(null,a,n,u,t),null),u},useId:function(){var l=Yl(),t=cl.identifierPrefix;if(J){var u=_t,a=rt;u=(a&~(1<<32-kl(a)-1)).toString(32)+u,t="_"+t+"R_"+u,u=Pe++,0<\/script>",n=n.removeChild(n.firstChild);break;case"select":n=typeof a.is=="string"?f.createElement("select",{is:a.is}):f.createElement("select"),a.multiple?n.multiple=!0:a.size&&(n.size=a.size);break;default:n=typeof a.is=="string"?f.createElement(e,{is:a.is}):f.createElement(e)}}n[Ml]=t,n[jl]=a;l:for(f=t.child;f!==null;){if(f.tag===5||f.tag===6)n.appendChild(f.stateNode);else if(f.tag!==4&&f.tag!==27&&f.child!==null){f.child.return=f,f=f.child;continue}if(f===t)break l;for(;f.sibling===null;){if(f.return===null||f.return===t)break l;f=f.return}f.sibling.return=f.return,f=f.sibling}t.stateNode=n;l:switch(Hl(n,e,a),e){case"button":case"input":case"select":case"textarea":a=!!a.autoFocus;break l;case"img":a=!0;break l;default:a=!1}a&&Qt(t)}}return yl(t),gc(t,t.type,l===null?null:l.memoizedProps,t.pendingProps,u),null;case 6:if(l&&t.stateNode!=null)l.memoizedProps!==a&&Qt(t);else{if(typeof a!="string"&&t.stateNode===null)throw Error(o(166));if(l=Q.current,aa(t)){if(l=t.stateNode,u=t.memoizedProps,a=null,e=Dl,e!==null)switch(e.tag){case 27:case 5:a=e.memoizedProps}l[Ml]=t,l=!!(l.nodeValue===u||a!==null&&a.suppressHydrationWarning===!0||Cv(l.nodeValue,u)),l||lu(t,!0)}else l=Mn(l).createTextNode(a),l[Ml]=t,t.stateNode=l}return yl(t),null;case 31:if(u=t.memoizedState,l===null||l.memoizedState!==null){if(a=aa(t),u!==null){if(l===null){if(!a)throw Error(o(318));if(l=t.memoizedState,l=l!==null?l.dehydrated:null,!l)throw Error(o(557));l[Ml]=t}else pu(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;yl(t),l=!1}else u=Df(),l!==null&&l.memoizedState!==null&&(l.memoizedState.hydrationErrors=u),l=!0;if(!l)return t.flags&256?(tt(t),t):(tt(t),null);if((t.flags&128)!==0)throw Error(o(558))}return yl(t),null;case 13:if(a=t.memoizedState,l===null||l.memoizedState!==null&&l.memoizedState.dehydrated!==null){if(e=aa(t),a!==null&&a.dehydrated!==null){if(l===null){if(!e)throw Error(o(318));if(e=t.memoizedState,e=e!==null?e.dehydrated:null,!e)throw Error(o(317));e[Ml]=t}else pu(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;yl(t),e=!1}else e=Df(),l!==null&&l.memoizedState!==null&&(l.memoizedState.hydrationErrors=e),e=!0;if(!e)return t.flags&256?(tt(t),t):(tt(t),null)}return tt(t),(t.flags&128)!==0?(t.lanes=u,t):(u=a!==null,l=l!==null&&l.memoizedState!==null,u&&(a=t.child,e=null,a.alternate!==null&&a.alternate.memoizedState!==null&&a.alternate.memoizedState.cachePool!==null&&(e=a.alternate.memoizedState.cachePool.pool),n=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(n=a.memoizedState.cachePool.pool),n!==e&&(a.flags|=2048)),u!==l&&u&&(t.child.flags|=8192),sn(t,t.updateQueue),yl(t),null);case 4:return ol(),l===null&&Qc(t.stateNode.containerInfo),yl(t),null;case 10:return Yt(t.type),yl(t),null;case 19:if(E(Sl),a=t.memoizedState,a===null)return yl(t),null;if(e=(t.flags&128)!==0,n=a.rendering,n===null)if(e)le(a,!1);else{if(hl!==0||l!==null&&(l.flags&128)!==0)for(l=t.child;l!==null;){if(n=ke(l),n!==null){for(t.flags|=128,le(a,!1),l=n.updateQueue,t.updateQueue=l,sn(t,l),t.subtreeFlags=0,l=u,u=t.child;u!==null;)m0(u,l),u=u.sibling;return O(Sl,Sl.current&1|2),J&&qt(t,a.treeForkCount),t.child}l=l.sibling}a.tail!==null&&$l()>Sn&&(t.flags|=128,e=!0,le(a,!1),t.lanes=4194304)}else{if(!e)if(l=ke(n),l!==null){if(t.flags|=128,e=!0,l=l.updateQueue,t.updateQueue=l,sn(t,l),le(a,!0),a.tail===null&&a.tailMode==="hidden"&&!n.alternate&&!J)return yl(t),null}else 2*$l()-a.renderingStartTime>Sn&&u!==536870912&&(t.flags|=128,e=!0,le(a,!1),t.lanes=4194304);a.isBackwards?(n.sibling=t.child,t.child=n):(l=a.last,l!==null?l.sibling=n:t.child=n,a.last=n)}return a.tail!==null?(l=a.tail,a.rendering=l,a.tail=l.sibling,a.renderingStartTime=$l(),l.sibling=null,u=Sl.current,O(Sl,e?u&1|2:u&1),J&&qt(t,a.treeForkCount),l):(yl(t),null);case 22:case 23:return tt(t),jf(),a=t.memoizedState!==null,l!==null?l.memoizedState!==null!==a&&(t.flags|=8192):a&&(t.flags|=8192),a?(u&536870912)!==0&&(t.flags&128)===0&&(yl(t),t.subtreeFlags&6&&(t.flags|=8192)):yl(t),u=t.updateQueue,u!==null&&sn(t,u.retryQueue),u=null,l!==null&&l.memoizedState!==null&&l.memoizedState.cachePool!==null&&(u=l.memoizedState.cachePool.pool),a=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(a=t.memoizedState.cachePool.pool),a!==u&&(t.flags|=2048),l!==null&&E(Ru),null;case 24:return u=null,l!==null&&(u=l.memoizedState.cache),t.memoizedState.cache!==u&&(t.flags|=2048),Yt(bl),yl(t),null;case 25:return null;case 30:return null}throw Error(o(156,t.tag))}function Wm(l,t){switch(Of(t),t.tag){case 1:return l=t.flags,l&65536?(t.flags=l&-65537|128,t):null;case 3:return Yt(bl),ol(),l=t.flags,(l&65536)!==0&&(l&128)===0?(t.flags=l&-65537|128,t):null;case 26:case 27:case 5:return Ee(t),null;case 31:if(t.memoizedState!==null){if(tt(t),t.alternate===null)throw Error(o(340));pu()}return l=t.flags,l&65536?(t.flags=l&-65537|128,t):null;case 13:if(tt(t),l=t.memoizedState,l!==null&&l.dehydrated!==null){if(t.alternate===null)throw Error(o(340));pu()}return l=t.flags,l&65536?(t.flags=l&-65537|128,t):null;case 19:return E(Sl),null;case 4:return ol(),null;case 10:return Yt(t.type),null;case 22:case 23:return tt(t),jf(),l!==null&&E(Ru),l=t.flags,l&65536?(t.flags=l&-65537|128,t):null;case 24:return Yt(bl),null;case 25:return null;default:return null}}function Qy(l,t){switch(Of(t),t.tag){case 3:Yt(bl),ol();break;case 26:case 27:case 5:Ee(t);break;case 4:ol();break;case 31:t.memoizedState!==null&&tt(t);break;case 13:tt(t);break;case 19:E(Sl);break;case 10:Yt(t.type);break;case 22:case 23:tt(t),jf(),l!==null&&E(Ru);break;case 24:Yt(bl)}}function te(l,t){try{var u=t.updateQueue,a=u!==null?u.lastEffect:null;if(a!==null){var e=a.next;u=e;do{if((u.tag&l)===l){a=void 0;var n=u.create,f=u.inst;a=n(),f.destroy=a}u=u.next}while(u!==e)}}catch(c){tl(t,t.return,c)}}function cu(l,t,u){try{var a=t.updateQueue,e=a!==null?a.lastEffect:null;if(e!==null){var n=e.next;a=n;do{if((a.tag&l)===l){var f=a.inst,c=f.destroy;if(c!==void 0){f.destroy=void 0,e=t;var i=u,d=c;try{d()}catch(g){tl(e,i,g)}}}a=a.next}while(a!==n)}}catch(g){tl(t,t.return,g)}}function jy(l){var t=l.updateQueue;if(t!==null){var u=l.stateNode;try{H0(t,u)}catch(a){tl(l,l.return,a)}}}function Zy(l,t,u){u.props=Gu(l.type,l.memoizedProps),u.state=l.memoizedState;try{u.componentWillUnmount()}catch(a){tl(l,t,a)}}function ue(l,t){try{var u=l.ref;if(u!==null){switch(l.tag){case 26:case 27:case 5:var a=l.stateNode;break;case 30:a=l.stateNode;break;default:a=l.stateNode}typeof u=="function"?l.refCleanup=u(a):u.current=a}}catch(e){tl(l,t,e)}}function Ot(l,t){var u=l.ref,a=l.refCleanup;if(u!==null)if(typeof a=="function")try{a()}catch(e){tl(l,t,e)}finally{l.refCleanup=null,l=l.alternate,l!=null&&(l.refCleanup=null)}else if(typeof u=="function")try{u(null)}catch(e){tl(l,t,e)}else u.current=null}function Vy(l){var t=l.type,u=l.memoizedProps,a=l.stateNode;try{l:switch(t){case"button":case"input":case"select":case"textarea":u.autoFocus&&a.focus();break l;case"img":u.src?a.src=u.src:u.srcSet&&(a.srcset=u.srcSet)}}catch(e){tl(l,l.return,e)}}function bc(l,t,u){try{var a=l.stateNode;Sd(a,l.type,u,t),a[jl]=t}catch(e){tl(l,l.return,e)}}function xy(l){return l.tag===5||l.tag===3||l.tag===26||l.tag===27&&hu(l.type)||l.tag===4}function zc(l){l:for(;;){for(;l.sibling===null;){if(l.return===null||xy(l.return))return null;l=l.return}for(l.sibling.return=l.return,l=l.sibling;l.tag!==5&&l.tag!==6&&l.tag!==18;){if(l.tag===27&&hu(l.type)||l.flags&2||l.child===null||l.tag===4)continue l;l.child.return=l,l=l.child}if(!(l.flags&2))return l.stateNode}}function Tc(l,t,u){var a=l.tag;if(a===5||a===6)l=l.stateNode,t?(u.nodeType===9?u.body:u.nodeName==="HTML"?u.ownerDocument.body:u).insertBefore(l,t):(t=u.nodeType===9?u.body:u.nodeName==="HTML"?u.ownerDocument.body:u,t.appendChild(l),u=u._reactRootContainer,u!=null||t.onclick!==null||(t.onclick=Ht));else if(a!==4&&(a===27&&hu(l.type)&&(u=l.stateNode,t=null),l=l.child,l!==null))for(Tc(l,t,u),l=l.sibling;l!==null;)Tc(l,t,u),l=l.sibling}function mn(l,t,u){var a=l.tag;if(a===5||a===6)l=l.stateNode,t?u.insertBefore(l,t):u.appendChild(l);else if(a!==4&&(a===27&&hu(l.type)&&(u=l.stateNode),l=l.child,l!==null))for(mn(l,t,u),l=l.sibling;l!==null;)mn(l,t,u),l=l.sibling}function Ly(l){var t=l.stateNode,u=l.memoizedProps;try{for(var a=l.type,e=t.attributes;e.length;)t.removeAttributeNode(e[0]);Hl(t,a,u),t[Ml]=l,t[jl]=u}catch(n){tl(l,l.return,n)}}var jt=!1,El=!1,Ec=!1,Ky=typeof WeakSet=="function"?WeakSet:Set,_l=null;function $m(l,t){if(l=l.containerInfo,Vc=qn,l=a0(l),hf(l)){if("selectionStart"in l)var u={start:l.selectionStart,end:l.selectionEnd};else l:{u=(u=l.ownerDocument)&&u.defaultView||window;var a=u.getSelection&&u.getSelection();if(a&&a.rangeCount!==0){u=a.anchorNode;var e=a.anchorOffset,n=a.focusNode;a=a.focusOffset;try{u.nodeType,n.nodeType}catch{u=null;break l}var f=0,c=-1,i=-1,d=0,g=0,T=l,h=null;t:for(;;){for(var S;T!==u||e!==0&&T.nodeType!==3||(c=f+e),T!==n||a!==0&&T.nodeType!==3||(i=f+a),T.nodeType===3&&(f+=T.nodeValue.length),(S=T.firstChild)!==null;)h=T,T=S;for(;;){if(T===l)break t;if(h===u&&++d===e&&(c=f),h===n&&++g===a&&(i=f),(S=T.nextSibling)!==null)break;T=h,h=T.parentNode}T=S}u=c===-1||i===-1?null:{start:c,end:i}}else u=null}u=u||{start:0,end:0}}else u=null;for(xc={focusedElem:l,selectionRange:u},qn=!1,_l=t;_l!==null;)if(t=_l,l=t.child,(t.subtreeFlags&1028)!==0&&l!==null)l.return=t,_l=l;else for(;_l!==null;){switch(t=_l,n=t.alternate,l=t.flags,t.tag){case 0:if((l&4)!==0&&(l=t.updateQueue,l=l!==null?l.events:null,l!==null))for(u=0;u title"))),Hl(n,a,u),n[Ml]=l,rl(n),a=n;break l;case"link":var f=kv("link","href",e).get(a+(u.href||""));if(f){for(var c=0;cnl&&(f=nl,nl=N,N=f);var s=t0(c,N),y=t0(c,nl);if(s&&y&&(S.rangeCount!==1||S.anchorNode!==s.node||S.anchorOffset!==s.offset||S.focusNode!==y.node||S.focusOffset!==y.offset)){var m=T.createRange();m.setStart(s.node,s.offset),S.removeAllRanges(),N>nl?(S.addRange(m),S.extend(y.node,y.offset)):(m.setEnd(y.node,y.offset),S.addRange(m))}}}}for(T=[],S=c;S=S.parentNode;)S.nodeType===1&&T.push({element:S,left:S.scrollLeft,top:S.scrollTop});for(typeof c.focus=="function"&&c.focus(),c=0;cu?32:u,b.T=null,u=Uc,Uc=null;var n=su,f=Kt;if(Al=0,ga=su=null,Kt=0,(k&6)!==0)throw Error(o(331));var c=k;if(k|=4,uv(n.current),Py(n,n.current,f,u),k=c,ie(0,!1),Fl&&typeof Fl.onPostCommitFiberRoot=="function")try{Fl.onPostCommitFiberRoot(Ma,n)}catch{}return!0}finally{_.p=e,b.T=a,Tv(l,t)}}function Av(l,t,u){t=yt(u,t),t=cc(l.stateNode,t,2),l=eu(l,t,2),l!==null&&(Ua(l,2),Mt(l))}function tl(l,t,u){if(l.tag===3)Av(l,l,u);else for(;t!==null;){if(t.tag===3){Av(t,l,u);break}else if(t.tag===1){var a=t.stateNode;if(typeof t.type.getDerivedStateFromError=="function"||typeof a.componentDidCatch=="function"&&(vu===null||!vu.has(a))){l=yt(u,l),u=ry(2),a=eu(t,u,2),a!==null&&(_y(u,a,t,l),Ua(a,2),Mt(a));break}}t=t.return}}function Rc(l,t,u){var a=l.pingCache;if(a===null){a=l.pingCache=new Im;var e=new Set;a.set(t,e)}else e=a.get(t),e===void 0&&(e=new Set,a.set(t,e));e.has(u)||(_c=!0,e.add(u),l=ad.bind(null,l,t,u),t.then(l,l))}function ad(l,t,u){var a=l.pingCache;a!==null&&a.delete(t),l.pingedLanes|=l.suspendedLanes&u,l.warmLanes&=~u,cl===l&&(V&u)===u&&(hl===4||hl===3&&(V&62914560)===V&&300>$l()-on?(k&2)===0&&ba(l,0):Oc|=u,Sa===V&&(Sa=0)),Mt(l)}function rv(l,t){t===0&&(t=gi()),l=Du(l,t),l!==null&&(Ua(l,t),Mt(l))}function ed(l){var t=l.memoizedState,u=0;t!==null&&(u=t.retryLane),rv(l,u)}function nd(l,t){var u=0;switch(l.tag){case 31:case 13:var a=l.stateNode,e=l.memoizedState;e!==null&&(u=e.retryLane);break;case 19:a=l.stateNode;break;case 22:a=l.stateNode._retryCache;break;default:throw Error(o(314))}a!==null&&a.delete(t),rv(l,u)}function fd(l,t){return Ln(l,t)}var An=null,Ta=null,qc=!1,rn=!1,Cc=!1,du=0;function Mt(l){l!==Ta&&l.next===null&&(Ta===null?An=Ta=l:Ta=Ta.next=l),rn=!0,qc||(qc=!0,id())}function ie(l,t){if(!Cc&&rn){Cc=!0;do for(var u=!1,a=An;a!==null;){if(l!==0){var e=a.pendingLanes;if(e===0)var n=0;else{var f=a.suspendedLanes,c=a.pingedLanes;n=(1<<31-kl(42|l)+1)-1,n&=e&~(f&~c),n=n&201326741?n&201326741|1:n?n|2:0}n!==0&&(u=!0,Dv(a,n))}else n=V,n=Me(a,a===cl?n:0,a.cancelPendingCommit!==null||a.timeoutHandle!==-1),(n&3)===0||Da(a,n)||(u=!0,Dv(a,n));a=a.next}while(u);Cc=!1}}function cd(){_v()}function _v(){rn=qc=!1;var l=0;du!==0&&bd()&&(l=du);for(var t=$l(),u=null,a=An;a!==null;){var e=a.next,n=Ov(a,t);n===0?(a.next=null,u===null?An=e:u.next=e,e===null&&(Ta=u)):(u=a,(l!==0||(n&3)!==0)&&(rn=!0)),a=e}Al!==0&&Al!==5||ie(l),du!==0&&(du=0)}function Ov(l,t){for(var u=l.suspendedLanes,a=l.pingedLanes,e=l.expirationTimes,n=l.pendingLanes&-62914561;0c)break;var g=i.transferSize,T=i.initiatorType;g&&Yv(T)&&(i=i.responseEnd,f+=g*(i"u"?null:document;function wv(l,t,u){var a=Ea;if(a&&typeof t=="string"&&t){var e=ct(t);e='link[rel="'+l+'"][href="'+e+'"]',typeof u=="string"&&(e+='[crossorigin="'+u+'"]'),Jv.has(e)||(Jv.add(e),l={rel:l,crossOrigin:u,href:t},a.querySelector(e)===null&&(t=a.createElement("link"),Hl(t,"link",l),rl(t),a.head.appendChild(t)))}}function Dd(l){Jt.D(l),wv("dns-prefetch",l,null)}function Ud(l,t){Jt.C(l,t),wv("preconnect",l,t)}function pd(l,t,u){Jt.L(l,t,u);var a=Ea;if(a&&l&&t){var e='link[rel="preload"][as="'+ct(t)+'"]';t==="image"&&u&&u.imageSrcSet?(e+='[imagesrcset="'+ct(u.imageSrcSet)+'"]',typeof u.imageSizes=="string"&&(e+='[imagesizes="'+ct(u.imageSizes)+'"]')):e+='[href="'+ct(l)+'"]';var n=e;switch(t){case"style":n=Aa(l);break;case"script":n=ra(l)}ot.has(n)||(l=R({rel:"preload",href:t==="image"&&u&&u.imageSrcSet?void 0:l,as:t},u),ot.set(n,l),a.querySelector(e)!==null||t==="style"&&a.querySelector(me(n))||t==="script"&&a.querySelector(de(n))||(t=a.createElement("link"),Hl(t,"link",l),rl(t),a.head.appendChild(t)))}}function Hd(l,t){Jt.m(l,t);var u=Ea;if(u&&l){var a=t&&typeof t.as=="string"?t.as:"script",e='link[rel="modulepreload"][as="'+ct(a)+'"][href="'+ct(l)+'"]',n=e;switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":n=ra(l)}if(!ot.has(n)&&(l=R({rel:"modulepreload",href:l},t),ot.set(n,l),u.querySelector(e)===null)){switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(u.querySelector(de(n)))return}a=u.createElement("link"),Hl(a,"link",l),rl(a),u.head.appendChild(a)}}}function Nd(l,t,u){Jt.S(l,t,u);var a=Ea;if(a&&l){var e=Lu(a).hoistableStyles,n=Aa(l);t=t||"default";var f=e.get(n);if(!f){var c={loading:0,preload:null};if(f=a.querySelector(me(n)))c.loading=5;else{l=R({rel:"stylesheet",href:l,"data-precedence":t},u),(u=ot.get(n))&&Fc(l,u);var i=f=a.createElement("link");rl(i),Hl(i,"link",l),i._p=new Promise(function(d,g){i.onload=d,i.onerror=g}),i.addEventListener("load",function(){c.loading|=1}),i.addEventListener("error",function(){c.loading|=2}),c.loading|=4,Un(f,t,a)}f={type:"stylesheet",instance:f,count:1,state:c},e.set(n,f)}}}function Rd(l,t){Jt.X(l,t);var u=Ea;if(u&&l){var a=Lu(u).hoistableScripts,e=ra(l),n=a.get(e);n||(n=u.querySelector(de(e)),n||(l=R({src:l,async:!0},t),(t=ot.get(e))&&kc(l,t),n=u.createElement("script"),rl(n),Hl(n,"link",l),u.head.appendChild(n)),n={type:"script",instance:n,count:1,state:null},a.set(e,n))}}function qd(l,t){Jt.M(l,t);var u=Ea;if(u&&l){var a=Lu(u).hoistableScripts,e=ra(l),n=a.get(e);n||(n=u.querySelector(de(e)),n||(l=R({src:l,async:!0,type:"module"},t),(t=ot.get(e))&&kc(l,t),n=u.createElement("script"),rl(n),Hl(n,"link",l),u.head.appendChild(n)),n={type:"script",instance:n,count:1,state:null},a.set(e,n))}}function Wv(l,t,u,a){var e=(e=Q.current)?Dn(e):null;if(!e)throw Error(o(446));switch(l){case"meta":case"title":return null;case"style":return typeof u.precedence=="string"&&typeof u.href=="string"?(t=Aa(u.href),u=Lu(e).hoistableStyles,a=u.get(t),a||(a={type:"style",instance:null,count:0,state:null},u.set(t,a)),a):{type:"void",instance:null,count:0,state:null};case"link":if(u.rel==="stylesheet"&&typeof u.href=="string"&&typeof u.precedence=="string"){l=Aa(u.href);var n=Lu(e).hoistableStyles,f=n.get(l);if(f||(e=e.ownerDocument||e,f={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},n.set(l,f),(n=e.querySelector(me(l)))&&!n._p&&(f.instance=n,f.state.loading=5),ot.has(l)||(u={rel:"preload",as:"style",href:u.href,crossOrigin:u.crossOrigin,integrity:u.integrity,media:u.media,hrefLang:u.hrefLang,referrerPolicy:u.referrerPolicy},ot.set(l,u),n||Cd(e,l,u,f.state))),t&&a===null)throw Error(o(528,""));return f}if(t&&a!==null)throw Error(o(529,""));return null;case"script":return t=u.async,u=u.src,typeof u=="string"&&t&&typeof t!="function"&&typeof t!="symbol"?(t=ra(u),u=Lu(e).hoistableScripts,a=u.get(t),a||(a={type:"script",instance:null,count:0,state:null},u.set(t,a)),a):{type:"void",instance:null,count:0,state:null};default:throw Error(o(444,l))}}function Aa(l){return'href="'+ct(l)+'"'}function me(l){return'link[rel="stylesheet"]['+l+"]"}function $v(l){return R({},l,{"data-precedence":l.precedence,precedence:null})}function Cd(l,t,u,a){l.querySelector('link[rel="preload"][as="style"]['+t+"]")?a.loading=1:(t=l.createElement("link"),a.preload=t,t.addEventListener("load",function(){return a.loading|=1}),t.addEventListener("error",function(){return a.loading|=2}),Hl(t,"link",u),rl(t),l.head.appendChild(t))}function ra(l){return'[src="'+ct(l)+'"]'}function de(l){return"script[async]"+l}function Fv(l,t,u){if(t.count++,t.instance===null)switch(t.type){case"style":var a=l.querySelector('style[data-href~="'+ct(u.href)+'"]');if(a)return t.instance=a,rl(a),a;var e=R({},u,{"data-href":u.href,"data-precedence":u.precedence,href:null,precedence:null});return a=(l.ownerDocument||l).createElement("style"),rl(a),Hl(a,"style",e),Un(a,u.precedence,l),t.instance=a;case"stylesheet":e=Aa(u.href);var n=l.querySelector(me(e));if(n)return t.state.loading|=4,t.instance=n,rl(n),n;a=$v(u),(e=ot.get(e))&&Fc(a,e),n=(l.ownerDocument||l).createElement("link"),rl(n);var f=n;return f._p=new Promise(function(c,i){f.onload=c,f.onerror=i}),Hl(n,"link",a),t.state.loading|=4,Un(n,u.precedence,l),t.instance=n;case"script":return n=ra(u.src),(e=l.querySelector(de(n)))?(t.instance=e,rl(e),e):(a=u,(e=ot.get(n))&&(a=R({},u),kc(a,e)),l=l.ownerDocument||l,e=l.createElement("script"),rl(e),Hl(e,"link",a),l.head.appendChild(e),t.instance=e);case"void":return null;default:throw Error(o(443,t.type))}else t.type==="stylesheet"&&(t.state.loading&4)===0&&(a=t.instance,t.state.loading|=4,Un(a,u.precedence,l));return t.instance}function Un(l,t,u){for(var a=u.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),e=a.length?a[a.length-1]:null,n=e,f=0;f title"):null)}function Yd(l,t,u){if(u===1||t.itemProp!=null)return!1;switch(l){case"meta":case"title":return!0;case"style":if(typeof t.precedence!="string"||typeof t.href!="string"||t.href==="")break;return!0;case"link":if(typeof t.rel!="string"||typeof t.href!="string"||t.href===""||t.onLoad||t.onError)break;return t.rel==="stylesheet"?(l=t.disabled,typeof t.precedence=="string"&&l==null):!0;case"script":if(t.async&&typeof t.async!="function"&&typeof t.async!="symbol"&&!t.onLoad&&!t.onError&&t.src&&typeof t.src=="string")return!0}return!1}function Pv(l){return!(l.type==="stylesheet"&&(l.state.loading&3)===0)}function Bd(l,t,u,a){if(u.type==="stylesheet"&&(typeof a.media!="string"||matchMedia(a.media).matches!==!1)&&(u.state.loading&4)===0){if(u.instance===null){var e=Aa(a.href),n=t.querySelector(me(e));if(n){t=n._p,t!==null&&typeof t=="object"&&typeof t.then=="function"&&(l.count++,l=Hn.bind(l),t.then(l,l)),u.state.loading|=4,u.instance=n,rl(n);return}n=t.ownerDocument||t,a=$v(a),(e=ot.get(e))&&Fc(a,e),n=n.createElement("link"),rl(n);var f=n;f._p=new Promise(function(c,i){f.onload=c,f.onerror=i}),Hl(n,"link",a),u.instance=n}l.stylesheets===null&&(l.stylesheets=new Map),l.stylesheets.set(u,t),(t=u.state.preload)&&(u.state.loading&3)===0&&(l.count++,u=Hn.bind(l),t.addEventListener("load",u),t.addEventListener("error",u))}}var Ic=0;function Gd(l,t){return l.stylesheets&&l.count===0&&Rn(l,l.stylesheets),0Ic?50:800)+t);return l.unsuspend=u,function(){l.unsuspend=null,clearTimeout(a),clearTimeout(e)}}:null}function Hn(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Rn(this,this.stylesheets);else if(this.unsuspend){var l=this.unsuspend;this.unsuspend=null,l()}}}var Nn=null;function Rn(l,t){l.stylesheets=null,l.unsuspend!==null&&(l.count++,Nn=new Map,t.forEach(Xd,l),Nn=null,Hn.call(l))}function Xd(l,t){if(!(t.state.loading&4)){var u=Nn.get(l);if(u)var a=u.get(null);else{u=new Map,Nn.set(l,u);for(var e=l.querySelectorAll("link[data-precedence],style[data-precedence]"),n=0;n"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(r)}catch(P){console.error(P)}}return r(),fi.exports=Id(),fi.exports}var l1=Pd();const e1=rs(l1);var vi={exports:{}},ze={};var Es;function t1(){if(Es)return ze;Es=1;var r=Symbol.for("react.transitional.element"),P=Symbol.for("react.fragment");function w(o,X,L){var vl=null;if(L!==void 0&&(vl=""+L),X.key!==void 0&&(vl=""+X.key),"key"in X){L={};for(var Ol in X)Ol!=="key"&&(L[Ol]=X[Ol])}else L=X;return X=L.ref,{$$typeof:r,type:o,key:vl,ref:X!==void 0?X:null,props:L}}return ze.Fragment=P,ze.jsx=w,ze.jsxs=w,ze}var As;function u1(){return As||(As=1,vi.exports=t1()),vi.exports}var wt=u1();function n1({children:r,fallback:P}){const[w,o]=Te.useState(null),[X,L]=Te.useState(!0),[vl,Ol]=Te.useState(null);return Te.useEffect(()=>{if(window.__MCP_APP_DATA__){o(window.__MCP_APP_DATA__),L(!1);return}const p=W=>{W.data?.type==="mcp-app-init"&&(o(W.data.data),L(!1))};window.addEventListener("message",p);const A=setTimeout(()=>{w||(Ol("No data received from MCP server"),L(!1))},5e3);return()=>{window.removeEventListener("message",p),clearTimeout(A)}},[]),X?wt.jsxs("div",{className:"ghl-loading",children:[wt.jsx("div",{className:"ghl-spinner"}),wt.jsx("span",{style:{marginLeft:12},children:"Loading..."})]}):vl?wt.jsx("div",{className:"ghl-empty",children:wt.jsx("p",{children:vl})}):!w&&P?wt.jsx(wt.Fragment,{children:P}):wt.jsx(wt.Fragment,{children:r(w)})}function f1(r){if(!r)return"N/A";try{return new Date(r).toLocaleDateString("en-US",{year:"numeric",month:"short",day:"numeric"})}catch{return r}}function c1(r,P="USD"){return r==null?"$0.00":new Intl.NumberFormat("en-US",{style:"currency",currency:P}).format(r/100)}function i1(r,P){const w=r?.charAt(0)?.toUpperCase()||"",o=P?.charAt(0)?.toUpperCase()||"";return w+o||"?"}function y1(r){return{paid:"success",sent:"primary",viewed:"info",draft:"default",overdue:"danger",cancelled:"danger",won:"success",lost:"danger",open:"primary",abandoned:"warning",active:"success",inactive:"default",pending:"warning"}[r?.toLowerCase()]||"default"}export{n1 as A,e1 as R,a1 as a,c1 as b,y1 as c,f1 as f,i1 as g,wt as j,Te as r}; diff --git a/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/calendar-widget.html b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/calendar-widget.html new file mode 100644 index 0000000..05c294d --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/calendar-widget.html @@ -0,0 +1,14 @@ + + + + + + Calendar Widget - GHL MCP + + + + + +
+ + diff --git a/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/contact-card.html b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/contact-card.html new file mode 100644 index 0000000..c5d8a81 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/contact-card.html @@ -0,0 +1,14 @@ + + + + + + Contact Card - GHL MCP + + + + + +
+ + diff --git a/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/contact-grid.html b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/contact-grid.html new file mode 100644 index 0000000..3106851 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/contact-grid.html @@ -0,0 +1,14 @@ + + + + + + Contact Grid - GHL MCP + + + + + +
+ + diff --git a/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/conversation-thread.html b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/conversation-thread.html new file mode 100644 index 0000000..2097a39 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/conversation-thread.html @@ -0,0 +1,14 @@ + + + + + + Conversation Thread - GHL MCP + + + + + +
+ + diff --git a/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/invoice-preview.html b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/invoice-preview.html new file mode 100644 index 0000000..77e5c3d --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/invoice-preview.html @@ -0,0 +1,14 @@ + + + + + + Invoice Preview - GHL MCP + + + + + +
+ + diff --git a/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/opportunity-kanban.html b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/opportunity-kanban.html new file mode 100644 index 0000000..8eb97d6 --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/src/ui/dist/opportunity-kanban.html @@ -0,0 +1,14 @@ + + + + + + Pipeline Kanban - GHL MCP + + + + + +
+ + diff --git a/mcp-diagrams/ghl-mcp-apps-only/tsconfig.json b/mcp-diagrams/ghl-mcp-apps-only/tsconfig.json new file mode 100644 index 0000000..944355d --- /dev/null +++ b/mcp-diagrams/ghl-mcp-apps-only/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "outDir": "./dist", + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "tests/**/*"] +} \ No newline at end of file diff --git a/memory/2026-01-30.md b/memory/2026-01-30.md new file mode 100644 index 0000000..e04768c --- /dev/null +++ b/memory/2026-01-30.md @@ -0,0 +1,33 @@ +# Memory Log - 2026-01-30 + +## Work Completed + +### MCP Animation Framework (Remotion) +- **Status:** Dolly camera version completed using canvas viewport technique +- **Technical approach:** Camera zooms into typing area, follows text as typed, zooms out when done +- **Features:** Category-specific questions per software type for MCP marketing bulk generation +- **Current state:** Waiting for Jake's feedback on camera movement to iterate + +### Recently Active Projects +- **fortura-assets:** 60 native components in component library +- **memory system:** Ongoing improvements to daily memory/logging system + +## Decisions Made +- Using canvas viewport technique for dolly camera effect in Remotion (cleaner than direct camera transforms) +- Category-specific questions strategy for different software types in MCP marketing videos + +## Next Steps +- Awaiting feedback on MCP Animation Framework dolly camera movement +- Based on feedback, may need to iterate on camera smoothness, timing, or framing +- Once approved, move forward with bulk animation generation pipeline + +## Notable Context for Future Me +- MCP Animation Framework is a bulk animation generator for MCP marketing +- The core challenge is making typing animations feel dynamic and engaging +- Dolly camera approach adds visual interest without being distracting +- Different software categories need tailored questions to make videos relevant + +## System Updates +- Daily memory checkpoint system is working (cron job triggered successfully) +- HEARTBEAT.md being updated with current task state +- Git backup routine in place diff --git a/memory/burton-method-research-intel.md b/memory/burton-method-research-intel.md index 8da7a1e..6810c77 100644 --- a/memory/burton-method-research-intel.md +++ b/memory/burton-method-research-intel.md @@ -1,111 +1,53 @@ -# Burton Method Research Intel +# Burton Method Competitor Research Intel -> **How this works:** Current week's in-depth intel lives at the top. Each week, I compress the previous week into 1-3 sentences and move it to the archive at the bottom. Reference this file when asked about competitor moves, EdTech trends, or strategic action items. +## Week of January 26 - February 1, 2026 + +### 7Sage +- **New Promo:** $10,000 giveaway contest for students who track applications via "My Schools" feature (deadline Jan 30, 2026) +- **Site Update:** New mobile-friendly design; app in development but no release date announced +- **Pricing:** Core ($69/mo), Live ($129/mo), Coach ($299/mo) - all require $120/yr LSAC LawHub Advantage +- **Key Differentiator:** "Insanely granular" test analytics, 924 video lessons, 7,500+ Reddit upvotes mentioned + +### LSAT Demon +- **Pricing:** $95/month (lowest among major competitors) +- **Live Classes:** Daily Zoom-based classes covering all LSAT sections +- **Content:** 10,000+ explanations, official LSAT drilling, "Ask" feature with 24-hour response time +- **Founders:** Ben Olson & Nathan Fox (Thinking LSAT Podcast) + +### Blueprint LSAT +- **Focus:** Heavy emphasis on 1:1 private tutoring +- **Social Proof:** Numerous 5-star reviews citing 9+ point score improvements +- **Notable:** Strong instructor personalization mentioned in reviews (Bobby, Dylan, Hannah, Larissa) + +### PowerScore +- **Dave Killoran departed** (HUGE personnel change) - Jon Denning continuing solo +- **MAJOR UPDATE:** New 2025-2026 products include "The Law School Admissions Bible" written by Spivey Consulting +- **Strategic Pivot:** Moving beyond pure LSAT prep into law school admissions consulting space (reinforced Jan 30) +- **Live Classes:** Extensive daily schedule with topic-specific sessions (RC, LR, LG question types) +- **Strategic read:** Industry veteran leaving created uncertainty, but new Spivey partnership signals aggressive admissions push. Burton's visual/multimodal approach could differentiate in market where LSAT alone matters less. + +### Magoosh LSAT +- **Content:** Active blog with study resources, percentile calculators +- **Less Visible:** Homepage minimal; main value appears to be blog content + +### LSAC Official +- **January 2026 LSAT:** Score release 1/28/2026 +- **Testing Disruption:** Mainland China testing unavailable for January 2026 LSAT +- **International:** International administration available, but China exception notable + +### Industry Trends (EdCircuit, Buffalo Law) +- **Rising Competition:** Law school applications and LSAT scores both increasing for 2025-2026 cycle +- **Diminishing Differentiation:** High LSAT scores becoming less differentiating in applicant pools (reinforced Jan 30) +- **Access Issue:** Financial backing and unpaid prep time creating advantages for wealthy students +- **Jan 30 Update:** Applications + LSAT scores both rising → high scores becoming less differentiating. Need efficiency and unique methodology to stand out. --- -## 📊 Current Week Intel (Week of Jan 27 - Feb 2, 2026) +### Blogwatcher Status +- **13 feeds tracked**, but most competitor blogs lack working RSS/scrapers +- **No new articles this week** (as of Jan 30) +- **Action needed:** Manually configure or find alternative data sources for key competitor blogs -### 🚨 CRITICAL: LSAC Format Change -**Reading Comp removed comparative passages** in January 2026 administration. Confirmed by Blueprint and PowerScore. -- **Impact:** Any RC curriculum teaching comparative passage strategy is now outdated -- **Opportunity:** First to fully adapt = trust signal to students - ---- - -### Competitor Movements - -**7Sage** -- Full site redesign launched (better analytics, cleaner UI) -- **NEW FREE FEATURE:** Application tracker showing interview/accept/reject/waitlist outcomes -- $10,000 giveaway promotion tied to tracker -- Heavy ABA 509 report coverage -- ADHD accommodations content series + 1L survival guides -- **Pricing (Jan 27):** Core $69/mo | Live $129/mo | Coach $299/mo (all require LawHub $120/yr) -- **Scale:** 60+ live classes/week, 3,000+ recorded classes -- **Strategic read:** Pushing hard into admissions territory, not just LSAT. Creates stickiness + data network effects. - -**LSAT Demon** -- **"Ugly Mode"** (Jan 19) — transforms interface to match exact official LSAT layout -- Tuition Roll Call on scholarship estimator — visualizes what students actually paid -- Veteran outreach program with dedicated liaison -- **Pricing (Jan 27):** Entry at $95/month — competitive with 7Sage Core -- **Strategic read:** Daily podcast creates parasocial relationships. Demon is personality-driven; Burton is methodology-driven. Different lanes. - -**PowerScore** -- **Dave Killoran departed** (HUGE personnel change) -- Jon Denning continuing solo, covering January LSAT chaos extensively -- Crystal Ball webinars still running -- **Strategic read:** Industry veteran leaving creates uncertainty. Watch for quality/content changes. - -**Blueprint** -- First to report RC comparative passages removal -- Non-traditional student content (LSAT at 30/40/50+) -- Score plateau breakthrough guides -- 2025-26 admissions cycle predictions -- **Jan 27 Update:** Heavy push on 1:1 tutoring testimonials; "170+ course" positioning; reviews emphasizing "plateau breakthroughs" and test anxiety management -- **Strategic read:** Solid content machine, "fun" brand positioning. Going premium/high-touch with tutoring. - -**Kaplan** -- $200 off all LSAT prep **extended through Jan 26** (expires TODAY) -- Applies to On Demand, Live Online, In Person, and Standard Tutoring -- Bar prep also discounted ($750 off through Feb 27) -- New 2026 edition book with "99th percentile instructor videos" -- **Strategic read:** Mass-market, price-conscious positioning continues. Heavy discounting signals competitive pressure. - -**Magoosh** -- Updated for post-Logic Games LSAT -- Budget positioning continues -- LSAC remote proctoring option coverage -- **Jan 28 Update:** Landing page now shows blog-only content — no active LSAT prep product visible. Appears to have scaled back or exited market. Potential market consolidation signal. - -**LSAC (Official)** -- February 2026 scheduling opened Jan 20 -- January registration closed; score release Jan 28 -- Mainland China testing unavailable for Jan 2026 -- Reminder to disable grammar-checking programs for Argumentative Writing - ---- - -### EdTech Trends (Jan 27 Scan) - -| Story | Score | Key Insight | -|-------|-------|-------------| -| **AdeptLR: LSAT AI Competitor** | 9/10 | Direct LSAT LR adaptive AI platform; validates market, narrow focus (LR only) | -| **Blueprint: AI Could Mess Up LSAT Prep** | 8/10 | GPT-4 scored 163; AI gives confident wrong answers; training data outdated | -| **UB Law: Apps Up 22%** | 7/10 | Highest applicant volume in decade; 160-180 scores climbing; career changers entering | -| **TCS: EdTech Trends 2026** | 7/10 | AI adaptive models proven; "agentic AI" emerging; outcomes > features now | -| **LSAC: 2026 Cycle Strong** | 7/10 | Official confirmation LSAT volumes high; broad applicant base | -| **eSchool: 49 EdTech Predictions** | 6/10 | "Shiny AI era over" — measurable outcomes required to win | - -### Previous Scan (Jan 25) - -| Story | Score | Key Insight | -|-------|-------|-------------| -| AI Can Deepen Learning | 8/10 | AI mistakes spark deeper learning; productive friction > shortcuts | -| Beyond Memorization: Redefining Rigor | 8/10 | LSAT-relevant: adaptability + critical thinking > memorization | -| Teaching Machines to Spot Human Errors | 7/10 | Eedi Labs predicting student misconceptions; human-in-the-loop AI tutoring | -| Learning As Addictive As TikTok? 7/10 | Dopamine science for engagement; make progress feel attainable | -| What Students Want From Edtech | 6/10 | UX research: clarity > gimmicks; meaningful gamification only | - ---- - -### 📌 Identified Action Items - -0. **🚨 TIME-SENSITIVE (Jan 28):** January LSAT scores release TODAY — prime acquisition window for students who missed target. Consider outreach campaign for 1/28-1/31. -1. **URGENT:** Update RC content to remove/deprioritize comparative passage strategy -2. **Content opportunity:** Blog post "What the RC Changes Mean for Your Score" — be fast, be definitive -3. **Positioning clarity:** 7Sage → admissions features, Demon → personality, Burton → systematic methodology that transcends format changes -4. **Product opportunity:** Consider "productive friction" AI features that make students think, not just answer -5. **Watch:** PowerScore post-Killoran quality — potential talent acquisition or market share opportunity -6. **NEW - Competitive research:** Study AdeptLR UX for adaptive LSAT AI patterns (what works, what doesn't) -7. **NEW - Differentiation angle:** Build AI that admits uncertainty (address Blueprint's "confident wrong answers" critique) -8. **NEW - Marketing:** Track & publish score improvement data to meet "outcomes over features" bar -9. **NEW - Market validation:** 22% app surge + decade-high volumes = sustained demand; career changers = self-study friendly audience - ---- - -## 📚 Previous Weeks Archive - -*(No previous weeks yet — this section will grow as weeks pass)* +## Previous Weeks +*No prior weeks tracked - file created 2026-01-30* diff --git a/pickle_history.txt b/pickle_history.txt index 9e5074a..82e25d9 100644 --- a/pickle_history.txt +++ b/pickle_history.txt @@ -10,3 +10,5 @@ 2026-01-26: Make today count! Pickle moment: Why are pickles such good friends? They're always there when you're in a jam...or jar. 2026-01-27: Your time is now! Pickles are wild: What's a pickle's favorite day of the week? Fri-dill of course. 2026-01-28: You're unstoppable! Quick pickle story: Why are pickles so resilient? They've been through a lot - literally submerged and came out crunchier. +2026-01-29: Dream big, work hard! Pickle knowledge drop: What do you call a pickle that's really stressed? A dill-lemma. +2026-01-30: Consistency is key! Never forget about pickles: Why did the pickle go to therapy? It had some unresolved jar issues.