ghl-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +89 -0
- package/package.json +27 -0
- package/src/cli.js +504 -0
- package/src/lib/config.js +51 -0
- package/src/lib/mcp.js +152 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Quentin Daems
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# GHL CLI 🚀
|
|
2
|
+
|
|
3
|
+
Command-line interface for [GoHighLevel](https://gohighlevel.com) CRM via MCP.
|
|
4
|
+
|
|
5
|
+
Manage contacts, conversations, calendar, opportunities, and payments from your terminal.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g ghl-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
ghl auth
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
You'll need:
|
|
20
|
+
1. **Private Integration Token (PIT)** - from Settings > Private Integrations in GHL
|
|
21
|
+
2. **Location ID** - your sub-account ID
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### Contacts
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
ghl contacts list # List contacts
|
|
29
|
+
ghl contacts get <id> # Get contact details
|
|
30
|
+
ghl contacts create --name "John Doe" --email john@example.com
|
|
31
|
+
ghl contacts tag <id> lead hot # Add tags
|
|
32
|
+
ghl contacts tasks <id> # Get tasks for contact
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Conversations
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
ghl conv search # Search conversations
|
|
39
|
+
ghl conv messages <id> # Get messages
|
|
40
|
+
ghl conv send <id> "Hello!" # Send SMS
|
|
41
|
+
ghl conv send <id> "Hello!" --type Email
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Calendar
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
ghl cal events # List calendar events
|
|
48
|
+
ghl cal notes <appointmentId> # Get appointment notes
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Opportunities
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
ghl opp pipelines # List pipelines
|
|
55
|
+
ghl opp search # Search opportunities
|
|
56
|
+
ghl opp get <id> # Get opportunity details
|
|
57
|
+
ghl opp update <id> --status won --value 5000
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Payments
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
ghl pay transactions # List transactions
|
|
64
|
+
ghl pay order <id> # Get order details
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Location
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
ghl loc info # Get location details
|
|
71
|
+
ghl loc fields # List custom fields
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Environment Variables
|
|
75
|
+
|
|
76
|
+
Instead of `ghl auth`, you can set:
|
|
77
|
+
```bash
|
|
78
|
+
export GHL_TOKEN="pit-xxxxx"
|
|
79
|
+
export GHL_LOCATION_ID="xxxxx"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
MIT © [Quentin Daems](https://shvz.fr)
|
|
85
|
+
|
|
86
|
+
## Credits
|
|
87
|
+
|
|
88
|
+
Built with [LoopShip](https://github.com/nexty5870/loopship) 🚀
|
|
89
|
+
Uses [GoHighLevel MCP](https://marketplace.gohighlevel.com/docs/other/mcp/)
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ghl-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for GoHighLevel CRM via MCP",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ghl": "./src/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": ["src", "README.md", "LICENSE"],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node src/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"chalk": "^5.3.0",
|
|
15
|
+
"commander": "^12.0.0"
|
|
16
|
+
},
|
|
17
|
+
"keywords": ["gohighlevel", "ghl", "crm", "cli", "mcp"],
|
|
18
|
+
"author": "Quentin Daems <quentin@shvz.fr>",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/nexty5870/ghl-cli.git"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18.0.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* GHL CLI - Command-line interface for GoHighLevel CRM
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import { createInterface } from "readline";
|
|
9
|
+
import { createClient, GHLClient } from "./lib/mcp.js";
|
|
10
|
+
import { getToken, getLocationId, setCredentials, clearCredentials, getConfigPath } from "./lib/config.js";
|
|
11
|
+
|
|
12
|
+
const program = new Command();
|
|
13
|
+
|
|
14
|
+
program
|
|
15
|
+
.name("ghl")
|
|
16
|
+
.description("CLI for GoHighLevel CRM via MCP")
|
|
17
|
+
.version("0.1.0");
|
|
18
|
+
|
|
19
|
+
// ============ AUTH ============
|
|
20
|
+
program
|
|
21
|
+
.command("auth")
|
|
22
|
+
.description("Set up your GHL credentials")
|
|
23
|
+
.option("--clear", "Remove saved credentials")
|
|
24
|
+
.option("--show", "Show current auth status")
|
|
25
|
+
.action(async (options) => {
|
|
26
|
+
if (options.clear) {
|
|
27
|
+
clearCredentials();
|
|
28
|
+
console.log(chalk.green("✅ Credentials removed."));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (options.show) {
|
|
33
|
+
const token = getToken();
|
|
34
|
+
const locationId = getLocationId();
|
|
35
|
+
if (token && locationId) {
|
|
36
|
+
console.log(chalk.green(`✅ Configured`));
|
|
37
|
+
console.log(chalk.gray(` Token: ${token.slice(0, 10)}...`));
|
|
38
|
+
console.log(chalk.gray(` Location: ${locationId}`));
|
|
39
|
+
console.log(chalk.gray(` Config: ${getConfigPath()}`));
|
|
40
|
+
} else {
|
|
41
|
+
console.log(chalk.yellow("⚠️ Not configured. Run 'ghl auth' to set up."));
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(chalk.cyan("\n🔐 GoHighLevel Setup\n"));
|
|
47
|
+
console.log(chalk.white("To get your Private Integration Token (PIT):"));
|
|
48
|
+
console.log(chalk.gray(" 1. Go to Settings > Private Integrations in GHL"));
|
|
49
|
+
console.log(chalk.gray(" 2. Create New Integration with required scopes"));
|
|
50
|
+
console.log(chalk.gray(" 3. Copy the generated token\n"));
|
|
51
|
+
|
|
52
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
53
|
+
const question = (prompt) => new Promise((resolve) => rl.question(prompt, resolve));
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const token = await question(chalk.yellow("Paste your PIT token: "));
|
|
57
|
+
const locationId = await question(chalk.yellow("Paste your Location ID: "));
|
|
58
|
+
rl.close();
|
|
59
|
+
|
|
60
|
+
if (!token || !locationId) {
|
|
61
|
+
console.log(chalk.red("\n❌ Both token and location ID are required."));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log(chalk.gray("\nVerifying credentials..."));
|
|
66
|
+
const client = new GHLClient(token.trim(), locationId.trim());
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
await client.getLocation();
|
|
70
|
+
setCredentials(token.trim(), locationId.trim());
|
|
71
|
+
console.log(chalk.green("\n✅ Credentials verified and saved!"));
|
|
72
|
+
console.log(chalk.cyan("\nTry 'ghl contacts list' to see your contacts."));
|
|
73
|
+
} catch (e) {
|
|
74
|
+
console.log(chalk.red(`\n❌ Verification failed: ${e.message}`));
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
} catch (e) {
|
|
78
|
+
rl.close();
|
|
79
|
+
console.log(chalk.red(`\nError: ${e.message}`));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ============ CONTACTS ============
|
|
85
|
+
const contacts = program.command("contacts").description("Manage contacts");
|
|
86
|
+
|
|
87
|
+
contacts
|
|
88
|
+
.command("list")
|
|
89
|
+
.description("List contacts")
|
|
90
|
+
.option("-l, --limit <n>", "Number of contacts", "20")
|
|
91
|
+
.option("-q, --query <text>", "Search query")
|
|
92
|
+
.action(async (options) => {
|
|
93
|
+
try {
|
|
94
|
+
const client = createClient();
|
|
95
|
+
const data = await client.getContacts({
|
|
96
|
+
limit: parseInt(options.limit),
|
|
97
|
+
query: options.query
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
console.log(chalk.cyan("\n👥 Contacts:\n"));
|
|
101
|
+
const contacts = data.contacts || data || [];
|
|
102
|
+
|
|
103
|
+
if (!contacts.length) {
|
|
104
|
+
console.log(chalk.yellow("No contacts found."));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
for (const c of contacts) {
|
|
109
|
+
console.log(chalk.white(` ${chalk.bold(c.id?.slice(0, 8) || "?")} ${c.firstName || ""} ${c.lastName || ""}`));
|
|
110
|
+
console.log(chalk.gray(` ${c.email || ""} • ${c.phone || ""}\n`));
|
|
111
|
+
}
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
contacts
|
|
119
|
+
.command("get <id>")
|
|
120
|
+
.description("Get contact details")
|
|
121
|
+
.action(async (id) => {
|
|
122
|
+
try {
|
|
123
|
+
const client = createClient();
|
|
124
|
+
const contact = await client.getContact(id);
|
|
125
|
+
|
|
126
|
+
console.log(chalk.cyan(`\n👤 Contact: ${contact.firstName || ""} ${contact.lastName || ""}\n`));
|
|
127
|
+
console.log(chalk.white(` ID: ${contact.id}`));
|
|
128
|
+
console.log(chalk.white(` Email: ${contact.email || "-"}`));
|
|
129
|
+
console.log(chalk.white(` Phone: ${contact.phone || "-"}`));
|
|
130
|
+
console.log(chalk.white(` Tags: ${contact.tags?.join(", ") || "-"}`));
|
|
131
|
+
console.log(chalk.white(` Created: ${contact.dateAdded || "-"}`));
|
|
132
|
+
console.log();
|
|
133
|
+
} catch (err) {
|
|
134
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
contacts
|
|
140
|
+
.command("create")
|
|
141
|
+
.description("Create a new contact")
|
|
142
|
+
.requiredOption("--name <name>", "Full name")
|
|
143
|
+
.option("--email <email>", "Email address")
|
|
144
|
+
.option("--phone <phone>", "Phone number")
|
|
145
|
+
.action(async (options) => {
|
|
146
|
+
try {
|
|
147
|
+
const client = createClient();
|
|
148
|
+
const [firstName, ...rest] = options.name.split(" ");
|
|
149
|
+
const lastName = rest.join(" ");
|
|
150
|
+
|
|
151
|
+
const contact = await client.createContact({
|
|
152
|
+
firstName,
|
|
153
|
+
lastName,
|
|
154
|
+
email: options.email,
|
|
155
|
+
phone: options.phone
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
console.log(chalk.green(`\n✅ Contact created: ${contact.id}`));
|
|
159
|
+
} catch (err) {
|
|
160
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
contacts
|
|
166
|
+
.command("tag <id> <tags...>")
|
|
167
|
+
.description("Add tags to a contact")
|
|
168
|
+
.action(async (id, tags) => {
|
|
169
|
+
try {
|
|
170
|
+
const client = createClient();
|
|
171
|
+
await client.addTags(id, tags);
|
|
172
|
+
console.log(chalk.green(`\n✅ Added tags: ${tags.join(", ")}`));
|
|
173
|
+
} catch (err) {
|
|
174
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
contacts
|
|
180
|
+
.command("tasks <id>")
|
|
181
|
+
.description("Get tasks for a contact")
|
|
182
|
+
.action(async (id) => {
|
|
183
|
+
try {
|
|
184
|
+
const client = createClient();
|
|
185
|
+
const data = await client.getTasks(id);
|
|
186
|
+
|
|
187
|
+
console.log(chalk.cyan("\n📋 Tasks:\n"));
|
|
188
|
+
const tasks = data.tasks || data || [];
|
|
189
|
+
|
|
190
|
+
for (const t of tasks) {
|
|
191
|
+
const status = t.completed ? "✅" : "⏳";
|
|
192
|
+
console.log(chalk.white(` ${status} ${t.title || t.body}`));
|
|
193
|
+
if (t.dueDate) console.log(chalk.gray(` Due: ${t.dueDate}`));
|
|
194
|
+
}
|
|
195
|
+
} catch (err) {
|
|
196
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// ============ CONVERSATIONS ============
|
|
202
|
+
const conversations = program.command("conversations").alias("conv").description("Manage conversations");
|
|
203
|
+
|
|
204
|
+
conversations
|
|
205
|
+
.command("search [query]")
|
|
206
|
+
.description("Search conversations")
|
|
207
|
+
.option("-l, --limit <n>", "Number of results", "20")
|
|
208
|
+
.action(async (query, options) => {
|
|
209
|
+
try {
|
|
210
|
+
const client = createClient();
|
|
211
|
+
const data = await client.searchConversations({
|
|
212
|
+
query,
|
|
213
|
+
limit: parseInt(options.limit)
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
console.log(chalk.cyan("\n💬 Conversations:\n"));
|
|
217
|
+
const convs = data.conversations || data || [];
|
|
218
|
+
|
|
219
|
+
for (const c of convs) {
|
|
220
|
+
console.log(chalk.white(` ${chalk.bold(c.id?.slice(0, 8) || "?")} ${c.contactName || c.fullName || "Unknown"}`));
|
|
221
|
+
console.log(chalk.gray(` ${c.lastMessageType || ""} • ${c.lastMessageDate || ""}\n`));
|
|
222
|
+
}
|
|
223
|
+
} catch (err) {
|
|
224
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
conversations
|
|
230
|
+
.command("messages <id>")
|
|
231
|
+
.description("Get messages in a conversation")
|
|
232
|
+
.action(async (id) => {
|
|
233
|
+
try {
|
|
234
|
+
const client = createClient();
|
|
235
|
+
const data = await client.getMessages(id);
|
|
236
|
+
|
|
237
|
+
console.log(chalk.cyan("\n📨 Messages:\n"));
|
|
238
|
+
const messages = data.messages || data || [];
|
|
239
|
+
|
|
240
|
+
for (const m of messages) {
|
|
241
|
+
const dir = m.direction === "inbound" ? "←" : "→";
|
|
242
|
+
const color = m.direction === "inbound" ? chalk.blue : chalk.green;
|
|
243
|
+
console.log(color(` ${dir} [${m.dateAdded || ""}] ${m.body || m.message || ""}`));
|
|
244
|
+
}
|
|
245
|
+
console.log();
|
|
246
|
+
} catch (err) {
|
|
247
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
conversations
|
|
253
|
+
.command("send <id> <message...>")
|
|
254
|
+
.description("Send a message")
|
|
255
|
+
.option("-t, --type <type>", "Message type (SMS, Email)", "SMS")
|
|
256
|
+
.action(async (id, messageParts, options) => {
|
|
257
|
+
try {
|
|
258
|
+
const client = createClient();
|
|
259
|
+
const message = messageParts.join(" ");
|
|
260
|
+
await client.sendMessage(id, message, options.type);
|
|
261
|
+
console.log(chalk.green(`\n✅ Message sent!`));
|
|
262
|
+
} catch (err) {
|
|
263
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// ============ CALENDAR ============
|
|
269
|
+
const calendar = program.command("calendar").alias("cal").description("Calendar and appointments");
|
|
270
|
+
|
|
271
|
+
calendar
|
|
272
|
+
.command("events")
|
|
273
|
+
.description("List calendar events")
|
|
274
|
+
.option("--user <userId>", "Filter by user ID")
|
|
275
|
+
.option("--calendar <calendarId>", "Filter by calendar ID")
|
|
276
|
+
.action(async (options) => {
|
|
277
|
+
try {
|
|
278
|
+
const client = createClient();
|
|
279
|
+
const data = await client.getCalendarEvents({
|
|
280
|
+
userId: options.user,
|
|
281
|
+
calendarId: options.calendar
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
console.log(chalk.cyan("\n📅 Events:\n"));
|
|
285
|
+
const events = data.events || data || [];
|
|
286
|
+
|
|
287
|
+
for (const e of events) {
|
|
288
|
+
console.log(chalk.white(` ${chalk.bold(e.id?.slice(0, 8) || "?")} ${e.title || e.name || "Untitled"}`));
|
|
289
|
+
console.log(chalk.gray(` ${e.startTime || ""} - ${e.endTime || ""}\n`));
|
|
290
|
+
}
|
|
291
|
+
} catch (err) {
|
|
292
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
calendar
|
|
298
|
+
.command("notes <appointmentId>")
|
|
299
|
+
.description("Get appointment notes")
|
|
300
|
+
.action(async (appointmentId) => {
|
|
301
|
+
try {
|
|
302
|
+
const client = createClient();
|
|
303
|
+
const data = await client.getAppointmentNotes(appointmentId);
|
|
304
|
+
|
|
305
|
+
console.log(chalk.cyan("\n📝 Notes:\n"));
|
|
306
|
+
const notes = data.notes || data || [];
|
|
307
|
+
|
|
308
|
+
for (const n of notes) {
|
|
309
|
+
console.log(chalk.white(` ${n.body || n.content || n}`));
|
|
310
|
+
console.log();
|
|
311
|
+
}
|
|
312
|
+
} catch (err) {
|
|
313
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// ============ OPPORTUNITIES ============
|
|
319
|
+
const opportunities = program.command("opportunities").alias("opp").description("Manage opportunities");
|
|
320
|
+
|
|
321
|
+
opportunities
|
|
322
|
+
.command("pipelines")
|
|
323
|
+
.description("List pipelines")
|
|
324
|
+
.action(async () => {
|
|
325
|
+
try {
|
|
326
|
+
const client = createClient();
|
|
327
|
+
const data = await client.getPipelines();
|
|
328
|
+
|
|
329
|
+
console.log(chalk.cyan("\n🔄 Pipelines:\n"));
|
|
330
|
+
const pipelines = data.pipelines || data || [];
|
|
331
|
+
|
|
332
|
+
for (const p of pipelines) {
|
|
333
|
+
console.log(chalk.white(` ${chalk.bold(p.id?.slice(0, 8) || "?")} ${p.name}`));
|
|
334
|
+
if (p.stages?.length) {
|
|
335
|
+
for (const s of p.stages) {
|
|
336
|
+
console.log(chalk.gray(` └─ ${s.name}`));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
console.log();
|
|
340
|
+
}
|
|
341
|
+
} catch (err) {
|
|
342
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
opportunities
|
|
348
|
+
.command("search [query]")
|
|
349
|
+
.description("Search opportunities")
|
|
350
|
+
.action(async (query) => {
|
|
351
|
+
try {
|
|
352
|
+
const client = createClient();
|
|
353
|
+
const data = await client.searchOpportunities({ query });
|
|
354
|
+
|
|
355
|
+
console.log(chalk.cyan("\n💰 Opportunities:\n"));
|
|
356
|
+
const opps = data.opportunities || data || [];
|
|
357
|
+
|
|
358
|
+
for (const o of opps) {
|
|
359
|
+
const value = o.monetaryValue ? `$${o.monetaryValue}` : "";
|
|
360
|
+
console.log(chalk.white(` ${chalk.bold(o.id?.slice(0, 8) || "?")} ${o.name || o.title || "Untitled"} ${chalk.green(value)}`));
|
|
361
|
+
console.log(chalk.gray(` ${o.pipelineStageId || o.status || ""}\n`));
|
|
362
|
+
}
|
|
363
|
+
} catch (err) {
|
|
364
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
365
|
+
process.exit(1);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
opportunities
|
|
370
|
+
.command("get <id>")
|
|
371
|
+
.description("Get opportunity details")
|
|
372
|
+
.action(async (id) => {
|
|
373
|
+
try {
|
|
374
|
+
const client = createClient();
|
|
375
|
+
const opp = await client.getOpportunity(id);
|
|
376
|
+
|
|
377
|
+
console.log(chalk.cyan(`\n💰 Opportunity: ${opp.name || opp.title || "Untitled"}\n`));
|
|
378
|
+
console.log(chalk.white(` ID: ${opp.id}`));
|
|
379
|
+
console.log(chalk.white(` Value: $${opp.monetaryValue || 0}`));
|
|
380
|
+
console.log(chalk.white(` Status: ${opp.status || "-"}`));
|
|
381
|
+
console.log(chalk.white(` Contact: ${opp.contactId || "-"}`));
|
|
382
|
+
console.log();
|
|
383
|
+
} catch (err) {
|
|
384
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
opportunities
|
|
390
|
+
.command("update <id>")
|
|
391
|
+
.description("Update opportunity")
|
|
392
|
+
.option("--stage <stageId>", "Move to stage")
|
|
393
|
+
.option("--value <amount>", "Set monetary value")
|
|
394
|
+
.option("--status <status>", "Set status (open, won, lost)")
|
|
395
|
+
.action(async (id, options) => {
|
|
396
|
+
try {
|
|
397
|
+
const client = createClient();
|
|
398
|
+
const updates = {};
|
|
399
|
+
if (options.stage) updates.pipelineStageId = options.stage;
|
|
400
|
+
if (options.value) updates.monetaryValue = parseFloat(options.value);
|
|
401
|
+
if (options.status) updates.status = options.status;
|
|
402
|
+
|
|
403
|
+
await client.updateOpportunity(id, updates);
|
|
404
|
+
console.log(chalk.green(`\n✅ Opportunity updated!`));
|
|
405
|
+
} catch (err) {
|
|
406
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
407
|
+
process.exit(1);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// ============ PAYMENTS ============
|
|
412
|
+
const payments = program.command("payments").alias("pay").description("Payments and transactions");
|
|
413
|
+
|
|
414
|
+
payments
|
|
415
|
+
.command("transactions")
|
|
416
|
+
.description("List transactions")
|
|
417
|
+
.option("-l, --limit <n>", "Number of transactions", "20")
|
|
418
|
+
.action(async (options) => {
|
|
419
|
+
try {
|
|
420
|
+
const client = createClient();
|
|
421
|
+
const data = await client.listTransactions({ limit: parseInt(options.limit) });
|
|
422
|
+
|
|
423
|
+
console.log(chalk.cyan("\n💳 Transactions:\n"));
|
|
424
|
+
const txns = data.transactions || data || [];
|
|
425
|
+
|
|
426
|
+
for (const t of txns) {
|
|
427
|
+
const amount = t.amount ? `$${t.amount}` : "";
|
|
428
|
+
const status = t.status === "succeeded" ? chalk.green("✓") : chalk.yellow(t.status || "");
|
|
429
|
+
console.log(chalk.white(` ${status} ${chalk.bold(t.id?.slice(0, 8) || "?")} ${amount}`));
|
|
430
|
+
console.log(chalk.gray(` ${t.createdAt || ""}\n`));
|
|
431
|
+
}
|
|
432
|
+
} catch (err) {
|
|
433
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
434
|
+
process.exit(1);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
payments
|
|
439
|
+
.command("order <id>")
|
|
440
|
+
.description("Get order details")
|
|
441
|
+
.action(async (id) => {
|
|
442
|
+
try {
|
|
443
|
+
const client = createClient();
|
|
444
|
+
const order = await client.getOrder(id);
|
|
445
|
+
|
|
446
|
+
console.log(chalk.cyan(`\n🧾 Order: ${order.id}\n`));
|
|
447
|
+
console.log(chalk.white(` Amount: $${order.amount || 0}`));
|
|
448
|
+
console.log(chalk.white(` Status: ${order.status || "-"}`));
|
|
449
|
+
console.log(chalk.white(` Contact: ${order.contactId || "-"}`));
|
|
450
|
+
console.log(chalk.white(` Created: ${order.createdAt || "-"}`));
|
|
451
|
+
console.log();
|
|
452
|
+
} catch (err) {
|
|
453
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
454
|
+
process.exit(1);
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// ============ LOCATION ============
|
|
459
|
+
const location = program.command("location").alias("loc").description("Location/sub-account info");
|
|
460
|
+
|
|
461
|
+
location
|
|
462
|
+
.command("info")
|
|
463
|
+
.description("Get location details")
|
|
464
|
+
.action(async () => {
|
|
465
|
+
try {
|
|
466
|
+
const client = createClient();
|
|
467
|
+
const loc = await client.getLocation();
|
|
468
|
+
|
|
469
|
+
console.log(chalk.cyan(`\n🏢 Location: ${loc.name || "Unnamed"}\n`));
|
|
470
|
+
console.log(chalk.white(` ID: ${loc.id}`));
|
|
471
|
+
console.log(chalk.white(` Email: ${loc.email || "-"}`));
|
|
472
|
+
console.log(chalk.white(` Phone: ${loc.phone || "-"}`));
|
|
473
|
+
console.log(chalk.white(` Address: ${loc.address || "-"}`));
|
|
474
|
+
console.log(chalk.white(` Timezone: ${loc.timezone || "-"}`));
|
|
475
|
+
console.log();
|
|
476
|
+
} catch (err) {
|
|
477
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
478
|
+
process.exit(1);
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
location
|
|
483
|
+
.command("fields")
|
|
484
|
+
.description("List custom fields")
|
|
485
|
+
.action(async () => {
|
|
486
|
+
try {
|
|
487
|
+
const client = createClient();
|
|
488
|
+
const data = await client.getCustomFields();
|
|
489
|
+
|
|
490
|
+
console.log(chalk.cyan("\n📋 Custom Fields:\n"));
|
|
491
|
+
const fields = data.customFields || data || [];
|
|
492
|
+
|
|
493
|
+
for (const f of fields) {
|
|
494
|
+
console.log(chalk.white(` ${chalk.bold(f.id?.slice(0, 8) || "?")} ${f.name}`));
|
|
495
|
+
console.log(chalk.gray(` Type: ${f.dataType || f.type || "-"}\n`));
|
|
496
|
+
}
|
|
497
|
+
} catch (err) {
|
|
498
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
499
|
+
process.exit(1);
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
// Default to help if no command
|
|
504
|
+
program.parse();
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config management for GHL CLI
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
8
|
+
|
|
9
|
+
const CONFIG_DIR = join(homedir(), ".config", "ghl");
|
|
10
|
+
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
11
|
+
|
|
12
|
+
export function getConfigPath() {
|
|
13
|
+
return CONFIG_FILE;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function loadConfig() {
|
|
17
|
+
try {
|
|
18
|
+
if (existsSync(CONFIG_FILE)) {
|
|
19
|
+
return JSON.parse(readFileSync(CONFIG_FILE, "utf8"));
|
|
20
|
+
}
|
|
21
|
+
} catch (e) {}
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function saveConfig(config) {
|
|
26
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
27
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getToken() {
|
|
33
|
+
if (process.env.GHL_TOKEN) return process.env.GHL_TOKEN;
|
|
34
|
+
return loadConfig().token;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getLocationId() {
|
|
38
|
+
if (process.env.GHL_LOCATION_ID) return process.env.GHL_LOCATION_ID;
|
|
39
|
+
return loadConfig().locationId;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function setCredentials(token, locationId) {
|
|
43
|
+
const config = loadConfig();
|
|
44
|
+
config.token = token;
|
|
45
|
+
config.locationId = locationId;
|
|
46
|
+
saveConfig(config);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function clearCredentials() {
|
|
50
|
+
saveConfig({});
|
|
51
|
+
}
|
package/src/lib/mcp.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GoHighLevel MCP Client
|
|
3
|
+
* Communicates with GHL's MCP server via HTTP Streamable protocol
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getToken, getLocationId } from "./config.js";
|
|
7
|
+
|
|
8
|
+
const MCP_URL = "https://services.leadconnectorhq.com/mcp/";
|
|
9
|
+
|
|
10
|
+
export class GHLClient {
|
|
11
|
+
constructor(token, locationId) {
|
|
12
|
+
this.token = token || getToken();
|
|
13
|
+
this.locationId = locationId || getLocationId();
|
|
14
|
+
|
|
15
|
+
if (!this.token) {
|
|
16
|
+
throw new Error("No GHL token found. Run 'ghl auth' to set up.");
|
|
17
|
+
}
|
|
18
|
+
if (!this.locationId) {
|
|
19
|
+
throw new Error("No location ID found. Run 'ghl auth' to set up.");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async callTool(toolName, args = {}) {
|
|
24
|
+
// MCP uses JSON-RPC style requests
|
|
25
|
+
const request = {
|
|
26
|
+
jsonrpc: "2.0",
|
|
27
|
+
id: Date.now(),
|
|
28
|
+
method: "tools/call",
|
|
29
|
+
params: {
|
|
30
|
+
name: toolName,
|
|
31
|
+
arguments: args
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const response = await fetch(MCP_URL, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: {
|
|
38
|
+
"Content-Type": "application/json",
|
|
39
|
+
"Authorization": `Bearer ${this.token}`,
|
|
40
|
+
"locationId": this.locationId
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify(request)
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
const text = await response.text();
|
|
47
|
+
throw new Error(`GHL MCP error (${response.status}): ${text}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const data = await response.json();
|
|
51
|
+
|
|
52
|
+
if (data.error) {
|
|
53
|
+
throw new Error(`MCP error: ${data.error.message || JSON.stringify(data.error)}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return data.result?.content?.[0]?.text ? JSON.parse(data.result.content[0].text) : data.result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Contacts
|
|
60
|
+
async getContacts(query = {}) {
|
|
61
|
+
return this.callTool("contacts_get-contacts", query);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async getContact(contactId) {
|
|
65
|
+
return this.callTool("contacts_get-contact", { contactId });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async createContact(data) {
|
|
69
|
+
return this.callTool("contacts_create-contact", data);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async updateContact(contactId, data) {
|
|
73
|
+
return this.callTool("contacts_update-contact", { contactId, ...data });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async addTags(contactId, tags) {
|
|
77
|
+
return this.callTool("contacts_add-tags", { contactId, tags });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async removeTags(contactId, tags) {
|
|
81
|
+
return this.callTool("contacts_remove-tags", { contactId, tags });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async getTasks(contactId) {
|
|
85
|
+
return this.callTool("contacts_get-all-tasks", { contactId });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Conversations
|
|
89
|
+
async searchConversations(query = {}) {
|
|
90
|
+
return this.callTool("conversations_search-conversation", query);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async getMessages(conversationId) {
|
|
94
|
+
return this.callTool("conversations_get-messages", { conversationId });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async sendMessage(conversationId, message, type = "SMS") {
|
|
98
|
+
return this.callTool("conversations_send-a-new-message", {
|
|
99
|
+
conversationId,
|
|
100
|
+
message,
|
|
101
|
+
type
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Calendar
|
|
106
|
+
async getCalendarEvents(params = {}) {
|
|
107
|
+
return this.callTool("calendars_get-calendar-events", params);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async getAppointmentNotes(appointmentId) {
|
|
111
|
+
return this.callTool("calendars_get-appointment-notes", { appointmentId });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Opportunities
|
|
115
|
+
async getPipelines() {
|
|
116
|
+
return this.callTool("opportunities_get-pipelines", {});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async searchOpportunities(query = {}) {
|
|
120
|
+
return this.callTool("opportunities_search-opportunity", query);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async getOpportunity(opportunityId) {
|
|
124
|
+
return this.callTool("opportunities_get-opportunity", { id: opportunityId });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async updateOpportunity(opportunityId, data) {
|
|
128
|
+
return this.callTool("opportunities_update-opportunity", { id: opportunityId, ...data });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Payments
|
|
132
|
+
async listTransactions(params = {}) {
|
|
133
|
+
return this.callTool("payments_list-transactions", params);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async getOrder(orderId) {
|
|
137
|
+
return this.callTool("payments_get-order-by-id", { orderId });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Location
|
|
141
|
+
async getLocation() {
|
|
142
|
+
return this.callTool("locations_get-location", { locationId: this.locationId });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async getCustomFields() {
|
|
146
|
+
return this.callTool("locations_get-custom-fields", { locationId: this.locationId });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function createClient() {
|
|
151
|
+
return new GHLClient();
|
|
152
|
+
}
|