Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f84a6738a4 |
@ -1,3 +1,2 @@
|
||||
HELPSCOUT_APP_ID=your_app_id_here
|
||||
HELPSCOUT_APP_SECRET=your_app_secret_here
|
||||
HELPSCOUT_ACCESS_TOKEN=your_oauth2_access_token_here
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,4 +2,3 @@ node_modules/
|
||||
dist/
|
||||
.env
|
||||
*.log
|
||||
.DS_Store
|
||||
|
||||
7
Dockerfile
Normal file
7
Dockerfile
Normal file
@ -0,0 +1,7 @@
|
||||
FROM node:18-slim
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci --production
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
CMD ["node", "dist/index.js"]
|
||||
226
README.md
226
README.md
@ -1,110 +1,42 @@
|
||||
# HelpScout MCP Server
|
||||
|
||||
A comprehensive Model Context Protocol (MCP) server for HelpScout's Mailbox API v2. This server provides 47+ tools and 18 interactive MCP apps for complete customer support management.
|
||||
A Model Context Protocol (MCP) server for HelpScout — the customer support platform.
|
||||
|
||||
## Features
|
||||
|
||||
### 🛠️ Tools (47)
|
||||
- **Mailbox Management** — List, create, and manage mailboxes
|
||||
- **Conversation Tools** — Search, read, reply to conversations
|
||||
- **Customer Management** — CRUD operations on customers
|
||||
- **Workflow Automation** — Manage workflows and automations
|
||||
- **Reporting** — Access HelpScout reports and analytics
|
||||
|
||||
#### Conversations (12 tools)
|
||||
- List, get, create, update, delete conversations
|
||||
- Manage threads (list, create reply, create note, create phone)
|
||||
- Update tags, change status, assign conversations
|
||||
|
||||
#### Customers (12 tools)
|
||||
- List, get, create, update, delete customers
|
||||
- Manage customer emails, phones, addresses
|
||||
- List customer properties
|
||||
|
||||
#### Mailboxes (5 tools)
|
||||
- List and get mailboxes
|
||||
- List folders and custom fields
|
||||
|
||||
#### Users (3 tools)
|
||||
- List and get users
|
||||
- Get current authenticated user
|
||||
|
||||
#### Tags (4 tools)
|
||||
- List, create, update, delete tags
|
||||
|
||||
#### Workflows (5 tools)
|
||||
- List, get workflows
|
||||
- Activate/deactivate workflows
|
||||
- Get workflow statistics
|
||||
|
||||
#### Saved Replies (5 tools)
|
||||
- List, get, create, update, delete saved replies
|
||||
|
||||
#### Teams (3 tools)
|
||||
- List, get teams
|
||||
- List team members
|
||||
|
||||
#### Webhooks (5 tools)
|
||||
- List, get, create, update, delete webhooks
|
||||
|
||||
#### Reporting (5 tools)
|
||||
- Company overview report
|
||||
- Conversations report
|
||||
- Happiness report
|
||||
- Productivity report
|
||||
- User-specific reports
|
||||
|
||||
### 📱 MCP Apps (18)
|
||||
|
||||
1. **conversation-dashboard** - Dashboard view of conversations with filters
|
||||
2. **conversation-detail** - Detailed single conversation view
|
||||
3. **conversation-grid** - Card-based grid layout
|
||||
4. **conversation-timeline** - Visual timeline of conversation history
|
||||
5. **customer-grid** - Grid view of all customers
|
||||
6. **customer-detail** - Detailed customer profile
|
||||
7. **mailbox-overview** - Overview of all mailboxes
|
||||
8. **folder-browser** - Browse and manage folders
|
||||
9. **user-stats** - Individual user performance metrics
|
||||
10. **tag-manager** - Manage tags with usage stats
|
||||
11. **workflow-dashboard** - Workflow management dashboard
|
||||
12. **workflow-detail** - Detailed workflow configuration
|
||||
13. **saved-replies** - Browse and manage saved replies
|
||||
14. **team-overview** - Team structure and member stats
|
||||
15. **happiness-report** - Customer satisfaction metrics
|
||||
16. **productivity-report** - Team productivity metrics
|
||||
17. **company-report** - Overall company performance KPIs
|
||||
18. **search-results** - Search interface for conversations
|
||||
|
||||
## Installation
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
git clone https://github.com/BusyBee3333/helpscout-mcp-2026-complete.git
|
||||
cd helpscout-mcp-2026-complete
|
||||
npm install
|
||||
cp .env.example .env
|
||||
# Add your HelpScout API credentials to .env
|
||||
npm run build
|
||||
npm start
|
||||
```
|
||||
|
||||
## Configuration
|
||||
## Environment Variables
|
||||
|
||||
Create a `.env` file with your HelpScout credentials:
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `HELPSCOUT_APP_ID` | Your HelpScout OAuth2 App ID |
|
||||
| `HELPSCOUT_APP_SECRET` | Your HelpScout OAuth2 App Secret |
|
||||
|
||||
```env
|
||||
HELPSCOUT_APP_ID=your_app_id
|
||||
HELPSCOUT_APP_SECRET=your_app_secret
|
||||
```
|
||||
|
||||
### Getting HelpScout Credentials
|
||||
|
||||
1. Go to https://secure.helpscout.net/apps/
|
||||
2. Create a new app
|
||||
3. Copy the App ID and App Secret
|
||||
4. Add them to your `.env` file
|
||||
|
||||
## Usage
|
||||
|
||||
### With MCP Client
|
||||
|
||||
Add to your MCP client configuration:
|
||||
## Claude Desktop Config
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"helpscout": {
|
||||
"command": "node",
|
||||
"args": ["/path/to/helpscout/dist/main.js"],
|
||||
"args": ["dist/index.js"],
|
||||
"env": {
|
||||
"HELPSCOUT_APP_ID": "your_app_id",
|
||||
"HELPSCOUT_APP_SECRET": "your_app_secret"
|
||||
@ -114,126 +46,6 @@ Add to your MCP client configuration:
|
||||
}
|
||||
```
|
||||
|
||||
### Standalone
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
## API Coverage
|
||||
|
||||
This server implements the HelpScout Mailbox API v2:
|
||||
- **Base URL**: https://api.helpscout.net/v2/
|
||||
- **Authentication**: OAuth2 Client Credentials
|
||||
- **Features**: Pagination, error handling, automatic token refresh
|
||||
|
||||
## Tool Examples
|
||||
|
||||
### List Active Conversations
|
||||
```javascript
|
||||
{
|
||||
"name": "helpscout_list_conversations",
|
||||
"arguments": {
|
||||
"status": "active",
|
||||
"mailbox": 123456
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Create a Conversation
|
||||
```javascript
|
||||
{
|
||||
"name": "helpscout_create_conversation",
|
||||
"arguments": {
|
||||
"subject": "Need help with billing",
|
||||
"type": "email",
|
||||
"mailboxId": 123456,
|
||||
"customerEmail": "customer@example.com",
|
||||
"status": "active"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Get Happiness Report
|
||||
```javascript
|
||||
{
|
||||
"name": "helpscout_get_happiness_report",
|
||||
"arguments": {
|
||||
"start": "2025-01-01",
|
||||
"end": "2025-01-31"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## MCP Apps Usage
|
||||
|
||||
Access MCP apps as resources:
|
||||
|
||||
```
|
||||
helpscout://app/conversation-dashboard
|
||||
helpscout://app/customer-detail
|
||||
helpscout://app/happiness-report
|
||||
```
|
||||
|
||||
Each app provides a rich HTML interface for interacting with HelpScout data.
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Build
|
||||
npm run build
|
||||
|
||||
# Watch mode
|
||||
npm run dev
|
||||
|
||||
# Prepare for publishing
|
||||
npm run prepare
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
src/
|
||||
├── api/
|
||||
│ └── client.ts # HelpScout API client with OAuth2
|
||||
├── tools/
|
||||
│ ├── conversations-tools.ts
|
||||
│ ├── customers-tools.ts
|
||||
│ ├── mailboxes-tools.ts
|
||||
│ ├── users-tools.ts
|
||||
│ ├── tags-tools.ts
|
||||
│ ├── workflows-tools.ts
|
||||
│ ├── saved-replies-tools.ts
|
||||
│ ├── teams-tools.ts
|
||||
│ ├── webhooks-tools.ts
|
||||
│ └── reporting-tools.ts
|
||||
├── apps/
|
||||
│ ├── conversation-dashboard.ts
|
||||
│ ├── conversation-detail.ts
|
||||
│ └── ... (18 apps total)
|
||||
├── types/
|
||||
│ └── index.ts # TypeScript type definitions
|
||||
├── server.ts # MCP server implementation
|
||||
└── main.ts # Entry point
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The server includes comprehensive error handling:
|
||||
- API errors are caught and returned with detailed messages
|
||||
- Token refresh is automatic
|
||||
- Pagination handles large datasets gracefully
|
||||
|
||||
## Contributing
|
||||
|
||||
This is part of the MCPEngine project. For contributions, please refer to the main repository guidelines.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Links
|
||||
|
||||
- [HelpScout API Documentation](https://developer.helpscout.com/)
|
||||
- [MCP Documentation](https://modelcontextprotocol.io/)
|
||||
- [MCPEngine](https://github.com/BusyBee3333/mcpengine)
|
||||
|
||||
637
index.html
Normal file
637
index.html
Normal file
@ -0,0 +1,637 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="scroll-smooth">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Help Scout Connect — AI-Power Your Support in 2 Clicks</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
brand: {
|
||||
50: '#eff6ff',
|
||||
100: '#dbeafe',
|
||||
200: '#bfdbfe',
|
||||
300: '#93c5fd',
|
||||
400: '#60a5fa',
|
||||
500: '#1292EE',
|
||||
600: '#0f7cd6',
|
||||
700: '#0c66be',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.gradient-text {
|
||||
background: linear-gradient(135deg, #1292EE 0%, #60a5fa 50%, #a78bfa 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
.hero-glow {
|
||||
background: radial-gradient(ellipse 80% 50% at 50% -20%, rgba(18, 146, 238, 0.2), transparent);
|
||||
}
|
||||
.card-glow {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.card-glow:hover {
|
||||
box-shadow: 0 0 50px rgba(18, 146, 238, 0.2);
|
||||
transform: translateY(-4px);
|
||||
border-color: rgba(18, 146, 238, 0.4);
|
||||
}
|
||||
.animate-float {
|
||||
animation: float 6s ease-in-out infinite;
|
||||
}
|
||||
.animate-float-delayed {
|
||||
animation: float 6s ease-in-out infinite;
|
||||
animation-delay: -3s;
|
||||
}
|
||||
.animate-pulse-glow {
|
||||
animation: pulseGlow 3s ease-in-out infinite;
|
||||
}
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0px); }
|
||||
50% { transform: translateY(-20px); }
|
||||
}
|
||||
@keyframes pulseGlow {
|
||||
0%, 100% { box-shadow: 0 0 20px rgba(18, 146, 238, 0.3); }
|
||||
50% { box-shadow: 0 0 40px rgba(18, 146, 238, 0.5); }
|
||||
}
|
||||
@keyframes wiggle {
|
||||
0%, 100% { transform: rotate(-2deg); }
|
||||
50% { transform: rotate(2deg); }
|
||||
}
|
||||
@keyframes glow-pulse {
|
||||
0%, 100% { box-shadow: 0 0 20px 0 rgba(18, 146, 238, 0.4), 0 0 40px 0 rgba(18, 146, 238, 0.2); }
|
||||
50% { box-shadow: 0 0 30px 5px rgba(18, 146, 238, 0.6), 0 0 60px 10px rgba(18, 146, 238, 0.3); }
|
||||
}
|
||||
.sticky-btn {
|
||||
animation: wiggle 2.5s ease-in-out infinite, glow-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
.sticky-btn:hover {
|
||||
animation: none;
|
||||
}
|
||||
.video-glow {
|
||||
box-shadow: 0 0 80px rgba(18, 146, 238, 0.3), 0 25px 80px -12px rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
.gradient-border {
|
||||
background: linear-gradient(135deg, rgba(18, 146, 238, 0.5), rgba(167, 139, 250, 0.2));
|
||||
padding: 1px;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
.gradient-border-inner {
|
||||
background: rgb(24 24 27);
|
||||
border-radius: calc(1rem - 1px);
|
||||
}
|
||||
.feature-icon {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.card-glow:hover .feature-icon {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
@keyframes typing {
|
||||
from { width: 0 }
|
||||
to { width: 100% }
|
||||
}
|
||||
.typing-effect {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
animation: typing 2s steps(30, end);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-zinc-950 text-zinc-100 font-sans antialiased">
|
||||
|
||||
<!-- Nav -->
|
||||
<nav class="fixed top-0 w-full z-50 border-b border-zinc-800/50 bg-zinc-950/80 backdrop-blur-xl">
|
||||
<div class="max-w-6xl mx-auto px-6 py-4 flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-8 h-8 rounded-lg bg-gradient-to-br from-brand-500 to-blue-400 flex items-center justify-center">
|
||||
<i data-lucide="life-buoy" class="w-5 h-5 text-white"></i>
|
||||
</div>
|
||||
<span class="font-bold text-xl">Help Scout Connect</span>
|
||||
</div>
|
||||
<div class="hidden md:flex items-center gap-8">
|
||||
<a href="#features" class="text-zinc-400 hover:text-white transition">Features</a>
|
||||
<a href="#pricing" class="text-zinc-400 hover:text-white transition">Waitlist</a>
|
||||
<a href="#faq" class="text-zinc-400 hover:text-white transition">FAQ</a>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<a href="#" class="text-zinc-400 hover:text-white transition hidden sm:block">Sign In</a>
|
||||
<a href="#pricing" class="px-4 py-2 bg-brand-500 hover:bg-brand-600 rounded-lg font-medium transition transform hover:scale-105">
|
||||
Join Waitlist
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="relative min-h-screen flex items-center hero-glow pt-20 overflow-hidden">
|
||||
<!-- Floating background elements -->
|
||||
<div class="absolute top-40 left-10 w-72 h-72 bg-brand-500/10 rounded-full blur-3xl animate-float"></div>
|
||||
<div class="absolute bottom-20 right-10 w-96 h-96 bg-purple-500/10 rounded-full blur-3xl animate-float-delayed"></div>
|
||||
|
||||
<div class="max-w-6xl mx-auto px-6 py-20">
|
||||
<div class="text-center relative z-10">
|
||||
<div class="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-zinc-800/50 border border-zinc-700/50 text-sm text-zinc-300 mb-8 backdrop-blur-sm">
|
||||
<span class="w-2 h-2 rounded-full bg-blue-400 animate-pulse"></span>
|
||||
Open Source + Hosted
|
||||
</div>
|
||||
|
||||
<h1 class="text-5xl md:text-6xl lg:text-7xl font-extrabold tracking-tight mb-6 leading-[1.1]">
|
||||
Connect <span class="gradient-text">Help Scout</span><br>
|
||||
to AI in 2 Clicks
|
||||
</h1>
|
||||
|
||||
<p class="text-xl md:text-2xl text-zinc-400 max-w-2xl mx-auto mb-10">
|
||||
The complete Help Scout MCP server. <strong class="text-white">54 tools</strong> for conversations, docs, and workflows.
|
||||
No setup. No OAuth headaches. Just connect and support.
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-4 justify-center mb-12">
|
||||
<a href="#pricing" class="px-8 py-4 bg-brand-500 hover:bg-brand-600 rounded-xl font-semibold text-lg transition transform hover:scale-105 text-center shadow-lg shadow-brand-500/25">
|
||||
Join the Waitlist
|
||||
</a>
|
||||
<a href="#demo" class="px-8 py-4 bg-zinc-800/80 hover:bg-zinc-700 border border-zinc-700 rounded-xl font-semibold text-lg transition flex items-center justify-center gap-2">
|
||||
<i data-lucide="play" class="w-5 h-5"></i>
|
||||
Watch Demo
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Social Proof -->
|
||||
<div class="flex items-center gap-4 justify-center">
|
||||
<div class="flex -space-x-3">
|
||||
<img src="https://i.pravatar.cc/100?img=21" class="w-10 h-10 rounded-full border-2 border-zinc-950" alt="">
|
||||
<img src="https://i.pravatar.cc/100?img=22" class="w-10 h-10 rounded-full border-2 border-zinc-950" alt="">
|
||||
<img src="https://i.pravatar.cc/100?img=23" class="w-10 h-10 rounded-full border-2 border-zinc-950" alt="">
|
||||
<img src="https://i.pravatar.cc/100?img=24" class="w-10 h-10 rounded-full border-2 border-zinc-950" alt="">
|
||||
<div class="w-10 h-10 rounded-full border-2 border-zinc-950 bg-brand-500 flex items-center justify-center text-xs font-bold">+50</div>
|
||||
</div>
|
||||
<p class="text-zinc-400">
|
||||
Trusted by <strong class="text-white">200+</strong> support teams
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Video Demo -->
|
||||
<section id="demo" class="py-20 border-t border-zinc-800/50">
|
||||
<div class="max-w-5xl mx-auto px-6">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-3xl md:text-4xl font-bold mb-4">See It In Action</h2>
|
||||
<p class="text-xl text-zinc-400">Watch how AI transforms your support workflow</p>
|
||||
</div>
|
||||
<div class="gradient-border">
|
||||
<div class="gradient-border-inner p-2">
|
||||
<div class="rounded-xl overflow-hidden video-glow">
|
||||
<video autoplay loop muted playsinline class="w-full">
|
||||
<source src="output/helpscout.mp4" type="video/mp4">
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center mt-8">
|
||||
<div class="inline-flex items-center gap-6 px-6 py-3 rounded-full bg-zinc-900/80 border border-zinc-800 backdrop-blur-sm">
|
||||
<div class="flex items-center gap-2">
|
||||
<i data-lucide="message-circle" class="w-4 h-4 text-brand-500"></i>
|
||||
<span class="text-sm text-zinc-300">Conversations</span>
|
||||
</div>
|
||||
<div class="w-px h-4 bg-zinc-700"></div>
|
||||
<div class="flex items-center gap-2">
|
||||
<i data-lucide="book-open" class="w-4 h-4 text-brand-500"></i>
|
||||
<span class="text-sm text-zinc-300">Docs</span>
|
||||
</div>
|
||||
<div class="w-px h-4 bg-zinc-700"></div>
|
||||
<div class="flex items-center gap-2">
|
||||
<i data-lucide="git-merge" class="w-4 h-4 text-brand-500"></i>
|
||||
<span class="text-sm text-zinc-300">Workflows</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Problem/Solution -->
|
||||
<section class="py-24 border-t border-zinc-800/50">
|
||||
<div class="max-w-6xl mx-auto px-6">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-3xl md:text-5xl font-bold mb-4">
|
||||
Setting up Help Scout + AI<br>
|
||||
<span class="text-zinc-500">shouldn't take a week</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-3 gap-8">
|
||||
<!-- Pain Point 1 -->
|
||||
<div class="relative group">
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-red-500/10 to-transparent rounded-2xl"></div>
|
||||
<div class="relative p-8 rounded-2xl bg-zinc-900/50 border border-zinc-800 h-full">
|
||||
<div class="w-12 h-12 rounded-xl bg-red-500/10 flex items-center justify-center mb-6">
|
||||
<i data-lucide="x" class="w-6 h-6 text-red-400"></i>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-zinc-300 mb-3">Repetitive support queries</h3>
|
||||
<p class="text-zinc-500 mb-6">Same questions, every day. Copy-paste answers get stale fast.</p>
|
||||
<div class="pt-6 border-t border-zinc-800">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded-lg bg-brand-500/20 flex items-center justify-center">
|
||||
<i data-lucide="check" class="w-4 h-4 text-brand-400"></i>
|
||||
</div>
|
||||
<p class="text-white font-medium">AI drafts from your docs</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pain Point 2 -->
|
||||
<div class="relative group">
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-red-500/10 to-transparent rounded-2xl"></div>
|
||||
<div class="relative p-8 rounded-2xl bg-zinc-900/50 border border-zinc-800 h-full">
|
||||
<div class="w-12 h-12 rounded-xl bg-red-500/10 flex items-center justify-center mb-6">
|
||||
<i data-lucide="x" class="w-6 h-6 text-red-400"></i>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-zinc-300 mb-3">No customer context</h3>
|
||||
<p class="text-zinc-500 mb-6">Who is this person? What happened before? Time wasted searching.</p>
|
||||
<div class="pt-6 border-t border-zinc-800">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded-lg bg-brand-500/20 flex items-center justify-center">
|
||||
<i data-lucide="check" class="w-4 h-4 text-brand-400"></i>
|
||||
</div>
|
||||
<p class="text-white font-medium">Full history at a glance</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pain Point 3 -->
|
||||
<div class="relative group">
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-red-500/10 to-transparent rounded-2xl"></div>
|
||||
<div class="relative p-8 rounded-2xl bg-zinc-900/50 border border-zinc-800 h-full">
|
||||
<div class="w-12 h-12 rounded-xl bg-red-500/10 flex items-center justify-center mb-6">
|
||||
<i data-lucide="x" class="w-6 h-6 text-red-400"></i>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-zinc-300 mb-3">Manual ticket routing</h3>
|
||||
<p class="text-zinc-500 mb-6">Tickets land in the wrong queue. Customers wait. CSAT drops.</p>
|
||||
<div class="pt-6 border-t border-zinc-800">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded-lg bg-brand-500/20 flex items-center justify-center">
|
||||
<i data-lucide="check" class="w-4 h-4 text-brand-400"></i>
|
||||
</div>
|
||||
<p class="text-white font-medium">AI assigns intelligently</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section id="features" class="py-24 border-t border-zinc-800/50">
|
||||
<div class="max-w-6xl mx-auto px-6">
|
||||
<div class="text-center mb-16">
|
||||
<div class="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-brand-500/10 text-brand-400 text-sm font-medium mb-6">
|
||||
<i data-lucide="sparkles" class="w-4 h-4"></i>
|
||||
Full API Coverage
|
||||
</div>
|
||||
<h2 class="text-3xl md:text-5xl font-bold mb-4">Everything you need</h2>
|
||||
<p class="text-xl text-zinc-400">Full Help Scout API access through one simple connection</p>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<div class="bg-zinc-900/50 rounded-2xl border border-zinc-800 p-6 card-glow">
|
||||
<div class="w-14 h-14 rounded-xl bg-gradient-to-br from-brand-500/20 to-blue-500/20 flex items-center justify-center mb-5 feature-icon">
|
||||
<i data-lucide="message-circle" class="w-7 h-7 text-brand-400"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold mb-3">Conversation Management</h3>
|
||||
<p class="text-zinc-400">Handle emails, chats, and messages — all unified in one place.</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-zinc-900/50 rounded-2xl border border-zinc-800 p-6 card-glow">
|
||||
<div class="w-14 h-14 rounded-xl bg-gradient-to-br from-purple-500/20 to-pink-500/20 flex items-center justify-center mb-5 feature-icon">
|
||||
<i data-lucide="book-open" class="w-7 h-7 text-purple-400"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold mb-3">Docs</h3>
|
||||
<p class="text-zinc-400">Search and surface knowledge base articles automatically.</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-zinc-900/50 rounded-2xl border border-zinc-800 p-6 card-glow">
|
||||
<div class="w-14 h-14 rounded-xl bg-gradient-to-br from-orange-500/20 to-yellow-500/20 flex items-center justify-center mb-5 feature-icon">
|
||||
<i data-lucide="user-circle" class="w-7 h-7 text-orange-400"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold mb-3">Customer Profiles</h3>
|
||||
<p class="text-zinc-400">Access history, properties, and context for every customer.</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-zinc-900/50 rounded-2xl border border-zinc-800 p-6 card-glow">
|
||||
<div class="w-14 h-14 rounded-xl bg-gradient-to-br from-cyan-500/20 to-teal-500/20 flex items-center justify-center mb-5 feature-icon">
|
||||
<i data-lucide="git-merge" class="w-7 h-7 text-cyan-400"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold mb-3">Workflows</h3>
|
||||
<p class="text-zinc-400">Automate tagging, assignment, and responses effortlessly.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-12 text-center">
|
||||
<p class="text-zinc-400 mb-4">+ 50 more endpoints including:</p>
|
||||
<div class="flex flex-wrap justify-center gap-2">
|
||||
<span class="px-3 py-1.5 bg-zinc-800/80 rounded-full text-sm hover:bg-zinc-700 transition cursor-default">Mailboxes</span>
|
||||
<span class="px-3 py-1.5 bg-zinc-800/80 rounded-full text-sm hover:bg-zinc-700 transition cursor-default">Tags</span>
|
||||
<span class="px-3 py-1.5 bg-zinc-800/80 rounded-full text-sm hover:bg-zinc-700 transition cursor-default">Saved Replies</span>
|
||||
<span class="px-3 py-1.5 bg-zinc-800/80 rounded-full text-sm hover:bg-zinc-700 transition cursor-default">Webhooks</span>
|
||||
<span class="px-3 py-1.5 bg-zinc-800/80 rounded-full text-sm hover:bg-zinc-700 transition cursor-default">Teams</span>
|
||||
<span class="px-3 py-1.5 bg-zinc-800/80 rounded-full text-sm hover:bg-zinc-700 transition cursor-default">Users</span>
|
||||
<span class="px-3 py-1.5 bg-zinc-800/80 rounded-full text-sm hover:bg-zinc-700 transition cursor-default">Reports</span>
|
||||
<span class="px-3 py-1.5 bg-zinc-800/80 rounded-full text-sm hover:bg-zinc-700 transition cursor-default">Beacons</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Waitlist -->
|
||||
<section id="pricing" class="py-24 border-t border-zinc-800/50">
|
||||
<div class="max-w-2xl mx-auto px-6">
|
||||
<div class="text-center mb-12">
|
||||
<div class="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-brand-500/20 text-brand-400 text-sm font-medium mb-6">
|
||||
<i data-lucide="sparkles" class="w-4 h-4"></i>
|
||||
Coming Soon
|
||||
</div>
|
||||
<h2 class="text-3xl md:text-5xl font-bold mb-4">Join the Waitlist</h2>
|
||||
<p class="text-xl text-zinc-400">Be the first to know when we launch. Early access + exclusive perks for waitlist members.</p>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 bg-gradient-to-r from-brand-500/10 to-purple-500/10 rounded-3xl blur-xl"></div>
|
||||
<div class="relative bg-zinc-900/80 rounded-2xl border border-zinc-800 p-8 backdrop-blur-sm">
|
||||
<form id="waitlist-form" class="space-y-6">
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-zinc-300 mb-2">Name <span class="text-brand-400">*</span></label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
required
|
||||
placeholder="Your full name"
|
||||
class="w-full px-4 py-3.5 bg-zinc-800/80 border border-zinc-700 rounded-xl text-white placeholder-zinc-500 focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20 transition"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="phone" class="block text-sm font-medium text-zinc-300 mb-2">Phone <span class="text-brand-400">*</span></label>
|
||||
<input
|
||||
type="tel"
|
||||
id="phone"
|
||||
name="phone"
|
||||
required
|
||||
placeholder="Your phone number"
|
||||
class="w-full px-4 py-3.5 bg-zinc-800/80 border border-zinc-700 rounded-xl text-white placeholder-zinc-500 focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20 transition"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-zinc-300 mb-2">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="you@company.com"
|
||||
class="w-full px-4 py-3.5 bg-zinc-800/80 border border-zinc-700 rounded-xl text-white placeholder-zinc-500 focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20 transition"
|
||||
>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
id="submit-btn"
|
||||
class="w-full py-4 bg-gradient-to-r from-brand-500 to-purple-500 hover:from-brand-600 hover:to-purple-600 rounded-xl font-semibold text-lg transition transform hover:scale-[1.02] flex items-center justify-center gap-2 shadow-lg shadow-brand-500/25"
|
||||
>
|
||||
<span>Join the Waitlist</span>
|
||||
<i data-lucide="arrow-right" class="w-5 h-5"></i>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Success Message (hidden by default) -->
|
||||
<div id="success-message" class="hidden text-center py-8">
|
||||
<div class="w-16 h-16 bg-green-500/20 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<i data-lucide="check" class="w-8 h-8 text-green-400"></i>
|
||||
</div>
|
||||
<h3 class="text-2xl font-bold mb-2">You're on the list!</h3>
|
||||
<p class="text-zinc-400">We'll reach out as soon as we're ready for you.</p>
|
||||
</div>
|
||||
|
||||
<p class="text-zinc-500 text-sm text-center mt-6 flex items-center justify-center gap-2">
|
||||
<i data-lucide="lock" class="w-4 h-4"></i>
|
||||
We respect your privacy. No spam, ever.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
document.getElementById('waitlist-form').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
const form = this;
|
||||
const submitBtn = document.getElementById('submit-btn');
|
||||
const successMsg = document.getElementById('success-message');
|
||||
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = '<span>Submitting...</span><svg class="animate-spin w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
|
||||
|
||||
setTimeout(() => {
|
||||
form.classList.add('hidden');
|
||||
successMsg.classList.remove('hidden');
|
||||
}, 1000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Open Source -->
|
||||
<section class="py-24 border-t border-zinc-800/50">
|
||||
<div class="max-w-6xl mx-auto px-6">
|
||||
<div class="bg-gradient-to-br from-zinc-900 to-zinc-900/50 rounded-3xl border border-zinc-800 p-8 md:p-12 relative overflow-hidden">
|
||||
<div class="absolute top-0 right-0 w-96 h-96 bg-brand-500/5 rounded-full blur-3xl"></div>
|
||||
<div class="grid md:grid-cols-2 gap-12 items-center relative">
|
||||
<div>
|
||||
<div class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-zinc-800 text-sm mb-6">
|
||||
<i data-lucide="github" class="w-4 h-4"></i>
|
||||
Open Source
|
||||
</div>
|
||||
<h2 class="text-3xl md:text-4xl font-bold mb-4">
|
||||
Self-host if you want.<br>
|
||||
<span class="text-zinc-500">We won't stop you.</span>
|
||||
</h2>
|
||||
<p class="text-zinc-400 mb-6">
|
||||
The entire MCP server is open source. Run it yourself, modify it, contribute back.
|
||||
The hosted version just saves you the hassle.
|
||||
</p>
|
||||
<a href="https://github.com/BusyBee3333/help-scout-mcp-2026-complete" target="_blank" rel="noopener" class="inline-flex items-center gap-2 text-brand-400 hover:text-brand-300 font-medium transition">
|
||||
View on GitHub
|
||||
<i data-lucide="arrow-right" class="w-4 h-4"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="bg-zinc-950 rounded-xl p-6 font-mono text-sm border border-zinc-800">
|
||||
<div class="flex items-center gap-2 text-zinc-500 mb-4">
|
||||
<span class="w-3 h-3 rounded-full bg-red-500"></span>
|
||||
<span class="w-3 h-3 rounded-full bg-yellow-500"></span>
|
||||
<span class="w-3 h-3 rounded-full bg-green-500"></span>
|
||||
<span class="ml-2">Terminal</span>
|
||||
</div>
|
||||
<pre class="text-zinc-300 overflow-x-auto"><code><span class="text-zinc-500">$</span> git clone https://github.com/BusyBee3333/help-scout-mcp-2026-complete.git
|
||||
<span class="text-zinc-500">$</span> cd mcp && npm install
|
||||
<span class="text-zinc-500">$</span> npm run build
|
||||
<span class="text-zinc-500">$</span> node dist/server.js
|
||||
|
||||
<span class="text-green-400">✓ Help Scout MCP Server running</span>
|
||||
<span class="text-green-400">✓ 54 tools loaded</span>
|
||||
<span class="text-zinc-500">Listening on stdio...</span></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FAQ -->
|
||||
<section id="faq" class="py-24 border-t border-zinc-800/50">
|
||||
<div class="max-w-3xl mx-auto px-6">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-3xl md:text-4xl font-bold mb-4">Frequently asked questions</h2>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<details class="group bg-zinc-900/50 rounded-xl border border-zinc-800 p-6 hover:border-zinc-700 transition">
|
||||
<summary class="flex items-center justify-between cursor-pointer list-none">
|
||||
<span class="font-medium text-lg">What is MCP?</span>
|
||||
<i data-lucide="chevron-down" class="w-5 h-5 text-zinc-400 group-open:rotate-180 transition"></i>
|
||||
</summary>
|
||||
<p class="mt-4 text-zinc-400 leading-relaxed">
|
||||
MCP (Model Context Protocol) is a standard for connecting AI assistants to external tools and data.
|
||||
It's supported by Claude, and lets AI actually take actions in your systems — not just chat about them.
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details class="group bg-zinc-900/50 rounded-xl border border-zinc-800 p-6 hover:border-zinc-700 transition">
|
||||
<summary class="flex items-center justify-between cursor-pointer list-none">
|
||||
<span class="font-medium text-lg">Do I need to install anything?</span>
|
||||
<i data-lucide="chevron-down" class="w-5 h-5 text-zinc-400 group-open:rotate-180 transition"></i>
|
||||
</summary>
|
||||
<p class="mt-4 text-zinc-400 leading-relaxed">
|
||||
For the hosted version, no. Just connect your Help Scout account via OAuth and add the MCP endpoint to your AI client (Claude Desktop, etc.).
|
||||
If you self-host, you'll need Node.js.
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details class="group bg-zinc-900/50 rounded-xl border border-zinc-800 p-6 hover:border-zinc-700 transition">
|
||||
<summary class="flex items-center justify-between cursor-pointer list-none">
|
||||
<span class="font-medium text-lg">Is my data secure?</span>
|
||||
<i data-lucide="chevron-down" class="w-5 h-5 text-zinc-400 group-open:rotate-180 transition"></i>
|
||||
</summary>
|
||||
<p class="mt-4 text-zinc-400 leading-relaxed">
|
||||
Yes. We use OAuth 2.0 and never store your Help Scout API keys. Tokens are encrypted at rest and in transit.
|
||||
You can revoke access anytime from your Help Scout settings.
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details class="group bg-zinc-900/50 rounded-xl border border-zinc-800 p-6 hover:border-zinc-700 transition">
|
||||
<summary class="flex items-center justify-between cursor-pointer list-none">
|
||||
<span class="font-medium text-lg">Can I use this with GPT or other AI models?</span>
|
||||
<i data-lucide="chevron-down" class="w-5 h-5 text-zinc-400 group-open:rotate-180 transition"></i>
|
||||
</summary>
|
||||
<p class="mt-4 text-zinc-400 leading-relaxed">
|
||||
MCP is currently best supported by Claude (Anthropic). GPT can use it via custom implementations.
|
||||
As MCP adoption grows, more clients will support it natively.
|
||||
</p>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="py-24 border-t border-zinc-800/50">
|
||||
<div class="max-w-4xl mx-auto px-6 text-center">
|
||||
<h2 class="text-3xl md:text-5xl font-bold mb-6">
|
||||
Ready to AI-power your Help Scout?
|
||||
</h2>
|
||||
<p class="text-xl text-zinc-400 mb-10">
|
||||
Join 200+ support teams already automating with Help Scout Connect.
|
||||
</p>
|
||||
<div class="flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<a href="#pricing" class="w-full sm:w-auto px-8 py-4 bg-gradient-to-r from-brand-500 to-purple-500 hover:from-brand-600 hover:to-purple-600 rounded-xl font-semibold text-lg transition transform hover:scale-105 shadow-lg shadow-brand-500/25">
|
||||
Join the Waitlist
|
||||
</a>
|
||||
<a href="#" class="w-full sm:w-auto px-8 py-4 bg-zinc-800 hover:bg-zinc-700 rounded-xl font-semibold text-lg transition">
|
||||
Book a Demo
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="border-t border-zinc-800/50 py-12">
|
||||
<div class="max-w-6xl mx-auto px-6">
|
||||
<div class="flex flex-col md:flex-row items-center justify-between gap-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-8 h-8 rounded-lg bg-gradient-to-br from-brand-500 to-blue-400 flex items-center justify-center">
|
||||
<i data-lucide="life-buoy" class="w-5 h-5 text-white"></i>
|
||||
</div>
|
||||
<span class="font-bold text-xl">Help Scout Connect</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-8 text-zinc-400">
|
||||
<a href="https://github.com/BusyBee3333/help-scout-mcp-2026-complete" target="_blank" rel="noopener" class="hover:text-white transition">Privacy</a>
|
||||
<a href="https://github.com/BusyBee3333/help-scout-mcp-2026-complete" target="_blank" rel="noopener" class="hover:text-white transition">Terms</a>
|
||||
<a href="https://github.com/BusyBee3333/help-scout-mcp-2026-complete" target="_blank" rel="noopener" class="hover:text-white transition">GitHub</a>
|
||||
<a href="https://github.com/BusyBee3333/help-scout-mcp-2026-complete" target="_blank" rel="noopener" class="hover:text-white transition">Twitter</a>
|
||||
</div>
|
||||
<p class="text-zinc-500 text-sm">© 2026 Help Scout Connect. Not affiliated with Help Scout.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Sticky Floating CTA -->
|
||||
<div id="sticky-cta" class="fixed bottom-6 right-6 z-50 opacity-0 translate-y-4 transition-all duration-300 pointer-events-none">
|
||||
<a
|
||||
href="#pricing"
|
||||
class="sticky-btn flex items-center gap-2 px-6 py-3 bg-gradient-to-r from-brand-500 to-purple-500 rounded-full font-semibold transition-all transform hover:scale-110"
|
||||
>
|
||||
<i data-lucide="sparkles" class="w-5 h-5"></i>
|
||||
<span>Join Waitlist</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const stickyCta = document.getElementById('sticky-cta');
|
||||
const pricingSection = document.getElementById('pricing');
|
||||
|
||||
function updateStickyCta() {
|
||||
const scrollY = window.scrollY;
|
||||
const pricingTop = pricingSection.offsetTop;
|
||||
const pricingBottom = pricingTop + pricingSection.offsetHeight;
|
||||
const viewportBottom = scrollY + window.innerHeight;
|
||||
|
||||
const shouldShow = scrollY > 300 && (viewportBottom < pricingTop || scrollY > pricingBottom);
|
||||
|
||||
if (shouldShow) {
|
||||
stickyCta.classList.remove('opacity-0', 'translate-y-4', 'pointer-events-none');
|
||||
stickyCta.classList.add('opacity-100', 'translate-y-0', 'pointer-events-auto');
|
||||
} else {
|
||||
stickyCta.classList.add('opacity-0', 'translate-y-4', 'pointer-events-none');
|
||||
stickyCta.classList.remove('opacity-100', 'translate-y-0', 'pointer-events-auto');
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', updateStickyCta);
|
||||
updateStickyCta();
|
||||
</script>
|
||||
|
||||
<script>lucide.createIcons();</script>
|
||||
</body>
|
||||
</html>
|
||||
38
package.json
38
package.json
@ -1,38 +1,20 @@
|
||||
{
|
||||
"name": "@mcpengine/helpscout-mcp-server",
|
||||
"name": "mcp-server-helpscout",
|
||||
"version": "1.0.0",
|
||||
"description": "Complete HelpScout Mailbox API v2 MCP Server with OAuth2 authentication",
|
||||
"type": "module",
|
||||
"main": "dist/main.js",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc && npm run copy-apps",
|
||||
"copy-apps": "mkdir -p dist/apps && cp -r src/apps/*.tsx dist/apps/ 2>/dev/null || true",
|
||||
"dev": "tsc --watch",
|
||||
"start": "node dist/main.js",
|
||||
"prepare": "npm run build"
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js",
|
||||
"dev": "tsx src/index.ts"
|
||||
},
|
||||
"keywords": [
|
||||
"helpscout",
|
||||
"mcp",
|
||||
"support",
|
||||
"customer-service",
|
||||
"helpdesk",
|
||||
"oauth2"
|
||||
],
|
||||
"author": "MCP Engine",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.0.4",
|
||||
"axios": "^1.7.9",
|
||||
"dotenv": "^16.4.7",
|
||||
"react": "^18.3.1"
|
||||
"@modelcontextprotocol/sdk": "^0.5.0",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.5",
|
||||
"@types/react": "^18.3.18",
|
||||
"typescript": "^5.7.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
"@types/node": "^20.10.0",
|
||||
"tsx": "^4.7.0",
|
||||
"typescript": "^5.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
4
railway.json
Normal file
4
railway.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"build": { "builder": "NIXPACKS" },
|
||||
"deploy": { "startCommand": "npm start" }
|
||||
}
|
||||
@ -1,138 +0,0 @@
|
||||
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
|
||||
import type {
|
||||
HelpScoutConfig,
|
||||
AccessTokenResponse,
|
||||
PaginatedResponse
|
||||
} from '../types/index.js';
|
||||
|
||||
export class HelpScoutClient {
|
||||
private config: HelpScoutConfig;
|
||||
private client: AxiosInstance;
|
||||
private baseURL = 'https://api.helpscout.net/v2';
|
||||
|
||||
constructor(config: HelpScoutConfig) {
|
||||
this.config = config;
|
||||
this.client = axios.create({
|
||||
baseURL: this.baseURL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// Add auth interceptor
|
||||
this.client.interceptors.request.use(async (config) => {
|
||||
const token = await this.getAccessToken();
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
return config;
|
||||
});
|
||||
|
||||
// Add error handler
|
||||
this.client.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response) {
|
||||
const { status, data } = error.response;
|
||||
throw new Error(
|
||||
`HelpScout API Error (${status}): ${JSON.stringify(data)}`
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async getAccessToken(): Promise<string> {
|
||||
// Check if we have a valid token
|
||||
if (this.config.accessToken && this.config.tokenExpiry) {
|
||||
if (Date.now() < this.config.tokenExpiry) {
|
||||
return this.config.accessToken;
|
||||
}
|
||||
}
|
||||
|
||||
// Get new token
|
||||
const response = await axios.post<AccessTokenResponse>(
|
||||
'https://api.helpscout.net/v2/oauth2/token',
|
||||
{
|
||||
grant_type: 'client_credentials',
|
||||
client_id: this.config.appId,
|
||||
client_secret: this.config.appSecret,
|
||||
}
|
||||
);
|
||||
|
||||
this.config.accessToken = response.data.access_token;
|
||||
this.config.tokenExpiry = Date.now() + response.data.expires_in * 1000;
|
||||
|
||||
return this.config.accessToken;
|
||||
}
|
||||
|
||||
async get<T>(path: string, params?: any): Promise<T> {
|
||||
const response = await this.client.get<T>(path, { params });
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async post<T>(path: string, data?: any): Promise<T> {
|
||||
const response = await this.client.post<T>(path, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async put<T>(path: string, data?: any): Promise<T> {
|
||||
const response = await this.client.put<T>(path, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async patch<T>(path: string, data?: any): Promise<T> {
|
||||
const response = await this.client.patch<T>(path, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async delete(path: string): Promise<void> {
|
||||
await this.client.delete(path);
|
||||
}
|
||||
|
||||
async *paginate<T>(
|
||||
path: string,
|
||||
params?: any,
|
||||
embedKey?: string
|
||||
): AsyncGenerator<T[], void, unknown> {
|
||||
let nextUrl: string | undefined = path;
|
||||
let page = 1;
|
||||
|
||||
while (nextUrl) {
|
||||
const fullParams = { ...params, page };
|
||||
const response = await this.get<PaginatedResponse<T>>(nextUrl, fullParams);
|
||||
|
||||
// Extract items from embedded response
|
||||
let items: T[] = [];
|
||||
if (response._embedded && embedKey && response._embedded[embedKey]) {
|
||||
items = response._embedded[embedKey];
|
||||
} else if (Array.isArray(response)) {
|
||||
items = response as any;
|
||||
}
|
||||
|
||||
yield items;
|
||||
|
||||
// Check for next page
|
||||
if (
|
||||
response._links?.next &&
|
||||
response.page &&
|
||||
response.page.number < response.page.totalPages
|
||||
) {
|
||||
page++;
|
||||
} else {
|
||||
nextUrl = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getAllPages<T>(
|
||||
path: string,
|
||||
params?: any,
|
||||
embedKey?: string
|
||||
): Promise<T[]> {
|
||||
const allItems: T[] = [];
|
||||
for await (const items of this.paginate<T>(path, params, embedKey)) {
|
||||
allItems.push(...items);
|
||||
}
|
||||
return allItems;
|
||||
}
|
||||
}
|
||||
@ -1,105 +0,0 @@
|
||||
export const companyReportApp = {
|
||||
name: 'company-report',
|
||||
description: 'Overall company performance and KPIs',
|
||||
content: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Company Report</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
|
||||
.container { max-width: 1400px; margin: 0 auto; }
|
||||
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 8px; }
|
||||
.subtitle { font-size: 14px; color: #6f7b8a; margin-bottom: 24px; }
|
||||
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; }
|
||||
.kpi-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.kpi-value { font-size: 32px; font-weight: bold; color: #1f2d3d; margin-bottom: 4px; }
|
||||
.kpi-label { font-size: 13px; color: #6f7b8a; margin-bottom: 8px; }
|
||||
.kpi-change { font-size: 12px; font-weight: 500; }
|
||||
.kpi-change.positive { color: #10b981; }
|
||||
.kpi-change.negative { color: #ef4444; }
|
||||
.sections-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; }
|
||||
.section { background: white; padding: 24px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.section-title { font-size: 18px; font-weight: 600; color: #1f2d3d; margin-bottom: 16px; }
|
||||
.chart-placeholder { height: 200px; background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%); border-radius: 6px; display: flex; align-items: center; justify-content: center; color: #0369a1; font-size: 14px; }
|
||||
.summary-item { padding: 12px 0; border-bottom: 1px solid #f0f2f5; display: flex; justify-content: space-between; }
|
||||
.summary-item:last-child { border-bottom: none; }
|
||||
.summary-label { font-size: 14px; color: #6f7b8a; }
|
||||
.summary-value { font-size: 14px; font-weight: 600; color: #1f2d3d; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🏢 Company Report</h1>
|
||||
<div class="subtitle">Overall performance overview for last 30 days</div>
|
||||
|
||||
<div class="kpi-grid">
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">CONVERSATIONS CREATED</div>
|
||||
<div class="kpi-value">1,524</div>
|
||||
<div class="kpi-change positive">↑ 15% vs last month</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">CONVERSATIONS CLOSED</div>
|
||||
<div class="kpi-value">1,389</div>
|
||||
<div class="kpi-change positive">↑ 12% vs last month</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">CUSTOMERS HELPED</div>
|
||||
<div class="kpi-value">847</div>
|
||||
<div class="kpi-change positive">↑ 8% vs last month</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">HAPPINESS SCORE</div>
|
||||
<div class="kpi-value">94%</div>
|
||||
<div class="kpi-change positive">↑ 3% vs last month</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">REPLIES SENT</div>
|
||||
<div class="kpi-value">3,247</div>
|
||||
<div class="kpi-change positive">↑ 18% vs last month</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">AVG RESOLUTION TIME</div>
|
||||
<div class="kpi-value">8.4h</div>
|
||||
<div class="kpi-change negative">↑ 1.2h vs last month</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sections-grid">
|
||||
<div class="section">
|
||||
<div class="section-title">Conversation Volume Trend</div>
|
||||
<div class="chart-placeholder">📈 7-day conversation trend chart</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Key Metrics Summary</div>
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">First Response Time</span>
|
||||
<span class="summary-value">3.2 hours</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">Active Conversations</span>
|
||||
<span class="summary-value">147</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">Pending Conversations</span>
|
||||
<span class="summary-value">23</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">Team Members</span>
|
||||
<span class="summary-value">12 agents</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">Active Mailboxes</span>
|
||||
<span class="summary-value">3</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
};
|
||||
@ -1,141 +0,0 @@
|
||||
export const conversationDashboardApp = {
|
||||
name: 'conversation-dashboard',
|
||||
description: 'Dashboard view of conversations with filters and quick actions',
|
||||
content: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Conversation Dashboard</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #f7f9fc;
|
||||
padding: 20px;
|
||||
}
|
||||
.dashboard { max-width: 1400px; margin: 0 auto; }
|
||||
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 20px; }
|
||||
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; }
|
||||
.stat-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.stat-value { font-size: 32px; font-weight: bold; color: #3197d6; }
|
||||
.stat-label { font-size: 14px; color: #6f7b8a; margin-top: 4px; }
|
||||
.filters { background: white; padding: 16px; border-radius: 8px; margin-bottom: 20px; display: flex; gap: 12px; flex-wrap: wrap; }
|
||||
.filter-group { display: flex; flex-direction: column; gap: 4px; }
|
||||
.filter-group label { font-size: 12px; color: #6f7b8a; }
|
||||
.filter-group select, .filter-group input { padding: 8px; border: 1px solid #d9dee4; border-radius: 4px; font-size: 14px; }
|
||||
.conversations { background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.conv-header { display: grid; grid-template-columns: 1fr 150px 120px 100px 100px; padding: 12px 16px; background: #f7f9fc; border-bottom: 1px solid #d9dee4; font-size: 12px; font-weight: 600; color: #6f7b8a; }
|
||||
.conv-row { display: grid; grid-template-columns: 1fr 150px 120px 100px 100px; padding: 16px; border-bottom: 1px solid #f0f2f5; align-items: center; cursor: pointer; transition: background 0.2s; }
|
||||
.conv-row:hover { background: #f7f9fc; }
|
||||
.conv-subject { font-weight: 500; color: #1f2d3d; }
|
||||
.conv-preview { font-size: 13px; color: #6f7b8a; margin-top: 4px; }
|
||||
.conv-customer { font-size: 14px; color: #3197d6; }
|
||||
.conv-assignee { font-size: 14px; color: #6f7b8a; }
|
||||
.status-badge { padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: 500; text-align: center; }
|
||||
.status-active { background: #d4f4dd; color: #0a6640; }
|
||||
.status-pending { background: #fff4cc; color: #b45309; }
|
||||
.status-closed { background: #e5e7eb; color: #4b5563; }
|
||||
.conv-date { font-size: 13px; color: #6f7b8a; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="dashboard">
|
||||
<h1>📬 Conversation Dashboard</h1>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">147</div>
|
||||
<div class="stat-label">Active Conversations</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">23</div>
|
||||
<div class="stat-label">Pending</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">8</div>
|
||||
<div class="stat-label">Unassigned</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">4.2h</div>
|
||||
<div class="stat-label">Avg Response Time</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filters">
|
||||
<div class="filter-group">
|
||||
<label>Status</label>
|
||||
<select>
|
||||
<option>All</option>
|
||||
<option>Active</option>
|
||||
<option>Pending</option>
|
||||
<option>Closed</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label>Mailbox</label>
|
||||
<select>
|
||||
<option>All Mailboxes</option>
|
||||
<option>Support</option>
|
||||
<option>Sales</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label>Assigned To</label>
|
||||
<select>
|
||||
<option>Everyone</option>
|
||||
<option>Me</option>
|
||||
<option>Unassigned</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label>Search</label>
|
||||
<input type="text" placeholder="Search conversations...">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="conversations">
|
||||
<div class="conv-header">
|
||||
<div>SUBJECT</div>
|
||||
<div>CUSTOMER</div>
|
||||
<div>ASSIGNED</div>
|
||||
<div>STATUS</div>
|
||||
<div>UPDATED</div>
|
||||
</div>
|
||||
<div class="conv-row" onclick="alert('Open conversation detail')">
|
||||
<div>
|
||||
<div class="conv-subject">Payment issue with subscription</div>
|
||||
<div class="conv-preview">Customer having trouble with their credit card...</div>
|
||||
</div>
|
||||
<div class="conv-customer">Sarah Chen</div>
|
||||
<div class="conv-assignee">John Smith</div>
|
||||
<div><span class="status-badge status-active">Active</span></div>
|
||||
<div class="conv-date">2 hours ago</div>
|
||||
</div>
|
||||
<div class="conv-row" onclick="alert('Open conversation detail')">
|
||||
<div>
|
||||
<div class="conv-subject">Feature request: Dark mode</div>
|
||||
<div class="conv-preview">Would love to have a dark mode option...</div>
|
||||
</div>
|
||||
<div class="conv-customer">Mike Johnson</div>
|
||||
<div class="conv-assignee">Emily Davis</div>
|
||||
<div><span class="status-badge status-pending">Pending</span></div>
|
||||
<div class="conv-date">5 hours ago</div>
|
||||
</div>
|
||||
<div class="conv-row" onclick="alert('Open conversation detail')">
|
||||
<div>
|
||||
<div class="conv-subject">Login problems</div>
|
||||
<div class="conv-preview">Cannot log in with my account credentials...</div>
|
||||
</div>
|
||||
<div class="conv-customer">Alex Brown</div>
|
||||
<div class="conv-assignee">—</div>
|
||||
<div><span class="status-badge status-active">Active</span></div>
|
||||
<div class="conv-date">1 day ago</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
};
|
||||
@ -1,134 +0,0 @@
|
||||
export const conversationDetailApp = {
|
||||
name: 'conversation-detail',
|
||||
description: 'Detailed view of a single conversation with thread history and actions',
|
||||
content: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Conversation Detail</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #f7f9fc;
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
}
|
||||
.sidebar { width: 300px; background: white; border-right: 1px solid #d9dee4; padding: 20px; overflow-y: auto; }
|
||||
.main { flex: 1; display: flex; flex-direction: column; }
|
||||
.header { background: white; padding: 16px 24px; border-bottom: 1px solid #d9dee4; display: flex; justify-content: space-between; align-items: center; }
|
||||
.subject { font-size: 20px; font-weight: 600; color: #1f2d3d; }
|
||||
.actions { display: flex; gap: 8px; }
|
||||
.btn { padding: 8px 16px; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; border: 1px solid #d9dee4; background: white; }
|
||||
.btn:hover { background: #f7f9fc; }
|
||||
.btn-primary { background: #3197d6; color: white; border: none; }
|
||||
.btn-primary:hover { background: #2578af; }
|
||||
.threads { flex: 1; overflow-y: auto; padding: 24px; }
|
||||
.thread { background: white; border-radius: 8px; padding: 20px; margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.thread-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
|
||||
.thread-author { font-weight: 600; color: #1f2d3d; }
|
||||
.thread-time { font-size: 13px; color: #6f7b8a; }
|
||||
.thread-body { color: #3e4c59; line-height: 1.6; }
|
||||
.thread-note { background: #fff9e6; border-left: 4px solid #f59e0b; }
|
||||
.sidebar-section { margin-bottom: 24px; }
|
||||
.sidebar-label { font-size: 12px; font-weight: 600; color: #6f7b8a; margin-bottom: 8px; text-transform: uppercase; }
|
||||
.sidebar-value { font-size: 14px; color: #1f2d3d; }
|
||||
.tag { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 12px; background: #e0e7ff; color: #3730a3; margin-right: 4px; margin-bottom: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-label">Customer</div>
|
||||
<div class="sidebar-value" style="color: #3197d6; font-weight: 500;">Sarah Chen</div>
|
||||
<div style="font-size: 13px; color: #6f7b8a; margin-top: 4px;">sarah.chen@example.com</div>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-label">Status</div>
|
||||
<div class="sidebar-value">
|
||||
<select style="width: 100%; padding: 8px; border: 1px solid #d9dee4; border-radius: 4px;">
|
||||
<option>Active</option>
|
||||
<option>Pending</option>
|
||||
<option>Closed</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-label">Assigned To</div>
|
||||
<div class="sidebar-value">
|
||||
<select style="width: 100%; padding: 8px; border: 1px solid #d9dee4; border-radius: 4px;">
|
||||
<option>John Smith</option>
|
||||
<option>Emily Davis</option>
|
||||
<option>Unassigned</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-label">Tags</div>
|
||||
<div>
|
||||
<span class="tag">billing</span>
|
||||
<span class="tag">urgent</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-label">Mailbox</div>
|
||||
<div class="sidebar-value">Support</div>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-label">Created</div>
|
||||
<div class="sidebar-value" style="font-size: 13px;">Jan 15, 2025 2:34 PM</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="header">
|
||||
<div class="subject">Payment issue with subscription</div>
|
||||
<div class="actions">
|
||||
<button class="btn">Add Note</button>
|
||||
<button class="btn btn-primary">Reply</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="threads">
|
||||
<div class="thread">
|
||||
<div class="thread-header">
|
||||
<div class="thread-author">Sarah Chen (Customer)</div>
|
||||
<div class="thread-time">2 hours ago</div>
|
||||
</div>
|
||||
<div class="thread-body">
|
||||
Hi, I'm having trouble updating my payment method. Every time I try to save my new credit card, I get an error message. Can you help?
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="thread thread-note">
|
||||
<div class="thread-header">
|
||||
<div class="thread-author">🔒 John Smith (Internal Note)</div>
|
||||
<div class="thread-time">1 hour ago</div>
|
||||
</div>
|
||||
<div class="thread-body">
|
||||
Checking with billing team - seems like a known issue with Visa cards from Canada. Should have a fix deployed by EOD.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="thread">
|
||||
<div class="thread-header">
|
||||
<div class="thread-author">John Smith (Agent)</div>
|
||||
<div class="thread-time">30 minutes ago</div>
|
||||
</div>
|
||||
<div class="thread-body">
|
||||
Hi Sarah,<br><br>
|
||||
Thanks for reaching out! I've identified the issue - there's a temporary problem with processing Canadian Visa cards. Our engineering team is working on a fix that should be live within the next few hours.<br><br>
|
||||
In the meantime, if you have an alternative payment method (Mastercard or American Express), that should work without issues.<br><br>
|
||||
I'll follow up once the fix is deployed!<br><br>
|
||||
Best regards,<br>
|
||||
John
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
};
|
||||
@ -1,104 +0,0 @@
|
||||
export const conversationGridApp = {
|
||||
name: 'conversation-grid',
|
||||
description: 'Alternative grid layout for conversations with card view',
|
||||
content: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Conversations Grid</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
|
||||
.container { max-width: 1400px; margin: 0 auto; }
|
||||
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 20px; }
|
||||
.conv-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 16px; }
|
||||
.conv-card { background: white; border-radius: 8px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); cursor: pointer; transition: transform 0.2s; }
|
||||
.conv-card:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.15); }
|
||||
.conv-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px; }
|
||||
.conv-number { font-size: 12px; color: #9ca3af; font-weight: 500; }
|
||||
.status-badge { padding: 4px 8px; border-radius: 4px; font-size: 11px; font-weight: 500; }
|
||||
.status-active { background: #d4f4dd; color: #0a6640; }
|
||||
.status-pending { background: #fff4cc; color: #b45309; }
|
||||
.conv-subject { font-size: 16px; font-weight: 600; color: #1f2d3d; margin-bottom: 8px; }
|
||||
.conv-preview { font-size: 13px; color: #6f7b8a; line-height: 1.5; margin-bottom: 16px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
.conv-footer { display: flex; justify-content: space-between; align-items: center; padding-top: 12px; border-top: 1px solid #f0f2f5; }
|
||||
.customer-info { display: flex; align-items: center; gap: 8px; }
|
||||
.customer-avatar { width: 28px; height: 28px; border-radius: 50%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 11px; font-weight: 600; }
|
||||
.customer-name { font-size: 13px; color: #3197d6; font-weight: 500; }
|
||||
.conv-time { font-size: 12px; color: #9ca3af; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>📬 Conversations</h1>
|
||||
<div class="conv-grid">
|
||||
<div class="conv-card" onclick="alert('Open conversation')">
|
||||
<div class="conv-header">
|
||||
<span class="conv-number">#1247</span>
|
||||
<span class="status-badge status-active">Active</span>
|
||||
</div>
|
||||
<div class="conv-subject">Payment issue with subscription</div>
|
||||
<div class="conv-preview">Customer having trouble with their credit card. Error appears when trying to update payment method...</div>
|
||||
<div class="conv-footer">
|
||||
<div class="customer-info">
|
||||
<div class="customer-avatar">SC</div>
|
||||
<span class="customer-name">Sarah Chen</span>
|
||||
</div>
|
||||
<span class="conv-time">2h ago</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="conv-card" onclick="alert('Open conversation')">
|
||||
<div class="conv-header">
|
||||
<span class="conv-number">#1246</span>
|
||||
<span class="status-badge status-pending">Pending</span>
|
||||
</div>
|
||||
<div class="conv-subject">Feature request: Dark mode</div>
|
||||
<div class="conv-preview">Would love to have a dark mode option for the application. Many users work late hours...</div>
|
||||
<div class="conv-footer">
|
||||
<div class="customer-info">
|
||||
<div class="customer-avatar" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">MJ</div>
|
||||
<span class="customer-name">Mike Johnson</span>
|
||||
</div>
|
||||
<span class="conv-time">5h ago</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="conv-card" onclick="alert('Open conversation')">
|
||||
<div class="conv-header">
|
||||
<span class="conv-number">#1245</span>
|
||||
<span class="status-badge status-active">Active</span>
|
||||
</div>
|
||||
<div class="conv-subject">Login problems</div>
|
||||
<div class="conv-preview">Cannot log in with my account credentials. Password reset doesn't seem to work either...</div>
|
||||
<div class="conv-footer">
|
||||
<div class="customer-info">
|
||||
<div class="customer-avatar" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">AB</div>
|
||||
<span class="customer-name">Alex Brown</span>
|
||||
</div>
|
||||
<span class="conv-time">1d ago</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="conv-card" onclick="alert('Open conversation')">
|
||||
<div class="conv-header">
|
||||
<span class="conv-number">#1244</span>
|
||||
<span class="status-badge status-active">Active</span>
|
||||
</div>
|
||||
<div class="conv-subject">Question about API limits</div>
|
||||
<div class="conv-preview">What are the rate limits for the API? We're planning to scale our integration...</div>
|
||||
<div class="conv-footer">
|
||||
<div class="customer-info">
|
||||
<div class="customer-avatar" style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);">ED</div>
|
||||
<span class="customer-name">Emily Davis</span>
|
||||
</div>
|
||||
<span class="conv-time">1d ago</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
};
|
||||
@ -1,127 +0,0 @@
|
||||
export const conversationTimelineApp = {
|
||||
name: 'conversation-timeline',
|
||||
description: 'Visual timeline view of conversation history and events',
|
||||
content: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Conversation Timeline</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
|
||||
.container { max-width: 900px; margin: 0 auto; }
|
||||
.header { background: white; padding: 24px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.conv-subject { font-size: 24px; font-weight: 600; color: #1f2d3d; margin-bottom: 8px; }
|
||||
.conv-meta { font-size: 14px; color: #6f7b8a; }
|
||||
.timeline { position: relative; padding-left: 40px; }
|
||||
.timeline::before { content: ''; position: absolute; left: 16px; top: 0; bottom: 0; width: 2px; background: #d9dee4; }
|
||||
.timeline-item { position: relative; background: white; padding: 20px; border-radius: 8px; margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.timeline-marker { position: absolute; left: -29px; width: 12px; height: 12px; border-radius: 50%; background: #3197d6; border: 3px solid white; box-shadow: 0 0 0 2px #3197d6; }
|
||||
.timeline-marker.note { background: #f59e0b; box-shadow: 0 0 0 2px #f59e0b; }
|
||||
.timeline-marker.event { background: #10b981; box-shadow: 0 0 0 2px #10b981; }
|
||||
.timeline-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
|
||||
.timeline-author { font-weight: 600; color: #1f2d3d; }
|
||||
.timeline-type { font-size: 12px; color: #6f7b8a; margin-left: 8px; }
|
||||
.timeline-time { font-size: 13px; color: #9ca3af; }
|
||||
.timeline-body { color: #3e4c59; line-height: 1.6; }
|
||||
.event-label { display: inline-block; padding: 6px 12px; border-radius: 4px; font-size: 13px; font-weight: 500; background: #f0fdf4; color: #166534; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<div class="conv-subject">Payment issue with subscription</div>
|
||||
<div class="conv-meta">Conversation #1247 • Created 2 days ago by Sarah Chen</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline">
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-marker"></div>
|
||||
<div class="timeline-header">
|
||||
<div>
|
||||
<span class="timeline-author">Sarah Chen</span>
|
||||
<span class="timeline-type">(Customer)</span>
|
||||
</div>
|
||||
<div class="timeline-time">2 days ago • 2:34 PM</div>
|
||||
</div>
|
||||
<div class="timeline-body">
|
||||
Hi, I'm having trouble updating my payment method. Every time I try to save my new credit card, I get an error message. Can you help?
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-marker event"></div>
|
||||
<div class="timeline-header">
|
||||
<div>
|
||||
<span class="timeline-author">System Event</span>
|
||||
</div>
|
||||
<div class="timeline-time">2 days ago • 2:35 PM</div>
|
||||
</div>
|
||||
<div class="timeline-body">
|
||||
<span class="event-label">✓ Assigned to John Smith</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-marker event"></div>
|
||||
<div class="timeline-header">
|
||||
<div>
|
||||
<span class="timeline-author">System Event</span>
|
||||
</div>
|
||||
<div class="timeline-time">2 days ago • 2:35 PM</div>
|
||||
</div>
|
||||
<div class="timeline-body">
|
||||
<span class="event-label">🏷️ Tags added: billing, urgent</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-marker note"></div>
|
||||
<div class="timeline-header">
|
||||
<div>
|
||||
<span class="timeline-author">John Smith</span>
|
||||
<span class="timeline-type">(Internal Note)</span>
|
||||
</div>
|
||||
<div class="timeline-time">2 days ago • 3:15 PM</div>
|
||||
</div>
|
||||
<div class="timeline-body">
|
||||
Checking with billing team - seems like a known issue with Visa cards from Canada. Should have a fix deployed by EOD.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-marker"></div>
|
||||
<div class="timeline-header">
|
||||
<div>
|
||||
<span class="timeline-author">John Smith</span>
|
||||
<span class="timeline-type">(Agent Reply)</span>
|
||||
</div>
|
||||
<div class="timeline-time">2 days ago • 4:30 PM</div>
|
||||
</div>
|
||||
<div class="timeline-body">
|
||||
Hi Sarah,<br><br>
|
||||
Thanks for reaching out! I've identified the issue - there's a temporary problem with processing Canadian Visa cards. Our engineering team is working on a fix that should be live within the next few hours.<br><br>
|
||||
In the meantime, if you have an alternative payment method (Mastercard or American Express), that should work without issues.<br><br>
|
||||
I'll follow up once the fix is deployed!
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-marker event"></div>
|
||||
<div class="timeline-header">
|
||||
<div>
|
||||
<span class="timeline-author">System Event</span>
|
||||
</div>
|
||||
<div class="timeline-time">2 days ago • 4:31 PM</div>
|
||||
</div>
|
||||
<div class="timeline-body">
|
||||
<span class="event-label">Status changed: Active → Pending</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
};
|
||||
@ -1,114 +0,0 @@
|
||||
export const customerDetailApp = {
|
||||
name: 'customer-detail',
|
||||
description: 'Detailed customer profile with history and contact information',
|
||||
content: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Customer Detail</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; }
|
||||
.container { max-width: 1200px; margin: 0 auto; padding: 20px; display: grid; grid-template-columns: 350px 1fr; gap: 20px; }
|
||||
.sidebar { background: white; border-radius: 8px; padding: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.main { background: white; border-radius: 8px; padding: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.avatar { width: 80px; height: 80px; border-radius: 50%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 32px; font-weight: bold; margin: 0 auto 16px; }
|
||||
.customer-name { font-size: 22px; font-weight: 600; color: #1f2d3d; text-align: center; margin-bottom: 8px; }
|
||||
.customer-org { font-size: 14px; color: #6f7b8a; text-align: center; margin-bottom: 24px; }
|
||||
.info-section { margin-bottom: 24px; }
|
||||
.info-label { font-size: 12px; font-weight: 600; color: #6f7b8a; margin-bottom: 8px; text-transform: uppercase; }
|
||||
.info-value { font-size: 14px; color: #1f2d3d; margin-bottom: 8px; }
|
||||
.info-value a { color: #3197d6; text-decoration: none; }
|
||||
.btn { width: 100%; padding: 10px; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; border: 1px solid #d9dee4; background: white; margin-bottom: 8px; }
|
||||
.btn:hover { background: #f7f9fc; }
|
||||
.tabs { display: flex; border-bottom: 1px solid #d9dee4; margin-bottom: 24px; }
|
||||
.tab { padding: 12px 24px; font-size: 14px; font-weight: 500; color: #6f7b8a; cursor: pointer; border-bottom: 2px solid transparent; }
|
||||
.tab.active { color: #3197d6; border-bottom-color: #3197d6; }
|
||||
.conversation-item { padding: 16px; border: 1px solid #e5e7eb; border-radius: 6px; margin-bottom: 12px; cursor: pointer; }
|
||||
.conversation-item:hover { background: #f7f9fc; }
|
||||
.conv-subject { font-weight: 500; color: #1f2d3d; margin-bottom: 4px; }
|
||||
.conv-meta { font-size: 13px; color: #6f7b8a; }
|
||||
.status-badge { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 11px; font-weight: 500; margin-left: 8px; }
|
||||
.status-active { background: #d4f4dd; color: #0a6640; }
|
||||
.status-closed { background: #e5e7eb; color: #4b5563; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="sidebar">
|
||||
<div class="avatar">SC</div>
|
||||
<div class="customer-name">Sarah Chen</div>
|
||||
<div class="customer-org">Acme Corporation</div>
|
||||
|
||||
<button class="btn">📧 Send Email</button>
|
||||
<button class="btn">💬 Start Conversation</button>
|
||||
|
||||
<div class="info-section">
|
||||
<div class="info-label">Email</div>
|
||||
<div class="info-value"><a href="mailto:sarah.chen@example.com">sarah.chen@example.com</a></div>
|
||||
</div>
|
||||
|
||||
<div class="info-section">
|
||||
<div class="info-label">Phone</div>
|
||||
<div class="info-value"><a href="tel:+15551234567">+1 (555) 123-4567</a></div>
|
||||
</div>
|
||||
|
||||
<div class="info-section">
|
||||
<div class="info-label">Job Title</div>
|
||||
<div class="info-value">Product Manager</div>
|
||||
</div>
|
||||
|
||||
<div class="info-section">
|
||||
<div class="info-label">Location</div>
|
||||
<div class="info-value">San Francisco, CA</div>
|
||||
</div>
|
||||
|
||||
<div class="info-section">
|
||||
<div class="info-label">Customer Since</div>
|
||||
<div class="info-value">January 2024</div>
|
||||
</div>
|
||||
|
||||
<div class="info-section">
|
||||
<div class="info-label">Background</div>
|
||||
<div class="info-value" style="font-size: 13px;">Long-time customer, frequently provides valuable product feedback.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="tabs">
|
||||
<div class="tab active">Conversations (12)</div>
|
||||
<div class="tab">Notes (3)</div>
|
||||
<div class="tab">Activity</div>
|
||||
</div>
|
||||
|
||||
<div class="conversation-item" onclick="alert('Open conversation')">
|
||||
<div class="conv-subject">
|
||||
Payment issue with subscription
|
||||
<span class="status-badge status-active">Active</span>
|
||||
</div>
|
||||
<div class="conv-meta">Last updated 2 hours ago • Assigned to John Smith</div>
|
||||
</div>
|
||||
|
||||
<div class="conversation-item" onclick="alert('Open conversation')">
|
||||
<div class="conv-subject">
|
||||
Feature request: Dark mode
|
||||
<span class="status-badge status-closed">Closed</span>
|
||||
</div>
|
||||
<div class="conv-meta">Closed 3 days ago • Assigned to Emily Davis</div>
|
||||
</div>
|
||||
|
||||
<div class="conversation-item" onclick="alert('Open conversation')">
|
||||
<div class="conv-subject">
|
||||
Question about API limits
|
||||
<span class="status-badge status-closed">Closed</span>
|
||||
</div>
|
||||
<div class="conv-meta">Closed 1 week ago • Assigned to John Smith</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
};
|
||||
@ -1,85 +0,0 @@
|
||||
export const customerGridApp = {
|
||||
name: 'customer-grid',
|
||||
description: 'Grid view of all customers with search and filtering',
|
||||
content: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Customers</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
|
||||
.container { max-width: 1400px; margin: 0 auto; }
|
||||
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 20px; }
|
||||
.toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||
.search { flex: 1; max-width: 400px; }
|
||||
.search input { width: 100%; padding: 10px 16px; border: 1px solid #d9dee4; border-radius: 6px; font-size: 14px; }
|
||||
.btn { padding: 10px 20px; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; border: none; }
|
||||
.btn-primary { background: #3197d6; color: white; }
|
||||
.customer-grid { background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.grid-header { display: grid; grid-template-columns: 250px 200px 150px 1fr 120px; padding: 12px 16px; background: #f7f9fc; border-bottom: 1px solid #d9dee4; font-size: 12px; font-weight: 600; color: #6f7b8a; }
|
||||
.grid-row { display: grid; grid-template-columns: 250px 200px 150px 1fr 120px; padding: 16px; border-bottom: 1px solid #f0f2f5; align-items: center; cursor: pointer; transition: background 0.2s; }
|
||||
.grid-row:hover { background: #f7f9fc; }
|
||||
.customer-name { font-weight: 500; color: #1f2d3d; }
|
||||
.customer-email { font-size: 13px; color: #3197d6; margin-top: 2px; }
|
||||
.customer-phone { font-size: 14px; color: #6f7b8a; }
|
||||
.customer-org { font-size: 14px; color: #6f7b8a; }
|
||||
.customer-location { font-size: 14px; color: #6f7b8a; }
|
||||
.customer-count { font-size: 13px; color: #6f7b8a; text-align: center; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>👥 Customers</h1>
|
||||
<div class="toolbar">
|
||||
<div class="search">
|
||||
<input type="text" placeholder="Search customers by name, email, or organization...">
|
||||
</div>
|
||||
<button class="btn btn-primary">+ Add Customer</button>
|
||||
</div>
|
||||
<div class="customer-grid">
|
||||
<div class="grid-header">
|
||||
<div>NAME</div>
|
||||
<div>EMAIL</div>
|
||||
<div>PHONE</div>
|
||||
<div>ORGANIZATION</div>
|
||||
<div>CONVERSATIONS</div>
|
||||
</div>
|
||||
<div class="grid-row" onclick="alert('Open customer detail')">
|
||||
<div>
|
||||
<div class="customer-name">Sarah Chen</div>
|
||||
<div class="customer-email">sarah.chen@example.com</div>
|
||||
</div>
|
||||
<div class="customer-email">sarah.chen@example.com</div>
|
||||
<div class="customer-phone">+1 (555) 123-4567</div>
|
||||
<div class="customer-org">Acme Corp</div>
|
||||
<div class="customer-count">12 conversations</div>
|
||||
</div>
|
||||
<div class="grid-row" onclick="alert('Open customer detail')">
|
||||
<div>
|
||||
<div class="customer-name">Mike Johnson</div>
|
||||
<div class="customer-email">mike.j@techstart.io</div>
|
||||
</div>
|
||||
<div class="customer-email">mike.j@techstart.io</div>
|
||||
<div class="customer-phone">+1 (555) 987-6543</div>
|
||||
<div class="customer-org">TechStart</div>
|
||||
<div class="customer-count">8 conversations</div>
|
||||
</div>
|
||||
<div class="grid-row" onclick="alert('Open customer detail')">
|
||||
<div>
|
||||
<div class="customer-name">Emily Davis</div>
|
||||
<div class="customer-email">emily@startup.com</div>
|
||||
</div>
|
||||
<div class="customer-email">emily@startup.com</div>
|
||||
<div class="customer-phone">+1 (555) 456-7890</div>
|
||||
<div class="customer-org">Startup Inc</div>
|
||||
<div class="customer-count">5 conversations</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
};
|
||||
@ -1,116 +0,0 @@
|
||||
export const folderBrowserApp = {
|
||||
name: 'folder-browser',
|
||||
description: 'Browse and manage mailbox folders',
|
||||
content: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Folder Browser</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
|
||||
.container { max-width: 1200px; margin: 0 auto; display: grid; grid-template-columns: 300px 1fr; gap: 20px; }
|
||||
.sidebar { background: white; border-radius: 8px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); height: fit-content; }
|
||||
.main { background: white; border-radius: 8px; padding: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.mailbox-section { margin-bottom: 24px; }
|
||||
.mailbox-name { font-size: 14px; font-weight: 600; color: #6f7b8a; margin-bottom: 12px; text-transform: uppercase; }
|
||||
.folder-list { display: flex; flex-direction: column; gap: 4px; }
|
||||
.folder-item { padding: 10px 12px; border-radius: 6px; cursor: pointer; transition: background 0.2s; display: flex; justify-content: space-between; align-items: center; }
|
||||
.folder-item:hover { background: #f7f9fc; }
|
||||
.folder-item.active { background: #e0f2fe; color: #0369a1; font-weight: 500; }
|
||||
.folder-name { font-size: 14px; display: flex; align-items: center; gap: 8px; }
|
||||
.folder-count { font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #e5e7eb; color: #4b5563; font-weight: 600; }
|
||||
.folder-item.active .folder-count { background: #0369a1; color: white; }
|
||||
.main-header { margin-bottom: 24px; }
|
||||
.main-title { font-size: 24px; font-weight: 600; color: #1f2d3d; margin-bottom: 8px; }
|
||||
.main-subtitle { font-size: 14px; color: #6f7b8a; }
|
||||
.conv-list { display: flex; flex-direction: column; gap: 12px; }
|
||||
.conv-item { padding: 16px; border: 1px solid #e5e7eb; border-radius: 6px; cursor: pointer; transition: all 0.2s; }
|
||||
.conv-item:hover { border-color: #3197d6; background: #f7f9fc; }
|
||||
.conv-subject { font-size: 15px; font-weight: 500; color: #1f2d3d; margin-bottom: 4px; }
|
||||
.conv-meta { font-size: 13px; color: #6f7b8a; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="sidebar">
|
||||
<div class="mailbox-section">
|
||||
<div class="mailbox-name">Support Mailbox</div>
|
||||
<div class="folder-list">
|
||||
<div class="folder-item active">
|
||||
<span class="folder-name">📥 Unassigned</span>
|
||||
<span class="folder-count">8</span>
|
||||
</div>
|
||||
<div class="folder-item">
|
||||
<span class="folder-name">📋 My Conversations</span>
|
||||
<span class="folder-count">23</span>
|
||||
</div>
|
||||
<div class="folder-item">
|
||||
<span class="folder-name">📌 Drafts</span>
|
||||
<span class="folder-count">2</span>
|
||||
</div>
|
||||
<div class="folder-item">
|
||||
<span class="folder-name">👥 Assigned</span>
|
||||
<span class="folder-count">47</span>
|
||||
</div>
|
||||
<div class="folder-item">
|
||||
<span class="folder-name">✅ Closed</span>
|
||||
<span class="folder-count">147</span>
|
||||
</div>
|
||||
<div class="folder-item">
|
||||
<span class="folder-name">🚫 Spam</span>
|
||||
<span class="folder-count">3</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mailbox-section">
|
||||
<div class="mailbox-name">Sales Mailbox</div>
|
||||
<div class="folder-list">
|
||||
<div class="folder-item">
|
||||
<span class="folder-name">📥 Unassigned</span>
|
||||
<span class="folder-count">3</span>
|
||||
</div>
|
||||
<div class="folder-item">
|
||||
<span class="folder-name">📋 My Conversations</span>
|
||||
<span class="folder-count">12</span>
|
||||
</div>
|
||||
<div class="folder-item">
|
||||
<span class="folder-name">✅ Closed</span>
|
||||
<span class="folder-count">89</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="main-header">
|
||||
<div class="main-title">📥 Unassigned</div>
|
||||
<div class="main-subtitle">8 conversations need to be assigned</div>
|
||||
</div>
|
||||
|
||||
<div class="conv-list">
|
||||
<div class="conv-item" onclick="alert('Open conversation')">
|
||||
<div class="conv-subject">Payment issue with subscription</div>
|
||||
<div class="conv-meta">Sarah Chen • 2 hours ago • #1247</div>
|
||||
</div>
|
||||
<div class="conv-item" onclick="alert('Open conversation')">
|
||||
<div class="conv-subject">Login problems</div>
|
||||
<div class="conv-meta">Alex Brown • 1 day ago • #1245</div>
|
||||
</div>
|
||||
<div class="conv-item" onclick="alert('Open conversation')">
|
||||
<div class="conv-subject">Question about API limits</div>
|
||||
<div class="conv-meta">Emily Davis • 1 day ago • #1244</div>
|
||||
</div>
|
||||
<div class="conv-item" onclick="alert('Open conversation')">
|
||||
<div class="conv-subject">Account upgrade request</div>
|
||||
<div class="conv-meta">Mike Johnson • 2 days ago • #1238</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
};
|
||||
@ -1,93 +0,0 @@
|
||||
export const happinessReportApp = {
|
||||
name: 'happiness-report',
|
||||
description: 'Customer satisfaction and happiness metrics',
|
||||
content: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Happiness Report</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
|
||||
.container { max-width: 1200px; margin: 0 auto; }
|
||||
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 8px; }
|
||||
.subtitle { font-size: 14px; color: #6f7b8a; margin-bottom: 24px; }
|
||||
.hero-score { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 40px; border-radius: 12px; text-align: center; margin-bottom: 24px; }
|
||||
.score-value { font-size: 72px; font-weight: bold; margin-bottom: 8px; }
|
||||
.score-label { font-size: 16px; opacity: 0.9; }
|
||||
.score-change { font-size: 14px; margin-top: 8px; }
|
||||
.score-change.positive { color: #a7f3d0; }
|
||||
.metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 24px; }
|
||||
.metric-card { background: white; padding: 24px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.metric-value { font-size: 32px; font-weight: bold; margin-bottom: 4px; }
|
||||
.metric-label { font-size: 13px; color: #6f7b8a; }
|
||||
.metric-great { color: #10b981; }
|
||||
.metric-okay { color: #f59e0b; }
|
||||
.metric-bad { color: #ef4444; }
|
||||
.chart-section { background: white; padding: 24px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 24px; }
|
||||
.chart-title { font-size: 18px; font-weight: 600; color: #1f2d3d; margin-bottom: 16px; }
|
||||
.bar-chart { display: flex; flex-direction: column; gap: 12px; }
|
||||
.bar-item { display: flex; align-items: center; gap: 12px; }
|
||||
.bar-label { width: 100px; font-size: 14px; color: #6f7b8a; }
|
||||
.bar-container { flex: 1; background: #f0f2f5; border-radius: 4px; height: 32px; position: relative; overflow: hidden; }
|
||||
.bar-fill { height: 100%; border-radius: 4px; display: flex; align-items: center; padding: 0 12px; font-size: 13px; font-weight: 600; color: white; }
|
||||
.bar-great { background: #10b981; }
|
||||
.bar-okay { background: #f59e0b; }
|
||||
.bar-bad { background: #ef4444; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>😊 Happiness Report</h1>
|
||||
<div class="subtitle">Customer satisfaction metrics for last 30 days</div>
|
||||
|
||||
<div class="hero-score">
|
||||
<div class="score-value">94%</div>
|
||||
<div class="score-label">Overall Happiness Score</div>
|
||||
<div class="score-change positive">↑ 3% from previous period</div>
|
||||
</div>
|
||||
|
||||
<div class="metrics">
|
||||
<div class="metric-card">
|
||||
<div class="metric-value metric-great">237</div>
|
||||
<div class="metric-label">😊 Great Ratings</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-value metric-okay">18</div>
|
||||
<div class="metric-label">😐 Okay Ratings</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-value metric-bad">7</div>
|
||||
<div class="metric-label">😞 Not Good Ratings</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-section">
|
||||
<div class="chart-title">Ratings Distribution</div>
|
||||
<div class="bar-chart">
|
||||
<div class="bar-item">
|
||||
<div class="bar-label">Great</div>
|
||||
<div class="bar-container">
|
||||
<div class="bar-fill bar-great" style="width: 90%;">237 (90%)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bar-item">
|
||||
<div class="bar-label">Okay</div>
|
||||
<div class="bar-container">
|
||||
<div class="bar-fill bar-okay" style="width: 7%;">18 (7%)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bar-item">
|
||||
<div class="bar-label">Not Good</div>
|
||||
<div class="bar-container">
|
||||
<div class="bar-fill bar-bad" style="width: 3%;">7 (3%)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
};
|
||||
@ -1,99 +0,0 @@
|
||||
export const mailboxOverviewApp = {
|
||||
name: 'mailbox-overview',
|
||||
description: 'Overview of all mailboxes with folder counts and activity',
|
||||
content: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Mailbox Overview</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
|
||||
.container { max-width: 1200px; margin: 0 auto; }
|
||||
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 20px; }
|
||||
.mailboxes { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; }
|
||||
.mailbox-card { background: white; border-radius: 8px; padding: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); cursor: pointer; transition: transform 0.2s; }
|
||||
.mailbox-card:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.15); }
|
||||
.mailbox-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
|
||||
.mailbox-name { font-size: 18px; font-weight: 600; color: #1f2d3d; }
|
||||
.mailbox-email { font-size: 13px; color: #6f7b8a; margin-bottom: 20px; }
|
||||
.folder-list { display: flex; flex-direction: column; gap: 12px; }
|
||||
.folder-item { display: flex; justify-content: space-between; align-items: center; padding: 8px; border-radius: 4px; background: #f7f9fc; }
|
||||
.folder-name { font-size: 14px; color: #3e4c59; }
|
||||
.folder-count { font-size: 14px; font-weight: 600; color: #3197d6; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>📮 Mailboxes</h1>
|
||||
<div class="mailboxes">
|
||||
<div class="mailbox-card" onclick="alert('Open mailbox detail')">
|
||||
<div class="mailbox-header">
|
||||
<div class="mailbox-name">Support</div>
|
||||
<div style="font-size: 24px;">💬</div>
|
||||
</div>
|
||||
<div class="mailbox-email">support@company.com</div>
|
||||
<div class="folder-list">
|
||||
<div class="folder-item">
|
||||
<span class="folder-name">📥 Unassigned</span>
|
||||
<span class="folder-count">8</span>
|
||||
</div>
|
||||
<div class="folder-item">
|
||||
<span class="folder-name">📋 My Conversations</span>
|
||||
<span class="folder-count">23</span>
|
||||
</div>
|
||||
<div class="folder-item">
|
||||
<span class="folder-name">✅ Closed</span>
|
||||
<span class="folder-count">147</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mailbox-card" onclick="alert('Open mailbox detail')">
|
||||
<div class="mailbox-header">
|
||||
<div class="mailbox-name">Sales</div>
|
||||
<div style="font-size: 24px;">💰</div>
|
||||
</div>
|
||||
<div class="mailbox-email">sales@company.com</div>
|
||||
<div class="folder-list">
|
||||
<div class="folder-item">
|
||||
<span class="folder-name">📥 Unassigned</span>
|
||||
<span class="folder-count">3</span>
|
||||
</div>
|
||||
<div class="folder-item">
|
||||
<span class="folder-name">📋 My Conversations</span>
|
||||
<span class="folder-count">12</span>
|
||||
</div>
|
||||
<div class="folder-item">
|
||||
<span class="folder-name">✅ Closed</span>
|
||||
<span class="folder-count">89</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mailbox-card" onclick="alert('Open mailbox detail')">
|
||||
<div class="mailbox-header">
|
||||
<div class="mailbox-name">Billing</div>
|
||||
<div style="font-size: 24px;">💳</div>
|
||||
</div>
|
||||
<div class="mailbox-email">billing@company.com</div>
|
||||
<div class="folder-list">
|
||||
<div class="folder-item">
|
||||
<span class="folder-name">📥 Unassigned</span>
|
||||
<span class="folder-count">2</span>
|
||||
</div>
|
||||
<div class="folder-item">
|
||||
<span class="folder-name">📋 My Conversations</span>
|
||||
<span class="folder-count">7</span>
|
||||
</div>
|
||||
<div class="folder-item">
|
||||
<span class="folder-name">✅ Closed</span>
|
||||
<span class="folder-count">64</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
};
|
||||
@ -1,105 +0,0 @@
|
||||
export const productivityReportApp = {
|
||||
name: 'productivity-report',
|
||||
description: 'Team productivity metrics and response times',
|
||||
content: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Productivity Report</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
|
||||
.container { max-width: 1200px; margin: 0 auto; }
|
||||
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 8px; }
|
||||
.subtitle { font-size: 14px; color: #6f7b8a; margin-bottom: 24px; }
|
||||
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; margin-bottom: 24px; }
|
||||
.stat-card { background: white; padding: 24px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.stat-value { font-size: 36px; font-weight: bold; color: #3197d6; margin-bottom: 4px; }
|
||||
.stat-label { font-size: 14px; color: #6f7b8a; }
|
||||
.stat-change { font-size: 12px; margin-top: 4px; }
|
||||
.stat-change.positive { color: #10b981; }
|
||||
.stat-change.negative { color: #ef4444; }
|
||||
.chart-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
||||
.chart-section { background: white; padding: 24px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.chart-title { font-size: 18px; font-weight: 600; color: #1f2d3d; margin-bottom: 16px; }
|
||||
.metric-item { display: flex; justify-content: space-between; padding: 12px 0; border-bottom: 1px solid #f0f2f5; }
|
||||
.metric-item:last-child { border-bottom: none; }
|
||||
.metric-name { font-size: 14px; color: #3e4c59; }
|
||||
.metric-val { font-size: 14px; font-weight: 600; color: #1f2d3d; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>📊 Productivity Report</h1>
|
||||
<div class="subtitle">Team performance metrics for last 30 days</div>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">1,247</div>
|
||||
<div class="stat-label">Replies Sent</div>
|
||||
<div class="stat-change positive">↑ 12% from last month</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">3.2h</div>
|
||||
<div class="stat-label">Avg First Response Time</div>
|
||||
<div class="stat-change positive">↓ 0.5h from last month</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">8.4h</div>
|
||||
<div class="stat-label">Avg Resolution Time</div>
|
||||
<div class="stat-change negative">↑ 1.2h from last month</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">892</div>
|
||||
<div class="stat-label">Resolved Conversations</div>
|
||||
<div class="stat-change positive">↑ 8% from last month</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-grid">
|
||||
<div class="chart-section">
|
||||
<div class="chart-title">Top Performers (Replies)</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-name">🥇 John Smith</span>
|
||||
<span class="metric-val">324 replies</span>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-name">🥈 Emily Davis</span>
|
||||
<span class="metric-val">287 replies</span>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-name">🥉 Mike Johnson</span>
|
||||
<span class="metric-val">219 replies</span>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-name">Sarah Wilson</span>
|
||||
<span class="metric-val">198 replies</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-section">
|
||||
<div class="chart-title">Response Time by Agent</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-name">John Smith</span>
|
||||
<span class="metric-val">2.8h</span>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-name">Emily Davis</span>
|
||||
<span class="metric-val">3.1h</span>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-name">Mike Johnson</span>
|
||||
<span class="metric-val">3.5h</span>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-name">Sarah Wilson</span>
|
||||
<span class="metric-val">4.2h</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
};
|
||||
@ -1,101 +0,0 @@
|
||||
export const savedRepliesApp = {
|
||||
name: 'saved-replies',
|
||||
description: 'Manage and browse saved reply templates',
|
||||
content: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Saved Replies</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
|
||||
.container { max-width: 1200px; margin: 0 auto; }
|
||||
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 20px; }
|
||||
.toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||
.search { flex: 1; max-width: 400px; }
|
||||
.search input { width: 100%; padding: 10px 16px; border: 1px solid #d9dee4; border-radius: 6px; font-size: 14px; }
|
||||
.btn { padding: 10px 20px; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; border: none; background: #3197d6; color: white; }
|
||||
.replies-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 16px; }
|
||||
.reply-card { background: white; border-radius: 8px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); cursor: pointer; transition: transform 0.2s; }
|
||||
.reply-card:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.15); }
|
||||
.reply-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px; }
|
||||
.reply-name { font-size: 16px; font-weight: 600; color: #1f2d3d; }
|
||||
.reply-actions { display: flex; gap: 8px; }
|
||||
.icon-btn { padding: 4px 8px; border-radius: 4px; border: 1px solid #d9dee4; background: white; cursor: pointer; font-size: 14px; }
|
||||
.icon-btn:hover { background: #f7f9fc; }
|
||||
.reply-preview { font-size: 14px; color: #6f7b8a; line-height: 1.5; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; margin-bottom: 12px; }
|
||||
.reply-meta { font-size: 12px; color: #9ca3af; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>💬 Saved Replies</h1>
|
||||
<div class="toolbar">
|
||||
<div class="search">
|
||||
<input type="text" placeholder="Search saved replies...">
|
||||
</div>
|
||||
<button class="btn">+ Create Reply</button>
|
||||
</div>
|
||||
<div class="replies-grid">
|
||||
<div class="reply-card" onclick="alert('Edit reply')">
|
||||
<div class="reply-header">
|
||||
<div class="reply-name">Welcome Message</div>
|
||||
<div class="reply-actions">
|
||||
<button class="icon-btn">✏️</button>
|
||||
<button class="icon-btn">🗑️</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="reply-preview">
|
||||
Hi there! Thanks for reaching out to our support team. We're here to help! Could you please provide more details about the issue you're experiencing?
|
||||
</div>
|
||||
<div class="reply-meta">Support mailbox • Used 47 times</div>
|
||||
</div>
|
||||
|
||||
<div class="reply-card" onclick="alert('Edit reply')">
|
||||
<div class="reply-header">
|
||||
<div class="reply-name">Billing Issue Response</div>
|
||||
<div class="reply-actions">
|
||||
<button class="icon-btn">✏️</button>
|
||||
<button class="icon-btn">🗑️</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="reply-preview">
|
||||
I understand you're experiencing a billing issue. I've escalated this to our billing team and they'll review your account within the next 24 hours. You'll receive an email update shortly.
|
||||
</div>
|
||||
<div class="reply-meta">Billing mailbox • Used 23 times</div>
|
||||
</div>
|
||||
|
||||
<div class="reply-card" onclick="alert('Edit reply')">
|
||||
<div class="reply-header">
|
||||
<div class="reply-name">Feature Request Acknowledgment</div>
|
||||
<div class="reply-actions">
|
||||
<button class="icon-btn">✏️</button>
|
||||
<button class="icon-btn">🗑️</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="reply-preview">
|
||||
Thank you for this feature suggestion! We really appreciate feedback from our users. I've passed this along to our product team for consideration in future updates.
|
||||
</div>
|
||||
<div class="reply-meta">All mailboxes • Used 34 times</div>
|
||||
</div>
|
||||
|
||||
<div class="reply-card" onclick="alert('Edit reply')">
|
||||
<div class="reply-header">
|
||||
<div class="reply-name">Account Access Help</div>
|
||||
<div class="reply-actions">
|
||||
<button class="icon-btn">✏️</button>
|
||||
<button class="icon-btn">🗑️</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="reply-preview">
|
||||
Let me help you regain access to your account. Please try resetting your password using the "Forgot Password" link on the login page. If that doesn't work, I can manually send you a reset link.
|
||||
</div>
|
||||
<div class="reply-meta">Support mailbox • Used 89 times</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
};
|
||||
@ -1,88 +0,0 @@
|
||||
export const searchResultsApp = {
|
||||
name: 'search-results',
|
||||
description: 'Search results interface for finding conversations and customers',
|
||||
content: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Search Results</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
|
||||
.container { max-width: 1000px; margin: 0 auto; }
|
||||
.search-bar { background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.search-bar input { width: 100%; padding: 12px 16px; border: 2px solid #3197d6; border-radius: 6px; font-size: 16px; }
|
||||
.results-header { font-size: 14px; color: #6f7b8a; margin-bottom: 16px; }
|
||||
.results-count { font-weight: 600; color: #1f2d3d; }
|
||||
.tabs { display: flex; gap: 8px; margin-bottom: 20px; }
|
||||
.tab { padding: 10px 20px; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; border: 1px solid #d9dee4; background: white; }
|
||||
.tab.active { background: #3197d6; color: white; border-color: #3197d6; }
|
||||
.result-item { background: white; padding: 20px; border-radius: 8px; margin-bottom: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); cursor: pointer; transition: transform 0.2s; }
|
||||
.result-item:hover { transform: translateX(4px); box-shadow: 0 2px 6px rgba(0,0,0,0.15); }
|
||||
.result-type { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; text-transform: uppercase; margin-bottom: 8px; }
|
||||
.type-conversation { background: #e0f2fe; color: #0369a1; }
|
||||
.type-customer { background: #fef3c7; color: #92400e; }
|
||||
.result-title { font-size: 16px; font-weight: 600; color: #1f2d3d; margin-bottom: 8px; }
|
||||
.result-snippet { font-size: 14px; color: #6f7b8a; line-height: 1.5; margin-bottom: 8px; }
|
||||
.highlight { background: #fef08a; padding: 2px 4px; border-radius: 2px; }
|
||||
.result-meta { font-size: 12px; color: #9ca3af; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="search-bar">
|
||||
<input type="text" placeholder="Search conversations, customers, and more..." value="payment issue">
|
||||
</div>
|
||||
|
||||
<div class="results-header">
|
||||
Found <span class="results-count">12 results</span> for "payment issue"
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
<button class="tab active">All (12)</button>
|
||||
<button class="tab">Conversations (8)</button>
|
||||
<button class="tab">Customers (3)</button>
|
||||
<button class="tab">Articles (1)</button>
|
||||
</div>
|
||||
|
||||
<div class="result-item" onclick="alert('Open conversation')">
|
||||
<span class="result-type type-conversation">Conversation #1247</span>
|
||||
<div class="result-title">Payment issue with subscription</div>
|
||||
<div class="result-snippet">
|
||||
Customer having trouble with their credit card. Error appears when trying to update <span class="highlight">payment</span> method. The <span class="highlight">issue</span> started yesterday...
|
||||
</div>
|
||||
<div class="result-meta">Sarah Chen • Active • 2 hours ago</div>
|
||||
</div>
|
||||
|
||||
<div class="result-item" onclick="alert('Open conversation')">
|
||||
<span class="result-type type-conversation">Conversation #1189</span>
|
||||
<div class="result-title">Billing problem - double charge</div>
|
||||
<div class="result-snippet">
|
||||
I was charged twice for my subscription this month. Can you help resolve this <span class="highlight">payment issue</span>?
|
||||
</div>
|
||||
<div class="result-meta">Mike Johnson • Closed • 3 days ago</div>
|
||||
</div>
|
||||
|
||||
<div class="result-item" onclick="alert('Open customer')">
|
||||
<span class="result-type type-customer">Customer</span>
|
||||
<div class="result-title">Sarah Chen</div>
|
||||
<div class="result-snippet">
|
||||
Background: VIP customer, frequently reports <span class="highlight">payment issues</span>. Works at Acme Corp as Product Manager.
|
||||
</div>
|
||||
<div class="result-meta">sarah.chen@example.com • 12 conversations</div>
|
||||
</div>
|
||||
|
||||
<div class="result-item" onclick="alert('Open conversation')">
|
||||
<span class="result-type type-conversation">Conversation #1156</span>
|
||||
<div class="result-title">Can't complete checkout</div>
|
||||
<div class="result-snippet">
|
||||
Checkout process fails at the <span class="highlight">payment</span> step. Tried multiple cards, same <span class="highlight">issue</span>.
|
||||
</div>
|
||||
<div class="result-meta">Alex Brown • Closed • 1 week ago</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
};
|
||||
@ -1,97 +0,0 @@
|
||||
export const tagManagerApp = {
|
||||
name: 'tag-manager',
|
||||
description: 'Manage tags with usage stats and color coding',
|
||||
content: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Tag Manager</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
|
||||
.container { max-width: 1000px; margin: 0 auto; }
|
||||
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 20px; }
|
||||
.toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||
.btn { padding: 10px 20px; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; border: none; background: #3197d6; color: white; }
|
||||
.tags-grid { background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.grid-header { display: grid; grid-template-columns: 1fr 100px 150px 120px; padding: 12px 16px; background: #f7f9fc; border-bottom: 1px solid #d9dee4; font-size: 12px; font-weight: 600; color: #6f7b8a; }
|
||||
.grid-row { display: grid; grid-template-columns: 1fr 100px 150px 120px; padding: 16px; border-bottom: 1px solid #f0f2f5; align-items: center; }
|
||||
.tag-display { display: inline-flex; align-items: center; gap: 8px; }
|
||||
.tag-color { width: 16px; height: 16px; border-radius: 3px; }
|
||||
.tag-name { font-size: 14px; font-weight: 500; color: #1f2d3d; }
|
||||
.tag-count { font-size: 14px; color: #6f7b8a; text-align: center; }
|
||||
.tag-created { font-size: 13px; color: #6f7b8a; }
|
||||
.tag-actions { display: flex; gap: 8px; justify-content: flex-end; }
|
||||
.action-btn { padding: 6px 12px; font-size: 12px; border-radius: 4px; cursor: pointer; border: 1px solid #d9dee4; background: white; }
|
||||
.action-btn:hover { background: #f7f9fc; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🏷️ Tag Manager</h1>
|
||||
<div class="toolbar">
|
||||
<div style="font-size: 14px; color: #6f7b8a;">Managing 12 tags</div>
|
||||
<button class="btn">+ Create Tag</button>
|
||||
</div>
|
||||
<div class="tags-grid">
|
||||
<div class="grid-header">
|
||||
<div>TAG NAME</div>
|
||||
<div style="text-align: center;">USAGE</div>
|
||||
<div>CREATED</div>
|
||||
<div style="text-align: right;">ACTIONS</div>
|
||||
</div>
|
||||
<div class="grid-row">
|
||||
<div class="tag-display">
|
||||
<div class="tag-color" style="background: #ef4444;"></div>
|
||||
<span class="tag-name">urgent</span>
|
||||
</div>
|
||||
<div class="tag-count">23 conversations</div>
|
||||
<div class="tag-created">Jan 2024</div>
|
||||
<div class="tag-actions">
|
||||
<button class="action-btn">Edit</button>
|
||||
<button class="action-btn" style="color: #ef4444;">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-row">
|
||||
<div class="tag-display">
|
||||
<div class="tag-color" style="background: #3b82f6;"></div>
|
||||
<span class="tag-name">billing</span>
|
||||
</div>
|
||||
<div class="tag-count">47 conversations</div>
|
||||
<div class="tag-created">Dec 2023</div>
|
||||
<div class="tag-actions">
|
||||
<button class="action-btn">Edit</button>
|
||||
<button class="action-btn" style="color: #ef4444;">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-row">
|
||||
<div class="tag-display">
|
||||
<div class="tag-color" style="background: #10b981;"></div>
|
||||
<span class="tag-name">feature-request</span>
|
||||
</div>
|
||||
<div class="tag-count">31 conversations</div>
|
||||
<div class="tag-created">Nov 2023</div>
|
||||
<div class="tag-actions">
|
||||
<button class="action-btn">Edit</button>
|
||||
<button class="action-btn" style="color: #ef4444;">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-row">
|
||||
<div class="tag-display">
|
||||
<div class="tag-color" style="background: #f59e0b;"></div>
|
||||
<span class="tag-name">bug</span>
|
||||
</div>
|
||||
<div class="tag-count">18 conversations</div>
|
||||
<div class="tag-created">Oct 2023</div>
|
||||
<div class="tag-actions">
|
||||
<button class="action-btn">Edit</button>
|
||||
<button class="action-btn" style="color: #ef4444;">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
};
|
||||
@ -1,104 +0,0 @@
|
||||
export const teamOverviewApp = {
|
||||
name: 'team-overview',
|
||||
description: 'Team structure and member statistics',
|
||||
content: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Teams</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
|
||||
.container { max-width: 1200px; margin: 0 auto; }
|
||||
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 20px; }
|
||||
.teams { display: grid; gap: 20px; }
|
||||
.team-card { background: white; border-radius: 8px; padding: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.team-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||
.team-name { font-size: 20px; font-weight: 600; color: #1f2d3d; }
|
||||
.team-count { font-size: 14px; color: #6f7b8a; }
|
||||
.members-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 16px; }
|
||||
.member-card { padding: 16px; border: 1px solid #e5e7eb; border-radius: 6px; display: flex; align-items: center; gap: 12px; }
|
||||
.avatar { width: 48px; height: 48px; border-radius: 50%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; color: white; font-weight: 600; font-size: 16px; }
|
||||
.member-info { flex: 1; }
|
||||
.member-name { font-size: 14px; font-weight: 500; color: #1f2d3d; margin-bottom: 2px; }
|
||||
.member-email { font-size: 12px; color: #6f7b8a; }
|
||||
.member-role { font-size: 11px; color: #9ca3af; margin-top: 2px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>👥 Teams</h1>
|
||||
|
||||
<div class="teams">
|
||||
<div class="team-card">
|
||||
<div class="team-header">
|
||||
<div class="team-name">Support Team</div>
|
||||
<div class="team-count">6 members</div>
|
||||
</div>
|
||||
<div class="members-grid">
|
||||
<div class="member-card">
|
||||
<div class="avatar" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">JS</div>
|
||||
<div class="member-info">
|
||||
<div class="member-name">John Smith</div>
|
||||
<div class="member-email">john@company.com</div>
|
||||
<div class="member-role">Team Lead</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="member-card">
|
||||
<div class="avatar" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">ED</div>
|
||||
<div class="member-info">
|
||||
<div class="member-name">Emily Davis</div>
|
||||
<div class="member-email">emily@company.com</div>
|
||||
<div class="member-role">Senior Agent</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="member-card">
|
||||
<div class="avatar" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">MJ</div>
|
||||
<div class="member-info">
|
||||
<div class="member-name">Mike Johnson</div>
|
||||
<div class="member-email">mike@company.com</div>
|
||||
<div class="member-role">Agent</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="member-card">
|
||||
<div class="avatar" style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);">SW</div>
|
||||
<div class="member-info">
|
||||
<div class="member-name">Sarah Wilson</div>
|
||||
<div class="member-email">sarah@company.com</div>
|
||||
<div class="member-role">Agent</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="team-card">
|
||||
<div class="team-header">
|
||||
<div class="team-name">Sales Team</div>
|
||||
<div class="team-count">4 members</div>
|
||||
</div>
|
||||
<div class="members-grid">
|
||||
<div class="member-card">
|
||||
<div class="avatar" style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);">AB</div>
|
||||
<div class="member-info">
|
||||
<div class="member-name">Alex Brown</div>
|
||||
<div class="member-email">alex@company.com</div>
|
||||
<div class="member-role">Sales Lead</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="member-card">
|
||||
<div class="avatar" style="background: linear-gradient(135deg, #30cfd0 0%, #330867 100%);">LG</div>
|
||||
<div class="member-info">
|
||||
<div class="member-name">Lisa Garcia</div>
|
||||
<div class="member-email">lisa@company.com</div>
|
||||
<div class="member-role">Sales Rep</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
};
|
||||
@ -1,86 +0,0 @@
|
||||
export const userStatsApp = {
|
||||
name: 'user-stats',
|
||||
description: 'Individual user performance statistics and metrics',
|
||||
content: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>User Statistics</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
|
||||
.container { max-width: 1000px; margin: 0 auto; }
|
||||
.profile { background: white; border-radius: 8px; padding: 32px; text-align: center; margin-bottom: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.avatar { width: 100px; height: 100px; border-radius: 50%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 40px; font-weight: bold; margin: 0 auto 16px; }
|
||||
.user-name { font-size: 24px; font-weight: 600; color: #1f2d3d; margin-bottom: 4px; }
|
||||
.user-role { font-size: 14px; color: #6f7b8a; margin-bottom: 4px; }
|
||||
.user-email { font-size: 13px; color: #9ca3af; }
|
||||
.stats-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 20px; }
|
||||
.stat-card { background: white; padding: 24px; border-radius: 8px; text-align: center; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.stat-value { font-size: 36px; font-weight: bold; color: #3197d6; margin-bottom: 8px; }
|
||||
.stat-label { font-size: 13px; color: #6f7b8a; }
|
||||
.metrics-section { background: white; border-radius: 8px; padding: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.metrics-title { font-size: 18px; font-weight: 600; color: #1f2d3d; margin-bottom: 16px; }
|
||||
.metric-row { display: flex; justify-content: space-between; padding: 12px 0; border-bottom: 1px solid #f0f2f5; }
|
||||
.metric-row:last-child { border-bottom: none; }
|
||||
.metric-label { font-size: 14px; color: #6f7b8a; }
|
||||
.metric-value { font-size: 14px; font-weight: 600; color: #1f2d3d; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="profile">
|
||||
<div class="avatar">JS</div>
|
||||
<div class="user-name">John Smith</div>
|
||||
<div class="user-role">Support Team Lead</div>
|
||||
<div class="user-email">john.smith@company.com</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">324</div>
|
||||
<div class="stat-label">Replies Sent (30d)</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">97%</div>
|
||||
<div class="stat-label">Happiness Score</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">2.8h</div>
|
||||
<div class="stat-label">Avg Response Time</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metrics-section">
|
||||
<div class="metrics-title">Performance Metrics (Last 30 Days)</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">Conversations Created</span>
|
||||
<span class="metric-value">89</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">Conversations Closed</span>
|
||||
<span class="metric-value">147</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">Average Resolution Time</span>
|
||||
<span class="metric-value">7.2 hours</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">First Response Time</span>
|
||||
<span class="metric-value">2.8 hours</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">Active Conversations</span>
|
||||
<span class="metric-value">23</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">Customer Ratings</span>
|
||||
<span class="metric-value">72 ratings (97% positive)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
};
|
||||
@ -1,128 +0,0 @@
|
||||
export const workflowDashboardApp = {
|
||||
name: 'workflow-dashboard',
|
||||
description: 'Dashboard for workflows with activation status and stats',
|
||||
content: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Workflows</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
|
||||
.container { max-width: 1200px; margin: 0 auto; }
|
||||
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 20px; }
|
||||
.workflows { background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.workflow-item { padding: 20px; border-bottom: 1px solid #f0f2f5; display: flex; justify-content: between; align-items: center; gap: 16px; }
|
||||
.workflow-info { flex: 1; }
|
||||
.workflow-name { font-size: 16px; font-weight: 600; color: #1f2d3d; margin-bottom: 4px; }
|
||||
.workflow-meta { font-size: 13px; color: #6f7b8a; }
|
||||
.workflow-type { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: 500; background: #e0e7ff; color: #3730a3; margin-right: 8px; }
|
||||
.workflow-stats { display: flex; gap: 24px; align-items: center; }
|
||||
.stat { text-align: center; }
|
||||
.stat-value { font-size: 20px; font-weight: 600; color: #1f2d3d; }
|
||||
.stat-label { font-size: 11px; color: #6f7b8a; text-transform: uppercase; }
|
||||
.toggle { width: 50px; height: 26px; background: #d1d5db; border-radius: 13px; position: relative; cursor: pointer; transition: background 0.3s; }
|
||||
.toggle.active { background: #10b981; }
|
||||
.toggle-knob { width: 22px; height: 22px; background: white; border-radius: 50%; position: absolute; top: 2px; left: 2px; transition: left 0.3s; box-shadow: 0 1px 3px rgba(0,0,0,0.3); }
|
||||
.toggle.active .toggle-knob { left: 26px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>⚡ Workflows</h1>
|
||||
<div class="workflows">
|
||||
<div class="workflow-item">
|
||||
<div class="workflow-info">
|
||||
<div class="workflow-name">
|
||||
<span class="workflow-type">Automatic</span>
|
||||
Auto-tag billing conversations
|
||||
</div>
|
||||
<div class="workflow-meta">Support mailbox • Modified 2 weeks ago</div>
|
||||
</div>
|
||||
<div class="workflow-stats">
|
||||
<div class="stat">
|
||||
<div class="stat-value">247</div>
|
||||
<div class="stat-label">Total Runs</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-value">245</div>
|
||||
<div class="stat-label">Successful</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toggle active" onclick="this.classList.toggle('active')">
|
||||
<div class="toggle-knob"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workflow-item">
|
||||
<div class="workflow-info">
|
||||
<div class="workflow-name">
|
||||
<span class="workflow-type" style="background: #fef3c7; color: #92400e;">Manual</span>
|
||||
Escalate to senior support
|
||||
</div>
|
||||
<div class="workflow-meta">Support mailbox • Modified 1 month ago</div>
|
||||
</div>
|
||||
<div class="workflow-stats">
|
||||
<div class="stat">
|
||||
<div class="stat-value">34</div>
|
||||
<div class="stat-label">Total Runs</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-value">34</div>
|
||||
<div class="stat-label">Successful</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toggle active" onclick="this.classList.toggle('active')">
|
||||
<div class="toggle-knob"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workflow-item">
|
||||
<div class="workflow-info">
|
||||
<div class="workflow-name">
|
||||
<span class="workflow-type">Automatic</span>
|
||||
Close spam conversations
|
||||
</div>
|
||||
<div class="workflow-meta">All mailboxes • Modified 3 days ago</div>
|
||||
</div>
|
||||
<div class="workflow-stats">
|
||||
<div class="stat">
|
||||
<div class="stat-value">89</div>
|
||||
<div class="stat-label">Total Runs</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-value">89</div>
|
||||
<div class="stat-label">Successful</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toggle active" onclick="this.classList.toggle('active')">
|
||||
<div class="toggle-knob"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workflow-item">
|
||||
<div class="workflow-info">
|
||||
<div class="workflow-name">
|
||||
<span class="workflow-type">Automatic</span>
|
||||
Send satisfaction survey
|
||||
</div>
|
||||
<div class="workflow-meta">Support mailbox • Modified 1 week ago</div>
|
||||
</div>
|
||||
<div class="workflow-stats">
|
||||
<div class="stat">
|
||||
<div class="stat-value">156</div>
|
||||
<div class="stat-label">Total Runs</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-value">154</div>
|
||||
<div class="stat-label">Successful</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toggle" onclick="this.classList.toggle('active')">
|
||||
<div class="toggle-knob"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
};
|
||||
@ -1,118 +0,0 @@
|
||||
export const workflowDetailApp = {
|
||||
name: 'workflow-detail',
|
||||
description: 'Detailed view of workflow configuration and execution history',
|
||||
content: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Workflow Detail</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
|
||||
.container { max-width: 1000px; margin: 0 auto; }
|
||||
.header { background: white; padding: 24px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); display: flex; justify-content: space-between; align-items: center; }
|
||||
.workflow-info { flex: 1; }
|
||||
.workflow-name { font-size: 24px; font-weight: 600; color: #1f2d3d; margin-bottom: 8px; }
|
||||
.workflow-meta { font-size: 14px; color: #6f7b8a; }
|
||||
.workflow-type { display: inline-block; padding: 4px 10px; border-radius: 4px; font-size: 12px; font-weight: 500; background: #e0e7ff; color: #3730a3; margin-right: 8px; }
|
||||
.toggle { width: 50px; height: 26px; background: #10b981; border-radius: 13px; position: relative; cursor: pointer; }
|
||||
.toggle-knob { width: 22px; height: 22px; background: white; border-radius: 50%; position: absolute; top: 2px; left: 26px; box-shadow: 0 1px 3px rgba(0,0,0,0.3); }
|
||||
.stats-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 20px; }
|
||||
.stat-card { background: white; padding: 20px; border-radius: 8px; text-align: center; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.stat-value { font-size: 32px; font-weight: bold; color: #3197d6; margin-bottom: 4px; }
|
||||
.stat-label { font-size: 13px; color: #6f7b8a; }
|
||||
.section { background: white; padding: 24px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.section-title { font-size: 18px; font-weight: 600; color: #1f2d3d; margin-bottom: 16px; }
|
||||
.condition-item { padding: 16px; background: #f7f9fc; border-radius: 6px; margin-bottom: 12px; border-left: 4px solid #3197d6; }
|
||||
.condition-label { font-size: 12px; font-weight: 600; color: #6f7b8a; margin-bottom: 4px; text-transform: uppercase; }
|
||||
.condition-value { font-size: 14px; color: #1f2d3d; }
|
||||
.action-item { padding: 16px; background: #f0fdf4; border-radius: 6px; margin-bottom: 12px; border-left: 4px solid #10b981; }
|
||||
.action-label { font-size: 12px; font-weight: 600; color: #166534; margin-bottom: 4px; text-transform: uppercase; }
|
||||
.action-value { font-size: 14px; color: #166534; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<div class="workflow-info">
|
||||
<div class="workflow-name">
|
||||
<span class="workflow-type">Automatic</span>
|
||||
Auto-tag billing conversations
|
||||
</div>
|
||||
<div class="workflow-meta">Support mailbox • Modified 2 weeks ago</div>
|
||||
</div>
|
||||
<div class="toggle">
|
||||
<div class="toggle-knob"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">247</div>
|
||||
<div class="stat-label">Total Runs</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">245</div>
|
||||
<div class="stat-label">Successful</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">2</div>
|
||||
<div class="stat-label">Failed</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">⚙️ Conditions (When to run)</div>
|
||||
<div class="condition-item">
|
||||
<div class="condition-label">Trigger</div>
|
||||
<div class="condition-value">Conversation is created</div>
|
||||
</div>
|
||||
<div class="condition-item">
|
||||
<div class="condition-label">Subject contains</div>
|
||||
<div class="condition-value">payment, billing, invoice, subscription</div>
|
||||
</div>
|
||||
<div class="condition-item">
|
||||
<div class="condition-label">Mailbox</div>
|
||||
<div class="condition-value">Support</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">🎯 Actions (What to do)</div>
|
||||
<div class="action-item">
|
||||
<div class="action-label">Add Tags</div>
|
||||
<div class="action-value">billing</div>
|
||||
</div>
|
||||
<div class="action-item">
|
||||
<div class="action-label">Assign To</div>
|
||||
<div class="action-value">Billing Team</div>
|
||||
</div>
|
||||
<div class="action-item">
|
||||
<div class="action-label">Set Priority</div>
|
||||
<div class="action-value">High</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">📊 Recent Executions</div>
|
||||
<div style="padding: 16px; border: 1px solid #e5e7eb; border-radius: 6px; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<div style="font-weight: 500; color: #1f2d3d;">Conversation #1247: Payment issue</div>
|
||||
<div style="font-size: 13px; color: #6f7b8a;">2 hours ago</div>
|
||||
</div>
|
||||
<div style="color: #10b981; font-weight: 600;">✓ Success</div>
|
||||
</div>
|
||||
<div style="padding: 16px; border: 1px solid #e5e7eb; border-radius: 6px; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<div style="font-weight: 500; color: #1f2d3d;">Conversation #1189: Billing problem</div>
|
||||
<div style="font-size: 13px; color: #6f7b8a;">3 days ago</div>
|
||||
</div>
|
||||
<div style="color: #10b981; font-weight: 600;">✓ Success</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
};
|
||||
333
src/index.ts
Normal file
333
src/index.ts
Normal file
@ -0,0 +1,333 @@
|
||||
#!/usr/bin/env node
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
|
||||
// ============================================
|
||||
// CONFIGURATION
|
||||
// ============================================
|
||||
const MCP_NAME = "helpscout";
|
||||
const MCP_VERSION = "1.0.0";
|
||||
const API_BASE_URL = "https://api.helpscout.net/v2";
|
||||
|
||||
// ============================================
|
||||
// API CLIENT (OAuth 2.0)
|
||||
// ============================================
|
||||
class HelpScoutClient {
|
||||
private accessToken: string;
|
||||
private baseUrl: string;
|
||||
|
||||
constructor(accessToken: string) {
|
||||
this.accessToken = accessToken;
|
||||
this.baseUrl = API_BASE_URL;
|
||||
}
|
||||
|
||||
async request(endpoint: string, options: RequestInit = {}) {
|
||||
const url = `${this.baseUrl}${endpoint}`;
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
"Authorization": `Bearer ${this.accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`Help Scout API error: ${response.status} - ${error}`);
|
||||
}
|
||||
|
||||
// Some endpoints return 201/204 with no body
|
||||
const text = await response.text();
|
||||
return text ? JSON.parse(text) : { success: true };
|
||||
}
|
||||
|
||||
async get(endpoint: string, params: Record<string, any> = {}) {
|
||||
const url = new URL(`${this.baseUrl}${endpoint}`);
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined && value !== null) {
|
||||
url.searchParams.set(key, String(value));
|
||||
}
|
||||
}
|
||||
return this.request(url.pathname + url.search, { method: "GET" });
|
||||
}
|
||||
|
||||
async post(endpoint: string, data: any) {
|
||||
return this.request(endpoint, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// TOOL DEFINITIONS
|
||||
// ============================================
|
||||
const tools = [
|
||||
{
|
||||
name: "list_conversations",
|
||||
description: "List conversations (tickets) from Help Scout. Returns paginated list with embedded conversation data.",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
mailbox: { type: "number", description: "Filter by mailbox ID" },
|
||||
status: {
|
||||
type: "string",
|
||||
description: "Filter by status",
|
||||
enum: ["active", "open", "closed", "pending", "spam"]
|
||||
},
|
||||
tag: { type: "string", description: "Filter by tag" },
|
||||
assigned_to: { type: "number", description: "Filter by assigned user ID" },
|
||||
folder: { type: "number", description: "Filter by folder ID" },
|
||||
page: { type: "number", description: "Page number (default 1)" },
|
||||
sortField: { type: "string", description: "Sort field (createdAt, modifiedAt, number)" },
|
||||
sortOrder: { type: "string", enum: ["asc", "desc"], description: "Sort order" },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get_conversation",
|
||||
description: "Get a specific conversation by ID with full thread details",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
id: { type: "number", description: "Conversation ID" },
|
||||
},
|
||||
required: ["id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create_conversation",
|
||||
description: "Create a new conversation (ticket) in Help Scout",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
mailboxId: { type: "number", description: "Mailbox ID (required)" },
|
||||
subject: { type: "string", description: "Conversation subject (required)" },
|
||||
customer: {
|
||||
type: "object",
|
||||
description: "Customer object with email (required): {email: 'customer@example.com'}",
|
||||
},
|
||||
type: {
|
||||
type: "string",
|
||||
enum: ["email", "phone", "chat"],
|
||||
description: "Conversation type (default: email)"
|
||||
},
|
||||
status: {
|
||||
type: "string",
|
||||
enum: ["active", "closed", "pending"],
|
||||
description: "Initial status (default: active)"
|
||||
},
|
||||
threads: {
|
||||
type: "array",
|
||||
description: "Initial threads [{type: 'customer', text: 'message content'}]",
|
||||
items: { type: "object" }
|
||||
},
|
||||
tags: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description: "Tags to apply"
|
||||
},
|
||||
assignTo: { type: "number", description: "User ID to assign to" },
|
||||
},
|
||||
required: ["mailboxId", "subject", "customer"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reply_conversation",
|
||||
description: "Reply to an existing conversation",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
conversationId: { type: "number", description: "Conversation ID to reply to (required)" },
|
||||
text: { type: "string", description: "Reply text/HTML content (required)" },
|
||||
user: { type: "number", description: "User ID sending reply (required for agent replies)" },
|
||||
customer: {
|
||||
type: "object",
|
||||
description: "Customer object for customer replies: {email: 'customer@example.com'}"
|
||||
},
|
||||
type: {
|
||||
type: "string",
|
||||
enum: ["reply", "note"],
|
||||
description: "Thread type (reply=visible to customer, note=internal)"
|
||||
},
|
||||
status: {
|
||||
type: "string",
|
||||
enum: ["active", "closed", "pending"],
|
||||
description: "Set conversation status after reply"
|
||||
},
|
||||
draft: { type: "boolean", description: "Save as draft" },
|
||||
cc: { type: "array", items: { type: "string" }, description: "CC email addresses" },
|
||||
bcc: { type: "array", items: { type: "string" }, description: "BCC email addresses" },
|
||||
},
|
||||
required: ["conversationId", "text"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list_customers",
|
||||
description: "List customers from Help Scout",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
email: { type: "string", description: "Filter by email address" },
|
||||
firstName: { type: "string", description: "Filter by first name" },
|
||||
lastName: { type: "string", description: "Filter by last name" },
|
||||
query: { type: "string", description: "Search query" },
|
||||
page: { type: "number", description: "Page number" },
|
||||
sortField: { type: "string", description: "Sort field (firstName, lastName, modifiedAt)" },
|
||||
sortOrder: { type: "string", enum: ["asc", "desc"], description: "Sort order" },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list_mailboxes",
|
||||
description: "List all mailboxes accessible to the authenticated user",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
page: { type: "number", description: "Page number (default 1)" },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "search",
|
||||
description: "Search conversations using Help Scout's search syntax",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
query: {
|
||||
type: "string",
|
||||
description: "Search query (required). Supports: subject:, customer:, status:, tag:, mailbox:, etc."
|
||||
},
|
||||
page: { type: "number", description: "Page number" },
|
||||
sortField: { type: "string", description: "Sort field" },
|
||||
sortOrder: { type: "string", enum: ["asc", "desc"], description: "Sort order" },
|
||||
},
|
||||
required: ["query"],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// ============================================
|
||||
// TOOL HANDLERS
|
||||
// ============================================
|
||||
async function handleTool(client: HelpScoutClient, name: string, args: any) {
|
||||
switch (name) {
|
||||
case "list_conversations": {
|
||||
const params: Record<string, any> = {};
|
||||
if (args.mailbox) params.mailbox = args.mailbox;
|
||||
if (args.status) params.status = args.status;
|
||||
if (args.tag) params.tag = args.tag;
|
||||
if (args.assigned_to) params["assigned_to"] = args.assigned_to;
|
||||
if (args.folder) params.folder = args.folder;
|
||||
if (args.page) params.page = args.page;
|
||||
if (args.sortField) params.sortField = args.sortField;
|
||||
if (args.sortOrder) params.sortOrder = args.sortOrder;
|
||||
return await client.get("/conversations", params);
|
||||
}
|
||||
case "get_conversation": {
|
||||
const { id } = args;
|
||||
return await client.get(`/conversations/${id}`);
|
||||
}
|
||||
case "create_conversation": {
|
||||
const payload: any = {
|
||||
mailboxId: args.mailboxId,
|
||||
subject: args.subject,
|
||||
customer: args.customer,
|
||||
type: args.type || "email",
|
||||
status: args.status || "active",
|
||||
};
|
||||
if (args.threads) payload.threads = args.threads;
|
||||
if (args.tags) payload.tags = args.tags;
|
||||
if (args.assignTo) payload.assignTo = args.assignTo;
|
||||
return await client.post("/conversations", payload);
|
||||
}
|
||||
case "reply_conversation": {
|
||||
const { conversationId, ...threadData } = args;
|
||||
const payload: any = {
|
||||
text: threadData.text,
|
||||
type: threadData.type || "reply",
|
||||
};
|
||||
if (threadData.user) payload.user = threadData.user;
|
||||
if (threadData.customer) payload.customer = threadData.customer;
|
||||
if (threadData.status) payload.status = threadData.status;
|
||||
if (threadData.draft) payload.draft = threadData.draft;
|
||||
if (threadData.cc) payload.cc = threadData.cc;
|
||||
if (threadData.bcc) payload.bcc = threadData.bcc;
|
||||
return await client.post(`/conversations/${conversationId}/reply`, payload);
|
||||
}
|
||||
case "list_customers": {
|
||||
const params: Record<string, any> = {};
|
||||
if (args.email) params.email = args.email;
|
||||
if (args.firstName) params.firstName = args.firstName;
|
||||
if (args.lastName) params.lastName = args.lastName;
|
||||
if (args.query) params.query = args.query;
|
||||
if (args.page) params.page = args.page;
|
||||
if (args.sortField) params.sortField = args.sortField;
|
||||
if (args.sortOrder) params.sortOrder = args.sortOrder;
|
||||
return await client.get("/customers", params);
|
||||
}
|
||||
case "list_mailboxes": {
|
||||
return await client.get("/mailboxes", { page: args.page });
|
||||
}
|
||||
case "search": {
|
||||
const params: Record<string, any> = { query: args.query };
|
||||
if (args.page) params.page = args.page;
|
||||
if (args.sortField) params.sortField = args.sortField;
|
||||
if (args.sortOrder) params.sortOrder = args.sortOrder;
|
||||
return await client.get("/conversations/search", params);
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// SERVER SETUP
|
||||
// ============================================
|
||||
async function main() {
|
||||
const accessToken = process.env.HELPSCOUT_ACCESS_TOKEN;
|
||||
if (!accessToken) {
|
||||
console.error("Error: HELPSCOUT_ACCESS_TOKEN environment variable required");
|
||||
console.error("Obtain via OAuth 2.0 flow at https://developer.helpscout.com/mailbox-api/overview/authentication/");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const client = new HelpScoutClient(accessToken);
|
||||
|
||||
const server = new Server(
|
||||
{ name: `${MCP_NAME}-mcp`, version: MCP_VERSION },
|
||||
{ capabilities: { tools: {} } }
|
||||
);
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
||||
tools,
|
||||
}));
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
const { name, arguments: args } = request.params;
|
||||
|
||||
try {
|
||||
const result = await handleTool(client, name, args || {});
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
||||
};
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
content: [{ type: "text", text: `Error: ${message}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error(`${MCP_NAME} MCP server running on stdio`);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
11
src/main.ts
11
src/main.ts
@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import { runServer } from './server.js';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
// Load environment variables from .env file if it exists
|
||||
dotenv.config();
|
||||
|
||||
runServer().catch((error) => {
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
186
src/server.ts
186
src/server.ts
@ -1,186 +0,0 @@
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
import {
|
||||
ListResourcesRequestSchema,
|
||||
ReadResourceRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
CallToolRequestSchema,
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
import { HelpScoutClient } from './api/client.js';
|
||||
import { registerConversationTools } from './tools/conversations-tools.js';
|
||||
import { registerCustomerTools } from './tools/customers-tools.js';
|
||||
import { registerMailboxTools } from './tools/mailboxes-tools.js';
|
||||
import { registerUserTools } from './tools/users-tools.js';
|
||||
import { registerTagTools } from './tools/tags-tools.js';
|
||||
import { registerWorkflowTools } from './tools/workflows-tools.js';
|
||||
import { registerSavedReplyTools } from './tools/saved-replies-tools.js';
|
||||
import { registerTeamTools } from './tools/teams-tools.js';
|
||||
import { registerWebhookTools } from './tools/webhooks-tools.js';
|
||||
import { registerReportingTools } from './tools/reporting-tools.js';
|
||||
|
||||
// Import MCP apps
|
||||
import { conversationDashboardApp } from './apps/conversation-dashboard.js';
|
||||
import { conversationDetailApp } from './apps/conversation-detail.js';
|
||||
import { conversationGridApp } from './apps/conversation-grid.js';
|
||||
import { conversationTimelineApp } from './apps/conversation-timeline.js';
|
||||
import { customerGridApp } from './apps/customer-grid.js';
|
||||
import { customerDetailApp } from './apps/customer-detail.js';
|
||||
import { mailboxOverviewApp } from './apps/mailbox-overview.js';
|
||||
import { folderBrowserApp } from './apps/folder-browser.js';
|
||||
import { userStatsApp } from './apps/user-stats.js';
|
||||
import { tagManagerApp } from './apps/tag-manager.js';
|
||||
import { workflowDashboardApp } from './apps/workflow-dashboard.js';
|
||||
import { workflowDetailApp } from './apps/workflow-detail.js';
|
||||
import { savedRepliesApp } from './apps/saved-replies.js';
|
||||
import { teamOverviewApp } from './apps/team-overview.js';
|
||||
import { happinessReportApp } from './apps/happiness-report.js';
|
||||
import { productivityReportApp } from './apps/productivity-report.js';
|
||||
import { companyReportApp } from './apps/company-report.js';
|
||||
import { searchResultsApp } from './apps/search-results.js';
|
||||
|
||||
export async function runServer() {
|
||||
const appId = process.env.HELPSCOUT_APP_ID;
|
||||
const appSecret = process.env.HELPSCOUT_APP_SECRET;
|
||||
|
||||
if (!appId || !appSecret) {
|
||||
throw new Error(
|
||||
'HELPSCOUT_APP_ID and HELPSCOUT_APP_SECRET environment variables are required'
|
||||
);
|
||||
}
|
||||
|
||||
const client = new HelpScoutClient({ appId, appSecret });
|
||||
|
||||
const server = new Server(
|
||||
{
|
||||
name: 'helpscout-server',
|
||||
version: '1.0.0',
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
resources: {},
|
||||
tools: {},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Register all tools
|
||||
const allTools = [
|
||||
...registerConversationTools(client),
|
||||
...registerCustomerTools(client),
|
||||
...registerMailboxTools(client),
|
||||
...registerUserTools(client),
|
||||
...registerTagTools(client),
|
||||
...registerWorkflowTools(client),
|
||||
...registerSavedReplyTools(client),
|
||||
...registerTeamTools(client),
|
||||
...registerWebhookTools(client),
|
||||
...registerReportingTools(client),
|
||||
];
|
||||
|
||||
// Register all MCP apps as resources
|
||||
const allApps = [
|
||||
conversationDashboardApp,
|
||||
conversationDetailApp,
|
||||
conversationGridApp,
|
||||
conversationTimelineApp,
|
||||
customerGridApp,
|
||||
customerDetailApp,
|
||||
mailboxOverviewApp,
|
||||
folderBrowserApp,
|
||||
userStatsApp,
|
||||
tagManagerApp,
|
||||
workflowDashboardApp,
|
||||
workflowDetailApp,
|
||||
savedRepliesApp,
|
||||
teamOverviewApp,
|
||||
happinessReportApp,
|
||||
productivityReportApp,
|
||||
companyReportApp,
|
||||
searchResultsApp,
|
||||
];
|
||||
|
||||
// Handle list_resources
|
||||
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
||||
return {
|
||||
resources: allApps.map((app) => ({
|
||||
uri: `helpscout://app/${app.name}`,
|
||||
name: app.name,
|
||||
description: app.description,
|
||||
mimeType: 'text/html',
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
// Handle read_resource
|
||||
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
||||
const uri = request.params.uri;
|
||||
const appName = uri.replace('helpscout://app/', '');
|
||||
const app = allApps.find((a) => a.name === appName);
|
||||
|
||||
if (!app) {
|
||||
throw new Error(`App not found: ${appName}`);
|
||||
}
|
||||
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri,
|
||||
mimeType: 'text/html',
|
||||
text: app.content,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
// Handle list_tools
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return {
|
||||
tools: allTools.map((tool) => ({
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
inputSchema: tool.inputSchema,
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
// Handle call_tool
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
const { name, arguments: args } = request.params;
|
||||
const tool = allTools.find((t) => t.name === name);
|
||||
|
||||
if (!tool) {
|
||||
throw new Error(`Tool not found: ${name}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await tool.handler(args || {});
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(result, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({ error: errorMessage }, null, 2),
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
|
||||
console.error('HelpScout MCP Server running on stdio');
|
||||
console.error(`Registered ${allTools.length} tools`);
|
||||
console.error(`Registered ${allApps.length} apps`);
|
||||
}
|
||||
@ -1,308 +0,0 @@
|
||||
import type { HelpScoutClient } from '../api/client.js';
|
||||
import type { Conversation, Thread } from '../types/index.js';
|
||||
|
||||
export function registerConversationTools(client: HelpScoutClient) {
|
||||
return [
|
||||
{
|
||||
name: 'helpscout_list_conversations',
|
||||
description: 'List conversations with optional filters (mailbox, folder, status, tag, assignee, customer)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
mailbox: { type: 'number', description: 'Filter by mailbox ID' },
|
||||
folder: { type: 'number', description: 'Filter by folder ID' },
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['active', 'pending', 'closed', 'spam'],
|
||||
description: 'Filter by status',
|
||||
},
|
||||
tag: { type: 'string', description: 'Filter by tag name' },
|
||||
assignedTo: { type: 'number', description: 'Filter by assigned user ID' },
|
||||
customerId: { type: 'number', description: 'Filter by customer ID' },
|
||||
query: { type: 'string', description: 'Search query' },
|
||||
page: { type: 'number', description: 'Page number (default: 1)' },
|
||||
sortField: { type: 'string', description: 'Field to sort by' },
|
||||
sortOrder: { type: 'string', enum: ['asc', 'desc'] },
|
||||
},
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const conversations = await client.getAllPages<Conversation>(
|
||||
'/conversations',
|
||||
args,
|
||||
'conversations'
|
||||
);
|
||||
return { conversations, count: conversations.length };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_get_conversation',
|
||||
description: 'Get a conversation by ID with full details',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Conversation ID' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args: { id: number }) => {
|
||||
const conversation = await client.get<Conversation>(`/conversations/${args.id}`);
|
||||
return conversation;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_create_conversation',
|
||||
description: 'Create a new conversation (email, chat, or phone)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
subject: { type: 'string', description: 'Conversation subject' },
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['email', 'chat', 'phone'],
|
||||
description: 'Conversation type',
|
||||
},
|
||||
mailboxId: { type: 'number', description: 'Mailbox ID' },
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['active', 'pending', 'closed'],
|
||||
description: 'Initial status',
|
||||
},
|
||||
customerId: { type: 'number', description: 'Customer ID' },
|
||||
customerEmail: { type: 'string', description: 'Customer email (if no customerId)' },
|
||||
assignTo: { type: 'number', description: 'User ID to assign to' },
|
||||
tags: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Tags to apply',
|
||||
},
|
||||
threads: {
|
||||
type: 'array',
|
||||
items: { type: 'object' },
|
||||
description: 'Initial threads',
|
||||
},
|
||||
},
|
||||
required: ['subject', 'type', 'mailboxId'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const response = await client.post<{ id: number }>(
|
||||
'/conversations',
|
||||
args
|
||||
);
|
||||
return response;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_update_conversation',
|
||||
description: 'Update conversation properties (subject, status, assignee, mailbox, etc)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Conversation ID' },
|
||||
op: {
|
||||
type: 'string',
|
||||
enum: ['replace', 'remove'],
|
||||
description: 'Operation type',
|
||||
},
|
||||
path: {
|
||||
type: 'string',
|
||||
description: 'Property path (e.g., /subject, /status, /assignTo)',
|
||||
},
|
||||
value: { description: 'New value for the property' },
|
||||
},
|
||||
required: ['id', 'op', 'path'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const { id, op, path, value } = args;
|
||||
await client.patch(`/conversations/${id}`, { op, path, value });
|
||||
return { success: true, message: 'Conversation updated' };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_delete_conversation',
|
||||
description: 'Delete a conversation permanently',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Conversation ID' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args: { id: number }) => {
|
||||
await client.delete(`/conversations/${args.id}`);
|
||||
return { success: true, message: 'Conversation deleted' };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_list_conversation_threads',
|
||||
description: 'List all threads in a conversation',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
conversationId: { type: 'number', description: 'Conversation ID' },
|
||||
},
|
||||
required: ['conversationId'],
|
||||
},
|
||||
handler: async (args: { conversationId: number }) => {
|
||||
const threads = await client.getAllPages<Thread>(
|
||||
`/conversations/${args.conversationId}/threads`,
|
||||
{},
|
||||
'threads'
|
||||
);
|
||||
return { threads, count: threads.length };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_create_conversation_reply',
|
||||
description: 'Create a reply thread in a conversation',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
conversationId: { type: 'number', description: 'Conversation ID' },
|
||||
text: { type: 'string', description: 'Reply text (HTML supported)' },
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['message', 'reply'],
|
||||
description: 'Thread type',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['active', 'pending', 'closed'],
|
||||
description: 'Conversation status after reply',
|
||||
},
|
||||
user: { type: 'number', description: 'User ID sending the reply' },
|
||||
attachments: {
|
||||
type: 'array',
|
||||
items: { type: 'object' },
|
||||
description: 'Attachments',
|
||||
},
|
||||
imported: { type: 'boolean', description: 'Mark as imported (no notifications)' },
|
||||
},
|
||||
required: ['conversationId', 'text', 'type'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const { conversationId, ...threadData } = args;
|
||||
const response = await client.post(
|
||||
`/conversations/${conversationId}/threads`,
|
||||
threadData
|
||||
);
|
||||
return response;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_create_conversation_note',
|
||||
description: 'Create a private note in a conversation',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
conversationId: { type: 'number', description: 'Conversation ID' },
|
||||
text: { type: 'string', description: 'Note text (HTML supported)' },
|
||||
user: { type: 'number', description: 'User ID creating the note' },
|
||||
},
|
||||
required: ['conversationId', 'text'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const { conversationId, text, user } = args;
|
||||
const response = await client.post(
|
||||
`/conversations/${conversationId}/threads`,
|
||||
{
|
||||
text,
|
||||
type: 'note',
|
||||
user,
|
||||
}
|
||||
);
|
||||
return response;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_create_conversation_phone',
|
||||
description: 'Create a phone thread in a conversation',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
conversationId: { type: 'number', description: 'Conversation ID' },
|
||||
text: { type: 'string', description: 'Phone call notes' },
|
||||
user: { type: 'number', description: 'User ID' },
|
||||
phone: { type: 'string', description: 'Phone number' },
|
||||
},
|
||||
required: ['conversationId', 'text'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const { conversationId, ...threadData } = args;
|
||||
const response = await client.post(
|
||||
`/conversations/${conversationId}/threads`,
|
||||
{
|
||||
...threadData,
|
||||
type: 'phone',
|
||||
}
|
||||
);
|
||||
return response;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_update_conversation_tags',
|
||||
description: 'Update tags on a conversation (add or remove)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Conversation ID' },
|
||||
tags: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Tag names to set',
|
||||
},
|
||||
},
|
||||
required: ['id', 'tags'],
|
||||
},
|
||||
handler: async (args: { id: number; tags: string[] }) => {
|
||||
await client.put(`/conversations/${args.id}/tags`, {
|
||||
tags: args.tags,
|
||||
});
|
||||
return { success: true, message: 'Tags updated' };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_change_conversation_status',
|
||||
description: 'Change conversation status (active, pending, closed, spam)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Conversation ID' },
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['active', 'pending', 'closed', 'spam'],
|
||||
description: 'New status',
|
||||
},
|
||||
},
|
||||
required: ['id', 'status'],
|
||||
},
|
||||
handler: async (args: { id: number; status: string }) => {
|
||||
await client.patch(`/conversations/${args.id}`, {
|
||||
op: 'replace',
|
||||
path: '/status',
|
||||
value: args.status,
|
||||
});
|
||||
return { success: true, message: `Status changed to ${args.status}` };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_assign_conversation',
|
||||
description: 'Assign a conversation to a user',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Conversation ID' },
|
||||
userId: { type: 'number', description: 'User ID to assign to' },
|
||||
},
|
||||
required: ['id', 'userId'],
|
||||
},
|
||||
handler: async (args: { id: number; userId: number }) => {
|
||||
await client.patch(`/conversations/${args.id}`, {
|
||||
op: 'replace',
|
||||
path: '/assignTo',
|
||||
value: args.userId,
|
||||
});
|
||||
return { success: true, message: 'Conversation assigned' };
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -1,268 +0,0 @@
|
||||
import type { HelpScoutClient } from '../api/client.js';
|
||||
import type { Customer, CustomerEmail, CustomerPhone, CustomerAddress } from '../types/index.js';
|
||||
|
||||
export function registerCustomerTools(client: HelpScoutClient) {
|
||||
return [
|
||||
{
|
||||
name: 'helpscout_list_customers',
|
||||
description: 'List customers with optional filters (email, first name, last name, query)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
email: { type: 'string', description: 'Filter by email' },
|
||||
firstName: { type: 'string', description: 'Filter by first name' },
|
||||
lastName: { type: 'string', description: 'Filter by last name' },
|
||||
query: { type: 'string', description: 'Search query' },
|
||||
mailbox: { type: 'number', description: 'Filter by mailbox ID' },
|
||||
page: { type: 'number', description: 'Page number (default: 1)' },
|
||||
},
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const customers = await client.getAllPages<Customer>(
|
||||
'/customers',
|
||||
args,
|
||||
'customers'
|
||||
);
|
||||
return { customers, count: customers.length };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_get_customer',
|
||||
description: 'Get a customer by ID with full details',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Customer ID' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args: { id: number }) => {
|
||||
const customer = await client.get<Customer>(`/customers/${args.id}`);
|
||||
return customer;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_create_customer',
|
||||
description: 'Create a new customer',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
firstName: { type: 'string', description: 'First name' },
|
||||
lastName: { type: 'string', description: 'Last name' },
|
||||
email: { type: 'string', description: 'Primary email' },
|
||||
phone: { type: 'string', description: 'Primary phone' },
|
||||
organization: { type: 'string', description: 'Organization name' },
|
||||
jobTitle: { type: 'string', description: 'Job title' },
|
||||
photoUrl: { type: 'string', description: 'Photo URL' },
|
||||
background: { type: 'string', description: 'Background/notes' },
|
||||
location: { type: 'string', description: 'Location' },
|
||||
age: { type: 'string', description: 'Age' },
|
||||
gender: { type: 'string', description: 'Gender' },
|
||||
},
|
||||
required: ['firstName', 'lastName'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const response = await client.post<{ id: number }>(
|
||||
'/customers',
|
||||
args
|
||||
);
|
||||
return response;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_update_customer',
|
||||
description: 'Update customer properties',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Customer ID' },
|
||||
op: {
|
||||
type: 'string',
|
||||
enum: ['replace', 'remove'],
|
||||
description: 'Operation type',
|
||||
},
|
||||
path: {
|
||||
type: 'string',
|
||||
description: 'Property path (e.g., /firstName, /email)',
|
||||
},
|
||||
value: { description: 'New value' },
|
||||
},
|
||||
required: ['id', 'op', 'path'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const { id, op, path, value } = args;
|
||||
await client.patch(`/customers/${id}`, { op, path, value });
|
||||
return { success: true, message: 'Customer updated' };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_delete_customer',
|
||||
description: 'Delete a customer permanently',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Customer ID' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args: { id: number }) => {
|
||||
await client.delete(`/customers/${args.id}`);
|
||||
return { success: true, message: 'Customer deleted' };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_list_customer_emails',
|
||||
description: 'List all emails for a customer',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
customerId: { type: 'number', description: 'Customer ID' },
|
||||
},
|
||||
required: ['customerId'],
|
||||
},
|
||||
handler: async (args: { customerId: number }) => {
|
||||
const emails = await client.getAllPages<CustomerEmail>(
|
||||
`/customers/${args.customerId}/emails`,
|
||||
{},
|
||||
'emails'
|
||||
);
|
||||
return { emails, count: emails.length };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_create_customer_email',
|
||||
description: 'Add an email address to a customer',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
customerId: { type: 'number', description: 'Customer ID' },
|
||||
value: { type: 'string', description: 'Email address' },
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['work', 'home', 'other'],
|
||||
description: 'Email type',
|
||||
},
|
||||
location: { type: 'string', description: 'Location label' },
|
||||
},
|
||||
required: ['customerId', 'value'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const { customerId, ...emailData } = args;
|
||||
const response = await client.post(
|
||||
`/customers/${customerId}/emails`,
|
||||
emailData
|
||||
);
|
||||
return response;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_list_customer_phones',
|
||||
description: 'List all phone numbers for a customer',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
customerId: { type: 'number', description: 'Customer ID' },
|
||||
},
|
||||
required: ['customerId'],
|
||||
},
|
||||
handler: async (args: { customerId: number }) => {
|
||||
const phones = await client.getAllPages<CustomerPhone>(
|
||||
`/customers/${args.customerId}/phones`,
|
||||
{},
|
||||
'phones'
|
||||
);
|
||||
return { phones, count: phones.length };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_create_customer_phone',
|
||||
description: 'Add a phone number to a customer',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
customerId: { type: 'number', description: 'Customer ID' },
|
||||
value: { type: 'string', description: 'Phone number' },
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['work', 'home', 'mobile', 'fax', 'other'],
|
||||
description: 'Phone type',
|
||||
},
|
||||
location: { type: 'string', description: 'Location label' },
|
||||
},
|
||||
required: ['customerId', 'value'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const { customerId, ...phoneData } = args;
|
||||
const response = await client.post(
|
||||
`/customers/${customerId}/phones`,
|
||||
phoneData
|
||||
);
|
||||
return response;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_list_customer_addresses',
|
||||
description: 'List all addresses for a customer',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
customerId: { type: 'number', description: 'Customer ID' },
|
||||
},
|
||||
required: ['customerId'],
|
||||
},
|
||||
handler: async (args: { customerId: number }) => {
|
||||
const addresses = await client.getAllPages<CustomerAddress>(
|
||||
`/customers/${args.customerId}/addresses`,
|
||||
{},
|
||||
'addresses'
|
||||
);
|
||||
return { addresses, count: addresses.length };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_create_customer_address',
|
||||
description: 'Add an address to a customer',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
customerId: { type: 'number', description: 'Customer ID' },
|
||||
city: { type: 'string', description: 'City' },
|
||||
state: { type: 'string', description: 'State/Province' },
|
||||
postalCode: { type: 'string', description: 'Postal code' },
|
||||
country: { type: 'string', description: 'Country code (ISO 3166-1 alpha-2)' },
|
||||
lines: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Address lines',
|
||||
},
|
||||
},
|
||||
required: ['customerId', 'city', 'country'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const { customerId, ...addressData } = args;
|
||||
const response = await client.post(
|
||||
`/customers/${customerId}/addresses`,
|
||||
addressData
|
||||
);
|
||||
return response;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_list_customer_properties',
|
||||
description: 'List all custom properties for a customer',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
customerId: { type: 'number', description: 'Customer ID' },
|
||||
},
|
||||
required: ['customerId'],
|
||||
},
|
||||
handler: async (args: { customerId: number }) => {
|
||||
const properties = await client.get<any>(
|
||||
`/customers/${args.customerId}/properties`
|
||||
);
|
||||
return properties;
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -1,97 +0,0 @@
|
||||
import type { HelpScoutClient } from '../api/client.js';
|
||||
import type { Mailbox, Folder, CustomField } from '../types/index.js';
|
||||
|
||||
export function registerMailboxTools(client: HelpScoutClient) {
|
||||
return [
|
||||
{
|
||||
name: 'helpscout_list_mailboxes',
|
||||
description: 'List all mailboxes',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
page: { type: 'number', description: 'Page number (default: 1)' },
|
||||
},
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const mailboxes = await client.getAllPages<Mailbox>(
|
||||
'/mailboxes',
|
||||
args,
|
||||
'mailboxes'
|
||||
);
|
||||
return { mailboxes, count: mailboxes.length };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_get_mailbox',
|
||||
description: 'Get a mailbox by ID with full details',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Mailbox ID' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args: { id: number }) => {
|
||||
const mailbox = await client.get<Mailbox>(`/mailboxes/${args.id}`);
|
||||
return mailbox;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_list_mailbox_folders',
|
||||
description: 'List all folders in a mailbox',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
mailboxId: { type: 'number', description: 'Mailbox ID' },
|
||||
page: { type: 'number', description: 'Page number (default: 1)' },
|
||||
},
|
||||
required: ['mailboxId'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const folders = await client.getAllPages<Folder>(
|
||||
`/mailboxes/${args.mailboxId}/folders`,
|
||||
{ page: args.page },
|
||||
'folders'
|
||||
);
|
||||
return { folders, count: folders.length };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_get_mailbox_folder',
|
||||
description: 'Get a specific folder by ID',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
mailboxId: { type: 'number', description: 'Mailbox ID' },
|
||||
folderId: { type: 'number', description: 'Folder ID' },
|
||||
},
|
||||
required: ['mailboxId', 'folderId'],
|
||||
},
|
||||
handler: async (args: { mailboxId: number; folderId: number }) => {
|
||||
const folder = await client.get<Folder>(
|
||||
`/mailboxes/${args.mailboxId}/folders/${args.folderId}`
|
||||
);
|
||||
return folder;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_list_mailbox_fields',
|
||||
description: 'List custom fields for a mailbox',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
mailboxId: { type: 'number', description: 'Mailbox ID' },
|
||||
},
|
||||
required: ['mailboxId'],
|
||||
},
|
||||
handler: async (args: { mailboxId: number }) => {
|
||||
const fields = await client.getAllPages<CustomField>(
|
||||
`/mailboxes/${args.mailboxId}/fields`,
|
||||
{},
|
||||
'fields'
|
||||
);
|
||||
return { fields, count: fields.length };
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -1,244 +0,0 @@
|
||||
import type { HelpScoutClient } from '../api/client.js';
|
||||
import type {
|
||||
CompanyReport,
|
||||
ConversationReport,
|
||||
HappinessReport,
|
||||
ProductivityReport,
|
||||
UserReport,
|
||||
} from '../types/index.js';
|
||||
|
||||
export function registerReportingTools(client: HelpScoutClient) {
|
||||
return [
|
||||
{
|
||||
name: 'helpscout_get_company_report',
|
||||
description: 'Get company overview report (conversations, customers, happiness, response times)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
start: {
|
||||
type: 'string',
|
||||
description: 'Start date (YYYY-MM-DD)',
|
||||
},
|
||||
end: {
|
||||
type: 'string',
|
||||
description: 'End date (YYYY-MM-DD)',
|
||||
},
|
||||
previousStart: {
|
||||
type: 'string',
|
||||
description: 'Previous period start (for comparison)',
|
||||
},
|
||||
previousEnd: {
|
||||
type: 'string',
|
||||
description: 'Previous period end (for comparison)',
|
||||
},
|
||||
mailboxes: {
|
||||
type: 'array',
|
||||
items: { type: 'number' },
|
||||
description: 'Filter by mailbox IDs',
|
||||
},
|
||||
tags: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Filter by tags',
|
||||
},
|
||||
},
|
||||
required: ['start', 'end'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const report = await client.get<CompanyReport>(
|
||||
'/reports/company',
|
||||
args
|
||||
);
|
||||
return report;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_get_conversations_report',
|
||||
description: 'Get conversations report (volume, trends, busiest times)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
start: {
|
||||
type: 'string',
|
||||
description: 'Start date (YYYY-MM-DD)',
|
||||
},
|
||||
end: {
|
||||
type: 'string',
|
||||
description: 'End date (YYYY-MM-DD)',
|
||||
},
|
||||
previousStart: {
|
||||
type: 'string',
|
||||
description: 'Previous period start (for comparison)',
|
||||
},
|
||||
previousEnd: {
|
||||
type: 'string',
|
||||
description: 'Previous period end (for comparison)',
|
||||
},
|
||||
mailboxes: {
|
||||
type: 'array',
|
||||
items: { type: 'number' },
|
||||
description: 'Filter by mailbox IDs',
|
||||
},
|
||||
tags: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Filter by tags',
|
||||
},
|
||||
folders: {
|
||||
type: 'array',
|
||||
items: { type: 'number' },
|
||||
description: 'Filter by folder IDs',
|
||||
},
|
||||
},
|
||||
required: ['start', 'end'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const report = await client.get<ConversationReport>(
|
||||
'/reports/conversations',
|
||||
args
|
||||
);
|
||||
return report;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_get_happiness_report',
|
||||
description: 'Get happiness report (ratings, scores, sentiment)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
start: {
|
||||
type: 'string',
|
||||
description: 'Start date (YYYY-MM-DD)',
|
||||
},
|
||||
end: {
|
||||
type: 'string',
|
||||
description: 'End date (YYYY-MM-DD)',
|
||||
},
|
||||
previousStart: {
|
||||
type: 'string',
|
||||
description: 'Previous period start (for comparison)',
|
||||
},
|
||||
previousEnd: {
|
||||
type: 'string',
|
||||
description: 'Previous period end (for comparison)',
|
||||
},
|
||||
mailboxes: {
|
||||
type: 'array',
|
||||
items: { type: 'number' },
|
||||
description: 'Filter by mailbox IDs',
|
||||
},
|
||||
tags: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Filter by tags',
|
||||
},
|
||||
},
|
||||
required: ['start', 'end'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const report = await client.get<HappinessReport>(
|
||||
'/reports/happiness',
|
||||
args
|
||||
);
|
||||
return report;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_get_productivity_report',
|
||||
description: 'Get productivity report (replies sent, resolution time, response time)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
start: {
|
||||
type: 'string',
|
||||
description: 'Start date (YYYY-MM-DD)',
|
||||
},
|
||||
end: {
|
||||
type: 'string',
|
||||
description: 'End date (YYYY-MM-DD)',
|
||||
},
|
||||
previousStart: {
|
||||
type: 'string',
|
||||
description: 'Previous period start (for comparison)',
|
||||
},
|
||||
previousEnd: {
|
||||
type: 'string',
|
||||
description: 'Previous period end (for comparison)',
|
||||
},
|
||||
mailboxes: {
|
||||
type: 'array',
|
||||
items: { type: 'number' },
|
||||
description: 'Filter by mailbox IDs',
|
||||
},
|
||||
tags: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Filter by tags',
|
||||
},
|
||||
},
|
||||
required: ['start', 'end'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const report = await client.get<ProductivityReport>(
|
||||
'/reports/productivity',
|
||||
args
|
||||
);
|
||||
return report;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_get_user_report',
|
||||
description: 'Get report for a specific user (performance, activity)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: {
|
||||
type: 'number',
|
||||
description: 'User ID to report on',
|
||||
},
|
||||
start: {
|
||||
type: 'string',
|
||||
description: 'Start date (YYYY-MM-DD)',
|
||||
},
|
||||
end: {
|
||||
type: 'string',
|
||||
description: 'End date (YYYY-MM-DD)',
|
||||
},
|
||||
previousStart: {
|
||||
type: 'string',
|
||||
description: 'Previous period start (for comparison)',
|
||||
},
|
||||
previousEnd: {
|
||||
type: 'string',
|
||||
description: 'Previous period end (for comparison)',
|
||||
},
|
||||
mailboxes: {
|
||||
type: 'array',
|
||||
items: { type: 'number' },
|
||||
description: 'Filter by mailbox IDs',
|
||||
},
|
||||
tags: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Filter by tags',
|
||||
},
|
||||
},
|
||||
required: ['userId', 'start', 'end'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const report = await client.get<UserReport>(
|
||||
`/reports/user/${args.userId}`,
|
||||
{
|
||||
start: args.start,
|
||||
end: args.end,
|
||||
previousStart: args.previousStart,
|
||||
previousEnd: args.previousEnd,
|
||||
mailboxes: args.mailboxes,
|
||||
tags: args.tags,
|
||||
}
|
||||
);
|
||||
return report;
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -1,102 +0,0 @@
|
||||
import type { HelpScoutClient } from '../api/client.js';
|
||||
import type { SavedReply } from '../types/index.js';
|
||||
|
||||
export function registerSavedReplyTools(client: HelpScoutClient) {
|
||||
return [
|
||||
{
|
||||
name: 'helpscout_list_saved_replies',
|
||||
description: 'List saved replies with optional filters',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
mailboxId: { type: 'number', description: 'Filter by mailbox ID' },
|
||||
userId: { type: 'number', description: 'Filter by user ID' },
|
||||
page: { type: 'number', description: 'Page number (default: 1)' },
|
||||
},
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const replies = await client.getAllPages<SavedReply>(
|
||||
'/saved-replies',
|
||||
args,
|
||||
'replies'
|
||||
);
|
||||
return { replies, count: replies.length };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_get_saved_reply',
|
||||
description: 'Get a saved reply by ID',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Saved reply ID' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args: { id: number }) => {
|
||||
const reply = await client.get<SavedReply>(`/saved-replies/${args.id}`);
|
||||
return reply;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_create_saved_reply',
|
||||
description: 'Create a new saved reply',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Reply name/title' },
|
||||
text: { type: 'string', description: 'Reply text (HTML supported)' },
|
||||
mailboxId: {
|
||||
type: 'number',
|
||||
description: 'Mailbox ID (for mailbox-specific reply)',
|
||||
},
|
||||
userId: {
|
||||
type: 'number',
|
||||
description: 'User ID (for user-specific reply)',
|
||||
},
|
||||
},
|
||||
required: ['name', 'text'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const response = await client.post<{ id: number }>(
|
||||
'/saved-replies',
|
||||
args
|
||||
);
|
||||
return response;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_update_saved_reply',
|
||||
description: 'Update a saved reply',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Saved reply ID' },
|
||||
name: { type: 'string', description: 'New name' },
|
||||
text: { type: 'string', description: 'New text' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const { id, ...updates } = args;
|
||||
await client.put(`/saved-replies/${id}`, updates);
|
||||
return { success: true, message: 'Saved reply updated' };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_delete_saved_reply',
|
||||
description: 'Delete a saved reply',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Saved reply ID' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args: { id: number }) => {
|
||||
await client.delete(`/saved-replies/${args.id}`);
|
||||
return { success: true, message: 'Saved reply deleted' };
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -1,77 +0,0 @@
|
||||
import type { HelpScoutClient } from '../api/client.js';
|
||||
import type { Tag } from '../types/index.js';
|
||||
|
||||
export function registerTagTools(client: HelpScoutClient) {
|
||||
return [
|
||||
{
|
||||
name: 'helpscout_list_tags',
|
||||
description: 'List all tags in the account',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
page: { type: 'number', description: 'Page number (default: 1)' },
|
||||
},
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const tags = await client.getAllPages<Tag>(
|
||||
'/tags',
|
||||
args,
|
||||
'tags'
|
||||
);
|
||||
return { tags, count: tags.length };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_create_tag',
|
||||
description: 'Create a new tag',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Tag name' },
|
||||
color: {
|
||||
type: 'string',
|
||||
description: 'Tag color (hex code, e.g., #FF5733)',
|
||||
},
|
||||
},
|
||||
required: ['name'],
|
||||
},
|
||||
handler: async (args: { name: string; color?: string }) => {
|
||||
const response = await client.post<{ id: number }>('/tags', args);
|
||||
return response;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_update_tag',
|
||||
description: 'Update a tag (rename or change color)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Tag ID' },
|
||||
name: { type: 'string', description: 'New tag name' },
|
||||
color: { type: 'string', description: 'New tag color (hex code)' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const { id, ...updates } = args;
|
||||
await client.put(`/tags/${id}`, updates);
|
||||
return { success: true, message: 'Tag updated' };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_delete_tag',
|
||||
description: 'Delete a tag',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Tag ID' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args: { id: number }) => {
|
||||
await client.delete(`/tags/${args.id}`);
|
||||
return { success: true, message: 'Tag deleted' };
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
import type { HelpScoutClient } from '../api/client.js';
|
||||
import type { Team, User } from '../types/index.js';
|
||||
|
||||
export function registerTeamTools(client: HelpScoutClient) {
|
||||
return [
|
||||
{
|
||||
name: 'helpscout_list_teams',
|
||||
description: 'List all teams',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
page: { type: 'number', description: 'Page number (default: 1)' },
|
||||
},
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const teams = await client.getAllPages<Team>(
|
||||
'/teams',
|
||||
args,
|
||||
'teams'
|
||||
);
|
||||
return { teams, count: teams.length };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_get_team',
|
||||
description: 'Get a team by ID with full details',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Team ID' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args: { id: number }) => {
|
||||
const team = await client.get<Team>(`/teams/${args.id}`);
|
||||
return team;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_list_team_members',
|
||||
description: 'List all members of a team',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
teamId: { type: 'number', description: 'Team ID' },
|
||||
page: { type: 'number', description: 'Page number (default: 1)' },
|
||||
},
|
||||
required: ['teamId'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const members = await client.getAllPages<User>(
|
||||
`/teams/${args.teamId}/members`,
|
||||
{ page: args.page },
|
||||
'members'
|
||||
);
|
||||
return { members, count: members.length };
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -1,53 +0,0 @@
|
||||
import type { HelpScoutClient } from '../api/client.js';
|
||||
import type { User } from '../types/index.js';
|
||||
|
||||
export function registerUserTools(client: HelpScoutClient) {
|
||||
return [
|
||||
{
|
||||
name: 'helpscout_list_users',
|
||||
description: 'List all users in the account',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
page: { type: 'number', description: 'Page number (default: 1)' },
|
||||
email: { type: 'string', description: 'Filter by email' },
|
||||
},
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const users = await client.getAllPages<User>(
|
||||
'/users',
|
||||
args,
|
||||
'users'
|
||||
);
|
||||
return { users, count: users.length };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_get_user',
|
||||
description: 'Get a user by ID with full details',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args: { id: number }) => {
|
||||
const user = await client.get<User>(`/users/${args.id}`);
|
||||
return user;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_get_current_user',
|
||||
description: 'Get the resource owner (current authenticated user)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
},
|
||||
handler: async () => {
|
||||
const user = await client.get<User>('/users/me');
|
||||
return user;
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -1,109 +0,0 @@
|
||||
import type { HelpScoutClient } from '../api/client.js';
|
||||
import type { Webhook } from '../types/index.js';
|
||||
|
||||
export function registerWebhookTools(client: HelpScoutClient) {
|
||||
return [
|
||||
{
|
||||
name: 'helpscout_list_webhooks',
|
||||
description: 'List all webhooks',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
page: { type: 'number', description: 'Page number (default: 1)' },
|
||||
},
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const webhooks = await client.getAllPages<Webhook>(
|
||||
'/webhooks',
|
||||
args,
|
||||
'webhooks'
|
||||
);
|
||||
return { webhooks, count: webhooks.length };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_get_webhook',
|
||||
description: 'Get a webhook by ID',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Webhook ID' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args: { id: string }) => {
|
||||
const webhook = await client.get<Webhook>(`/webhooks/${args.id}`);
|
||||
return webhook;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_create_webhook',
|
||||
description: 'Create a new webhook',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
url: { type: 'string', description: 'Webhook URL' },
|
||||
events: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Events to subscribe to (e.g., conversation.created)',
|
||||
},
|
||||
secret: {
|
||||
type: 'string',
|
||||
description: 'Webhook secret for signature verification',
|
||||
},
|
||||
},
|
||||
required: ['url', 'events'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const response = await client.post<{ id: string }>(
|
||||
'/webhooks',
|
||||
args
|
||||
);
|
||||
return response;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_update_webhook',
|
||||
description: 'Update a webhook',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Webhook ID' },
|
||||
url: { type: 'string', description: 'New webhook URL' },
|
||||
events: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'New events list',
|
||||
},
|
||||
state: {
|
||||
type: 'string',
|
||||
enum: ['enabled', 'disabled'],
|
||||
description: 'Webhook state',
|
||||
},
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const { id, ...updates } = args;
|
||||
await client.put(`/webhooks/${id}`, updates);
|
||||
return { success: true, message: 'Webhook updated' };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_delete_webhook',
|
||||
description: 'Delete a webhook',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Webhook ID' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args: { id: string }) => {
|
||||
await client.delete(`/webhooks/${args.id}`);
|
||||
return { success: true, message: 'Webhook deleted' };
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -1,96 +0,0 @@
|
||||
import type { HelpScoutClient } from '../api/client.js';
|
||||
import type { Workflow, WorkflowStats } from '../types/index.js';
|
||||
|
||||
export function registerWorkflowTools(client: HelpScoutClient) {
|
||||
return [
|
||||
{
|
||||
name: 'helpscout_list_workflows',
|
||||
description: 'List all workflows with optional filters',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
mailboxId: { type: 'number', description: 'Filter by mailbox ID' },
|
||||
page: { type: 'number', description: 'Page number (default: 1)' },
|
||||
},
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const workflows = await client.getAllPages<Workflow>(
|
||||
'/workflows',
|
||||
args,
|
||||
'workflows'
|
||||
);
|
||||
return { workflows, count: workflows.length };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_get_workflow',
|
||||
description: 'Get a workflow by ID with full details',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Workflow ID' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args: { id: number }) => {
|
||||
const workflow = await client.get<Workflow>(`/workflows/${args.id}`);
|
||||
return workflow;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_activate_workflow',
|
||||
description: 'Activate a workflow',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Workflow ID' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args: { id: number }) => {
|
||||
await client.patch(`/workflows/${args.id}`, {
|
||||
op: 'replace',
|
||||
path: '/status',
|
||||
value: 'active',
|
||||
});
|
||||
return { success: true, message: 'Workflow activated' };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_deactivate_workflow',
|
||||
description: 'Deactivate a workflow',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Workflow ID' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args: { id: number }) => {
|
||||
await client.patch(`/workflows/${args.id}`, {
|
||||
op: 'replace',
|
||||
path: '/status',
|
||||
value: 'inactive',
|
||||
});
|
||||
return { success: true, message: 'Workflow deactivated' };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'helpscout_get_workflow_stats',
|
||||
description: 'Get statistics for a workflow (run counts)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Workflow ID' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args: { id: number }) => {
|
||||
const stats = await client.get<WorkflowStats>(
|
||||
`/workflows/${args.id}/stats`
|
||||
);
|
||||
return stats;
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -1,500 +0,0 @@
|
||||
// HelpScout Mailbox API v2 Types
|
||||
|
||||
export interface HelpScoutConfig {
|
||||
appId?: string;
|
||||
appSecret?: string;
|
||||
accessToken: string;
|
||||
}
|
||||
|
||||
export interface APIError {
|
||||
message: string;
|
||||
logref?: string;
|
||||
_links?: {
|
||||
about?: { href: string };
|
||||
};
|
||||
}
|
||||
|
||||
export interface PagedResponse<T> {
|
||||
_embedded: T;
|
||||
_links?: {
|
||||
self?: { href: string };
|
||||
first?: { href: string };
|
||||
last?: { href: string };
|
||||
next?: { href: string };
|
||||
prev?: { href: string };
|
||||
page?: { href: string };
|
||||
};
|
||||
page?: {
|
||||
size: number;
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
number: number;
|
||||
};
|
||||
}
|
||||
|
||||
// Conversations
|
||||
export interface Conversation {
|
||||
id: number;
|
||||
number: number;
|
||||
threads: number;
|
||||
type: 'email' | 'chat' | 'phone';
|
||||
folderId: number;
|
||||
status: 'active' | 'closed' | 'open' | 'pending' | 'spam';
|
||||
state: 'deleted' | 'draft' | 'published';
|
||||
subject: string;
|
||||
preview: string;
|
||||
mailboxId: number;
|
||||
assignee?: Person;
|
||||
createdBy: Person;
|
||||
createdAt: string;
|
||||
closedBy?: number;
|
||||
closedByUser?: Person;
|
||||
closedAt?: string;
|
||||
userUpdatedAt: string;
|
||||
customerWaitingSince?: {
|
||||
time: string;
|
||||
friendly: string;
|
||||
};
|
||||
source: {
|
||||
type: string;
|
||||
via: 'user' | 'customer';
|
||||
};
|
||||
tags?: Tag[];
|
||||
cc?: string[];
|
||||
bcc?: string[];
|
||||
primaryCustomer: Customer;
|
||||
snooze?: {
|
||||
snoozedBy: number;
|
||||
snoozedUntil: string;
|
||||
unsnoozeOnCustomerReply: boolean;
|
||||
};
|
||||
nextEvent?: {
|
||||
time: string;
|
||||
eventType: 'snooze' | 'scheduled';
|
||||
userId: number;
|
||||
cancelOnCustomerReply: boolean;
|
||||
};
|
||||
customFields?: CustomField[];
|
||||
_embedded?: {
|
||||
threads?: Thread[];
|
||||
};
|
||||
_links?: Record<string, { href: string }>;
|
||||
}
|
||||
|
||||
export interface Thread {
|
||||
id: number;
|
||||
type: 'note' | 'message' | 'customer' | 'lineitem' | 'chat' | 'phone';
|
||||
status: 'active' | 'nochange' | 'pending';
|
||||
state: 'published' | 'draft' | 'deleted' | 'hidden';
|
||||
action?: {
|
||||
type: string;
|
||||
text: string;
|
||||
};
|
||||
body: string;
|
||||
source: {
|
||||
type: string;
|
||||
via: string;
|
||||
};
|
||||
customer?: Customer;
|
||||
createdBy: Person;
|
||||
assignedTo?: Person;
|
||||
savedReplyId?: number;
|
||||
to?: string[];
|
||||
cc?: string[];
|
||||
bcc?: string[];
|
||||
createdAt: string;
|
||||
openedAt?: string;
|
||||
_embedded?: {
|
||||
attachments?: Attachment[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreateThread {
|
||||
type: 'note' | 'message' | 'customer' | 'reply' | 'forwardCustomer' | 'forwardParent';
|
||||
text: string;
|
||||
user?: number;
|
||||
customer?: number;
|
||||
imported?: boolean;
|
||||
createdAt?: string;
|
||||
status?: 'active' | 'nochange' | 'pending';
|
||||
to?: string[];
|
||||
cc?: string[];
|
||||
bcc?: string[];
|
||||
attachments?: CreateAttachment[];
|
||||
draft?: boolean;
|
||||
}
|
||||
|
||||
export interface Attachment {
|
||||
id: number;
|
||||
mimeType: string;
|
||||
filename: string;
|
||||
size: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
url: string;
|
||||
_links?: {
|
||||
data?: { href: string };
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreateAttachment {
|
||||
fileName: string;
|
||||
mimeType: string;
|
||||
data: string; // base64
|
||||
}
|
||||
|
||||
// Customers
|
||||
export interface Customer {
|
||||
id: number;
|
||||
type?: 'customer';
|
||||
first?: string;
|
||||
last?: string;
|
||||
email: string;
|
||||
phone?: string;
|
||||
photoUrl?: string;
|
||||
photoType?: 'twitter' | 'facebook' | 'gravatar' | 'google' | 'unknown';
|
||||
gender?: 'male' | 'female' | 'unknown';
|
||||
age?: string;
|
||||
organization?: string;
|
||||
jobTitle?: string;
|
||||
location?: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
background?: string;
|
||||
address?: Address;
|
||||
socialProfiles?: SocialProfile[];
|
||||
emails?: CustomerEmail[];
|
||||
phones?: CustomerPhone[];
|
||||
chats?: CustomerChat[];
|
||||
websites?: CustomerWebsite[];
|
||||
_embedded?: {
|
||||
entries?: CustomerEntry[];
|
||||
};
|
||||
_links?: Record<string, { href: string }>;
|
||||
}
|
||||
|
||||
export interface Address {
|
||||
id?: number;
|
||||
lines: string[];
|
||||
city: string;
|
||||
state: string;
|
||||
postalCode: string;
|
||||
country: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface SocialProfile {
|
||||
id?: number;
|
||||
type: 'twitter' | 'facebook' | 'linkedin' | 'aboutme' | 'google' | 'googleplus' | 'tungleme' | 'quora' | 'foursquare' | 'youtube' | 'flickr' | 'other';
|
||||
value: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface CustomerEmail {
|
||||
id?: number;
|
||||
type: 'home' | 'work' | 'other';
|
||||
value: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface CustomerPhone {
|
||||
id?: number;
|
||||
type: 'home' | 'work' | 'mobile' | 'fax' | 'pager' | 'other';
|
||||
value: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface CustomerChat {
|
||||
id?: number;
|
||||
type: 'aim' | 'gtalk' | 'icq' | 'xmpp' | 'msn' | 'skype' | 'yahoo' | 'qq' | 'other';
|
||||
value: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface CustomerWebsite {
|
||||
id?: number;
|
||||
value: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface CustomerEntry {
|
||||
id: number;
|
||||
type: 'email' | 'phone' | 'chat' | 'website';
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface CreateCustomer {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
photoUrl?: string;
|
||||
photoType?: 'twitter' | 'facebook' | 'gravatar' | 'google' | 'unknown';
|
||||
gender?: 'male' | 'female' | 'unknown';
|
||||
age?: string;
|
||||
organization?: string;
|
||||
jobTitle?: string;
|
||||
location?: string;
|
||||
background?: string;
|
||||
address?: Omit<Address, 'id' | 'createdAt' | 'updatedAt'>;
|
||||
socialProfiles?: Omit<SocialProfile, 'id' | 'createdAt' | 'updatedAt'>[];
|
||||
emails?: Omit<CustomerEmail, 'id' | 'createdAt' | 'updatedAt'>[];
|
||||
phones?: Omit<CustomerPhone, 'id' | 'createdAt' | 'updatedAt'>[];
|
||||
chats?: Omit<CustomerChat, 'id' | 'createdAt' | 'updatedAt'>[];
|
||||
websites?: Omit<CustomerWebsite, 'id' | 'createdAt' | 'updatedAt'>[];
|
||||
}
|
||||
|
||||
// Mailboxes
|
||||
export interface Mailbox {
|
||||
id: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
email: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
_links?: Record<string, { href: string }>;
|
||||
}
|
||||
|
||||
export interface MailboxFields {
|
||||
id: number;
|
||||
name: string;
|
||||
type: 'SINGLE_LINE' | 'MULTI_LINE' | 'DATE' | 'NUMBER' | 'DROPDOWN';
|
||||
order: number;
|
||||
required: boolean;
|
||||
options?: string[];
|
||||
_links?: Record<string, { href: string }>;
|
||||
}
|
||||
|
||||
export interface Folder {
|
||||
id: number;
|
||||
name: string;
|
||||
type: 'mytickets' | 'unassigned' | 'drafts' | 'assigned' | 'closed' | 'spam' | 'deleted' | 'mine';
|
||||
userId?: number;
|
||||
totalCount: number;
|
||||
activeCount: number;
|
||||
updatedAt: string;
|
||||
_links?: Record<string, { href: string }>;
|
||||
}
|
||||
|
||||
// Users
|
||||
export interface User {
|
||||
id: number;
|
||||
type: 'user' | 'team';
|
||||
first?: string;
|
||||
last?: string;
|
||||
email: string;
|
||||
role: 'owner' | 'admin' | 'user';
|
||||
timezone: string;
|
||||
photoUrl?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
_links?: Record<string, { href: string }>;
|
||||
}
|
||||
|
||||
export interface Person {
|
||||
id: number;
|
||||
type: 'user' | 'customer' | 'team';
|
||||
first?: string;
|
||||
last?: string;
|
||||
email?: string;
|
||||
photoUrl?: string;
|
||||
}
|
||||
|
||||
// Teams
|
||||
export interface Team {
|
||||
id: number;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
_links?: Record<string, { href: string }>;
|
||||
}
|
||||
|
||||
export interface TeamMember {
|
||||
id: number;
|
||||
first: string;
|
||||
last: string;
|
||||
email: string;
|
||||
role: 'owner' | 'admin' | 'user';
|
||||
photoUrl?: string;
|
||||
_links?: Record<string, { href: string }>;
|
||||
}
|
||||
|
||||
// Tags
|
||||
export interface Tag {
|
||||
id: number;
|
||||
tag: string;
|
||||
color: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
_links?: Record<string, { href: string }>;
|
||||
}
|
||||
|
||||
// Workflows
|
||||
export interface Workflow {
|
||||
id: number;
|
||||
mailboxId: number;
|
||||
type: 'manual' | 'automatic';
|
||||
status: 'active' | 'inactive' | 'invalid';
|
||||
order: number;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
modifiedAt: string;
|
||||
_links?: Record<string, { href: string }>;
|
||||
}
|
||||
|
||||
// Saved Replies
|
||||
export interface SavedReply {
|
||||
id: number;
|
||||
text: string;
|
||||
name: string;
|
||||
_links?: Record<string, { href: string }>;
|
||||
}
|
||||
|
||||
// Webhooks
|
||||
export interface Webhook {
|
||||
id: number;
|
||||
url: string;
|
||||
state: 'enabled' | 'disabled';
|
||||
events: string[];
|
||||
notification: boolean;
|
||||
payloadVersion: 'V1' | 'V2';
|
||||
label?: string;
|
||||
secret?: string;
|
||||
_links?: Record<string, { href: string }>;
|
||||
}
|
||||
|
||||
export interface CreateWebhook {
|
||||
url: string;
|
||||
events: string[];
|
||||
secret?: string;
|
||||
}
|
||||
|
||||
// Reports
|
||||
export interface Report {
|
||||
filterTags?: string[];
|
||||
}
|
||||
|
||||
export interface ConversationReport {
|
||||
current: ReportMetrics;
|
||||
previous?: ReportMetrics;
|
||||
deltas?: {
|
||||
[key: string]: {
|
||||
value: number;
|
||||
percent: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface ReportMetrics {
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
conversations: number;
|
||||
conversationsCreated: number;
|
||||
newConversations?: number;
|
||||
customers?: number;
|
||||
resolved?: number;
|
||||
replies?: number;
|
||||
repliesSent?: number;
|
||||
resolvedOnFirstReply?: number;
|
||||
responseTime?: TimeMetrics;
|
||||
resolutionTime?: TimeMetrics;
|
||||
firstResponseTime?: TimeMetrics;
|
||||
}
|
||||
|
||||
export interface TimeMetrics {
|
||||
friendly: string;
|
||||
seconds: number;
|
||||
}
|
||||
|
||||
export interface UserReport {
|
||||
user: Person;
|
||||
current: UserMetrics;
|
||||
previous?: UserMetrics;
|
||||
deltas?: Record<string, { value: number; percent: number }>;
|
||||
}
|
||||
|
||||
export interface UserMetrics {
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
totalConversations: number;
|
||||
conversationsCreated: number;
|
||||
conversationsResolved: number;
|
||||
repliesSent: number;
|
||||
resolvedOnFirstReply?: number;
|
||||
responseTime?: TimeMetrics;
|
||||
resolutionTime?: TimeMetrics;
|
||||
percentResolved?: number;
|
||||
happiness?: {
|
||||
score: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface HappinessReport {
|
||||
current: HappinessMetrics;
|
||||
previous?: HappinessMetrics;
|
||||
}
|
||||
|
||||
export interface HappinessMetrics {
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
happinessScore: number;
|
||||
ratingsCount: number;
|
||||
}
|
||||
|
||||
// Custom Fields
|
||||
export interface CustomField {
|
||||
id: number;
|
||||
name: string;
|
||||
value: string | number;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
// Notes
|
||||
export interface Note {
|
||||
text: string;
|
||||
}
|
||||
|
||||
// Search
|
||||
export interface SearchConversation {
|
||||
id: number;
|
||||
number: number;
|
||||
mailboxid: number;
|
||||
subject: string;
|
||||
status: string;
|
||||
threadCount: number;
|
||||
preview: string;
|
||||
customerId: number;
|
||||
customerEmail: string;
|
||||
customerName: string;
|
||||
updatedAt: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface SearchCustomer {
|
||||
id: number;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
phone?: string;
|
||||
photoUrl?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
// Ratings
|
||||
export interface Rating {
|
||||
id: number;
|
||||
customerId: number;
|
||||
userId?: number;
|
||||
threadId: number;
|
||||
rating: 'great' | 'okay' | 'bad';
|
||||
comments?: string;
|
||||
createdAt: string;
|
||||
modifiedAt?: string;
|
||||
_links?: Record<string, { href: string }>;
|
||||
}
|
||||
@ -1,20 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"lib": ["ES2022"],
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"jsx": "react"
|
||||
"declaration": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user