@mangomagic/cli 0.1.12 → 0.1.14
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/package.json +1 -1
- package/src/chat/natural-language.mjs +283 -5
- package/src/index.mjs +11 -4
- package/src/tools/catalog.mjs +3 -3
- package/src/tools/run.mjs +59 -0
package/package.json
CHANGED
|
@@ -2,13 +2,25 @@ import readline from "node:readline/promises";
|
|
|
2
2
|
import { stdin as input, stdout as output } from "node:process";
|
|
3
3
|
import { apiCall } from "../api.mjs";
|
|
4
4
|
import { planWithKimi } from "../ai/kimi.mjs";
|
|
5
|
-
import { ALL_MCP_TOOL_CATALOG } from "../tools/catalog.mjs";
|
|
6
5
|
import { runCatalogTool } from "../tools/run.mjs";
|
|
7
6
|
|
|
8
7
|
const GOLD = "\x1b[38;2;241;171;28m";
|
|
9
8
|
const DIM = "\x1b[2m";
|
|
10
9
|
const BOLD = "\x1b[1m";
|
|
11
10
|
const RESET = "\x1b[0m";
|
|
11
|
+
const CHAT_TOOL_NAMES = [
|
|
12
|
+
"create_talking_cards",
|
|
13
|
+
"list_episodes",
|
|
14
|
+
"search_episodes",
|
|
15
|
+
"get_episode",
|
|
16
|
+
"get_user_episodes",
|
|
17
|
+
"get_user_clips",
|
|
18
|
+
"get_user_leads",
|
|
19
|
+
"get_user_stats",
|
|
20
|
+
"get_upcoming_meetings",
|
|
21
|
+
"get_bookings",
|
|
22
|
+
"get_inbox",
|
|
23
|
+
];
|
|
12
24
|
|
|
13
25
|
function compact(s) {
|
|
14
26
|
return String(s || "").trim().replace(/\s+/g, " ");
|
|
@@ -42,6 +54,27 @@ function localPlan(text) {
|
|
|
42
54
|
if (t.includes("talking card") || t.includes("carousel") || /\bcards?\b/.test(t)) {
|
|
43
55
|
return { action: "create_talking_cards", args: { focus: raw, count: inferCount(raw) } };
|
|
44
56
|
}
|
|
57
|
+
if (/\b(email|emails|inbox|messages?)\b/.test(t) && /\b(show|list|get|what|my|check)\b/.test(t)) {
|
|
58
|
+
return { action: "list_inbox", args: { limit: 5 } };
|
|
59
|
+
}
|
|
60
|
+
if (/\b(stats?|account|credits?|streak|level|usage)\b/.test(t) && /\b(show|get|what|my|how|check)\b/.test(t)) {
|
|
61
|
+
return { action: "account_stats", args: {} };
|
|
62
|
+
}
|
|
63
|
+
if (/\b(clips?|highlights?)\b/.test(t) && /\b(show|list|get|my|what)\b/.test(t)) {
|
|
64
|
+
return { action: "list_clips", args: { limit: 5 } };
|
|
65
|
+
}
|
|
66
|
+
if (/\b(guests?|leads?|contacts?)\b/.test(t) && /\b(analy[sz]e|qualify|score|best|priority|prioritise|prioritize)\b/.test(t)) {
|
|
67
|
+
return { action: "analyze_guests", args: { limit: 10 } };
|
|
68
|
+
}
|
|
69
|
+
if (/\b(guests?|leads?|contacts?)\b/.test(t) && /\b(show|list|get|my|what)\b/.test(t)) {
|
|
70
|
+
return { action: "list_leads", args: { limit: 8 } };
|
|
71
|
+
}
|
|
72
|
+
if (/\b(meetings?|recording sessions?|sessions?)\b/.test(t) && /\b(show|list|get|upcoming|my|what)\b/.test(t)) {
|
|
73
|
+
return { action: "list_meetings", args: { limit: 5 } };
|
|
74
|
+
}
|
|
75
|
+
if (/\b(bookings?|booking requests?)\b/.test(t) && /\b(show|list|get|my|what)\b/.test(t)) {
|
|
76
|
+
return { action: "list_bookings", args: { limit: 5 } };
|
|
77
|
+
}
|
|
45
78
|
if (t.includes("mcp") && (t.includes("config") || t.includes("connect") || t.includes("claude") || t.includes("cursor") || t.includes("codex"))) {
|
|
46
79
|
return { action: "mcp_config", args: {} };
|
|
47
80
|
}
|
|
@@ -80,6 +113,28 @@ function asArray(data) {
|
|
|
80
113
|
return [];
|
|
81
114
|
}
|
|
82
115
|
|
|
116
|
+
function items(data) {
|
|
117
|
+
if (Array.isArray(data?.items)) return data.items;
|
|
118
|
+
return asArray(data).map((row) => ({
|
|
119
|
+
id: row.id,
|
|
120
|
+
title: row.title || row.name || [row.first_name, row.last_name].filter(Boolean).join(" ") || row.subject || row.id,
|
|
121
|
+
subtitle: row.subtitle || row.company || row.participant_email || row.status || "",
|
|
122
|
+
link: row.link,
|
|
123
|
+
metadata: row.metadata || row,
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function formatDate(value) {
|
|
128
|
+
if (!value) return "";
|
|
129
|
+
const date = new Date(value);
|
|
130
|
+
if (Number.isNaN(date.getTime())) return "";
|
|
131
|
+
return date.toLocaleDateString("en-AU", { month: "short", day: "numeric", year: "numeric" });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function plural(count, singular, pluralName = `${singular}s`) {
|
|
135
|
+
return `${count} ${count === 1 ? singular : pluralName}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
83
138
|
function printEpisodeList(data) {
|
|
84
139
|
const rows = asArray(data);
|
|
85
140
|
if (!rows.length) {
|
|
@@ -93,6 +148,177 @@ function printEpisodeList(data) {
|
|
|
93
148
|
}
|
|
94
149
|
}
|
|
95
150
|
|
|
151
|
+
function printInbox(data) {
|
|
152
|
+
const rows = asArray(data);
|
|
153
|
+
if (!rows.length) {
|
|
154
|
+
process.stdout.write("No MangoMagic inbox threads found.\n");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const unread = rows.reduce((sum, row) => sum + Number(row.unread_count || 0), 0);
|
|
158
|
+
process.stdout.write(`${BOLD}MangoMagic inbox:${RESET} ${plural(rows.length, "thread")} shown${unread ? `, ${plural(unread, "unread message")}` : ""}.\n\n`);
|
|
159
|
+
for (const row of rows.slice(0, 8)) {
|
|
160
|
+
const sender = row.participant_name || row.participant_email || "Unknown sender";
|
|
161
|
+
const unreadText = Number(row.unread_count || 0) ? `${row.unread_count} unread` : "read";
|
|
162
|
+
const when = formatDate(row.last_message_at);
|
|
163
|
+
process.stdout.write(` ${GOLD}${row.subject || "Untitled thread"}${RESET}\n`);
|
|
164
|
+
process.stdout.write(` ${DIM}${sender} • ${unreadText}${when ? ` • ${when}` : ""}${RESET}\n`);
|
|
165
|
+
}
|
|
166
|
+
process.stdout.write(`\n${DIM}This is your MangoMagic inbox. Ask "show my stats" or "analyse my guests" for the next useful snapshot.${RESET}\n`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function printStats(data) {
|
|
170
|
+
const stats = data?.data || data;
|
|
171
|
+
if (!stats || typeof stats !== "object") {
|
|
172
|
+
process.stdout.write("No account stats found.\n");
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
process.stdout.write(`${BOLD}Account snapshot${RESET}\n`);
|
|
176
|
+
process.stdout.write(` Level: ${GOLD}${stats.level ?? "?"}${RESET}${stats.xp_points != null ? ` (${stats.xp_points} XP)` : ""}\n`);
|
|
177
|
+
process.stdout.write(` Streak: ${GOLD}${stats.current_streak ?? 0}${RESET} days\n`);
|
|
178
|
+
process.stdout.write(` Recordings: ${GOLD}${stats.total_recordings ?? 0}${RESET}${stats.total_minutes_recorded != null ? ` • ${stats.total_minutes_recorded} minutes` : ""}\n`);
|
|
179
|
+
process.stdout.write(` Clips: ${GOLD}${stats.total_clips_created ?? 0}${RESET}\n`);
|
|
180
|
+
process.stdout.write(` Credits: ${GOLD}${stats.credit_balance ?? stats.episodes_remaining ?? "?"}${RESET}\n`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function printClips(data) {
|
|
184
|
+
const rows = asArray(data);
|
|
185
|
+
if (!rows.length) {
|
|
186
|
+
process.stdout.write("No clips found yet.\n");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const failed = rows.filter((row) => row.status === "failed").length;
|
|
190
|
+
process.stdout.write(`${BOLD}Recent clips${RESET}${failed ? ` ${DIM}(${failed} need attention)${RESET}` : ""}\n\n`);
|
|
191
|
+
for (const clip of rows.slice(0, 8)) {
|
|
192
|
+
const status = clip.status || clip.metadata?.status || "unknown";
|
|
193
|
+
const views = clip.view_count ?? clip.metadata?.view_count ?? 0;
|
|
194
|
+
const link = clip.slug ? `https://mangomagic.live/clips/${clip.slug}` : clip.link ? `https://mangomagic.live${clip.link}` : "";
|
|
195
|
+
process.stdout.write(` ${GOLD}${clip.title || "Untitled clip"}${RESET}\n`);
|
|
196
|
+
process.stdout.write(` ${DIM}${status} • ${plural(Number(views), "view")}${link ? ` • ${link}` : ""}${RESET}\n`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function leadName(row) {
|
|
201
|
+
return row.title || [row.first_name, row.last_name].filter(Boolean).join(" ") || row.name || row.email || row.id || "Unnamed lead";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function printLeads(data) {
|
|
205
|
+
const rows = asArray(data);
|
|
206
|
+
if (!rows.length) {
|
|
207
|
+
process.stdout.write("No guests or leads found yet.\n");
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
process.stdout.write(`${BOLD}Guests and leads${RESET}\n\n`);
|
|
211
|
+
for (const lead of rows.slice(0, 10)) {
|
|
212
|
+
const role = [lead.position, lead.company].filter(Boolean).join(" at ");
|
|
213
|
+
const stage = lead.stage || lead.metadata?.stage || "unknown stage";
|
|
214
|
+
const enrichment = lead.enrichment_status || lead.metadata?.enrichment_status;
|
|
215
|
+
process.stdout.write(` ${GOLD}${leadName(lead)}${RESET}\n`);
|
|
216
|
+
process.stdout.write(` ${DIM}${[role, stage, enrichment].filter(Boolean).join(" • ")}${RESET}\n`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function printGuestAnalysis(data) {
|
|
221
|
+
const rows = asArray(data);
|
|
222
|
+
if (!rows.length) {
|
|
223
|
+
process.stdout.write("I could not find guests or leads to analyse yet.\n");
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const stageCounts = new Map();
|
|
228
|
+
const pending = [];
|
|
229
|
+
const ready = [];
|
|
230
|
+
for (const lead of rows) {
|
|
231
|
+
const stage = lead.stage || "unknown";
|
|
232
|
+
stageCounts.set(stage, (stageCounts.get(stage) || 0) + 1);
|
|
233
|
+
if ((lead.enrichment_status || "").toLowerCase() === "pending") pending.push(lead);
|
|
234
|
+
if (lead.email || lead.profile_url) ready.push(lead);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
process.stdout.write(`${BOLD}Guest pipeline snapshot${RESET}\n`);
|
|
238
|
+
process.stdout.write(` Analysed: ${GOLD}${plural(rows.length, "guest")}${RESET}\n`);
|
|
239
|
+
process.stdout.write(` Contactable now: ${GOLD}${ready.length}${RESET}\n`);
|
|
240
|
+
process.stdout.write(` Need enrichment: ${GOLD}${pending.length}${RESET}\n`);
|
|
241
|
+
if (stageCounts.size) {
|
|
242
|
+
const summary = [...stageCounts.entries()].map(([stage, count]) => `${stage}: ${count}`).join(", ");
|
|
243
|
+
process.stdout.write(` Stages: ${DIM}${summary}${RESET}\n`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
process.stdout.write(`\n${BOLD}Best immediate moves${RESET}\n`);
|
|
247
|
+
for (const lead of ready.slice(0, 5)) {
|
|
248
|
+
const contact = lead.email || lead.profile_url || "contact details available";
|
|
249
|
+
process.stdout.write(` ${GOLD}${leadName(lead)}${RESET} ${DIM}${contact}${RESET}\n`);
|
|
250
|
+
}
|
|
251
|
+
const lead = leadName(ready[0] || pending[0]);
|
|
252
|
+
process.stdout.write(`\n${DIM}Next: ask "show me my leads" or "create talking cards about why ${lead} should join my podcast".${RESET}\n`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function printMeetings(data) {
|
|
256
|
+
const rows = asArray(data);
|
|
257
|
+
if (!rows.length) {
|
|
258
|
+
process.stdout.write("No upcoming meetings found.\n");
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
process.stdout.write(`${BOLD}Upcoming meetings${RESET}\n\n`);
|
|
262
|
+
for (const meeting of rows.slice(0, 8)) {
|
|
263
|
+
const when = formatDate(meeting.scheduled_at || meeting.scheduledAt || meeting.start_time);
|
|
264
|
+
process.stdout.write(` ${GOLD}${meeting.title || meeting.name || "Untitled meeting"}${RESET}\n`);
|
|
265
|
+
process.stdout.write(` ${DIM}${[when, meeting.status, meeting.guest_email || meeting.guestEmail].filter(Boolean).join(" • ")}${RESET}\n`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function printBookings(data) {
|
|
270
|
+
const rows = asArray(data);
|
|
271
|
+
if (!rows.length) {
|
|
272
|
+
process.stdout.write("No bookings found.\n");
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
process.stdout.write(`${BOLD}Bookings${RESET}\n\n`);
|
|
276
|
+
for (const booking of rows.slice(0, 8)) {
|
|
277
|
+
const when = formatDate(booking.scheduled_at || booking.created_at);
|
|
278
|
+
process.stdout.write(` ${GOLD}${booking.title || booking.name || booking.guest_name || "Booking"}${RESET}\n`);
|
|
279
|
+
process.stdout.write(` ${DIM}${[booking.status, when, booking.email || booking.guest_email].filter(Boolean).join(" • ")}${RESET}\n`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function printGenericResult(name, data) {
|
|
284
|
+
const rows = items(data);
|
|
285
|
+
if (rows.length) {
|
|
286
|
+
process.stdout.write(`${BOLD}${name}${RESET}\n\n`);
|
|
287
|
+
for (const row of rows.slice(0, 10)) {
|
|
288
|
+
process.stdout.write(` ${GOLD}${row.title || row.id || "Result"}${RESET}\n`);
|
|
289
|
+
if (row.subtitle) process.stdout.write(` ${DIM}${row.subtitle}${RESET}\n`);
|
|
290
|
+
if (row.link) process.stdout.write(` ${DIM}https://mangomagic.live${row.link}${RESET}\n`);
|
|
291
|
+
}
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const message = data?.message || data?.summary || data?.status;
|
|
295
|
+
process.stdout.write(message ? `${message}\n` : `${BOLD}Done.${RESET}\n`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function printToolResult(name, data) {
|
|
299
|
+
switch (name) {
|
|
300
|
+
case "get_inbox":
|
|
301
|
+
return printInbox(data);
|
|
302
|
+
case "get_user_stats":
|
|
303
|
+
return printStats(data);
|
|
304
|
+
case "get_user_clips":
|
|
305
|
+
return printClips(data);
|
|
306
|
+
case "get_user_leads":
|
|
307
|
+
return printLeads(data);
|
|
308
|
+
case "get_upcoming_meetings":
|
|
309
|
+
return printMeetings(data);
|
|
310
|
+
case "get_bookings":
|
|
311
|
+
return printBookings(data);
|
|
312
|
+
case "get_user_episodes":
|
|
313
|
+
case "list_episodes":
|
|
314
|
+
case "search_user_episodes":
|
|
315
|
+
case "search_episodes":
|
|
316
|
+
return printEpisodeList(data);
|
|
317
|
+
default:
|
|
318
|
+
return printGenericResult(name, data);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
96
322
|
function printEpisode(data) {
|
|
97
323
|
const ep = data?.episode || data?.data || data;
|
|
98
324
|
if (!ep || typeof ep !== "object") {
|
|
@@ -166,12 +392,17 @@ export async function handleNaturalLanguage(text, actions, { allowModel = true }
|
|
|
166
392
|
plan = await planWithKimi(text, {
|
|
167
393
|
availableTools: [
|
|
168
394
|
"create_talking_cards",
|
|
169
|
-
"
|
|
170
|
-
"
|
|
395
|
+
"list_inbox",
|
|
396
|
+
"list_leads",
|
|
397
|
+
"analyze_guests",
|
|
398
|
+
"list_clips",
|
|
399
|
+
"account_stats",
|
|
400
|
+
"list_meetings",
|
|
401
|
+
"list_bookings",
|
|
171
402
|
"show_tools",
|
|
172
403
|
"mcp_config",
|
|
173
404
|
"home",
|
|
174
|
-
...
|
|
405
|
+
...CHAT_TOOL_NAMES,
|
|
175
406
|
],
|
|
176
407
|
});
|
|
177
408
|
} catch (err) {
|
|
@@ -210,6 +441,34 @@ ${DIM}${err?.message ?? err}${RESET}
|
|
|
210
441
|
return actions.mcpConfig();
|
|
211
442
|
case "home":
|
|
212
443
|
return actions.home();
|
|
444
|
+
case "list_inbox": {
|
|
445
|
+
const data = await runCatalogTool("get_inbox", { limit: Math.max(1, Math.min(Number(args.limit || 5), 10)) });
|
|
446
|
+
return printInbox(data);
|
|
447
|
+
}
|
|
448
|
+
case "account_stats": {
|
|
449
|
+
const data = await runCatalogTool("get_user_stats", {});
|
|
450
|
+
return printStats(data);
|
|
451
|
+
}
|
|
452
|
+
case "list_clips": {
|
|
453
|
+
const data = await runCatalogTool("get_user_clips", { limit: Math.max(1, Math.min(Number(args.limit || 5), 10)) });
|
|
454
|
+
return printClips(data);
|
|
455
|
+
}
|
|
456
|
+
case "list_leads": {
|
|
457
|
+
const data = await runCatalogTool("get_user_leads", { limit: Math.max(1, Math.min(Number(args.limit || 8), 20)) });
|
|
458
|
+
return printLeads(data);
|
|
459
|
+
}
|
|
460
|
+
case "analyze_guests": {
|
|
461
|
+
const data = await runCatalogTool("get_user_leads", { limit: Math.max(1, Math.min(Number(args.limit || 10), 20)) });
|
|
462
|
+
return printGuestAnalysis(data);
|
|
463
|
+
}
|
|
464
|
+
case "list_meetings": {
|
|
465
|
+
const data = await runCatalogTool("get_upcoming_meetings", { limit: Math.max(1, Math.min(Number(args.limit || 5), 10)) });
|
|
466
|
+
return printMeetings(data);
|
|
467
|
+
}
|
|
468
|
+
case "list_bookings": {
|
|
469
|
+
const data = await runCatalogTool("get_bookings", { limit: Math.max(1, Math.min(Number(args.limit || 5), 10)) });
|
|
470
|
+
return printBookings(data);
|
|
471
|
+
}
|
|
213
472
|
case "list_episodes": {
|
|
214
473
|
const data = await apiCall("cli-list-episodes", { body: { limit: Math.max(1, Math.min(Number(args.limit || 5), 10)) } });
|
|
215
474
|
return printEpisodeList(data);
|
|
@@ -235,10 +494,29 @@ ${DIM}${err?.message ?? err}${RESET}
|
|
|
235
494
|
process.stdout.write("Which tool should I run?\n");
|
|
236
495
|
return;
|
|
237
496
|
}
|
|
497
|
+
if (String(args.toolName).startsWith("edge_")) {
|
|
498
|
+
process.stdout.write(`That is a developer/admin edge function. In chat mode I only run value-facing actions.\n`);
|
|
499
|
+
process.stdout.write(`Use ${GOLD}npx -y @mangomagic/cli tool ${args.toolName} '{"dryRun":true}'${RESET} if you want the raw operational tool.\n`);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
238
502
|
const data = await runCatalogTool(args.toolName, args.args || {});
|
|
239
|
-
|
|
503
|
+
printToolResult(args.toolName, data);
|
|
240
504
|
return;
|
|
241
505
|
}
|
|
506
|
+
case "get_inbox":
|
|
507
|
+
case "get_user_stats":
|
|
508
|
+
case "get_user_clips":
|
|
509
|
+
case "get_user_leads":
|
|
510
|
+
case "get_upcoming_meetings":
|
|
511
|
+
case "get_bookings":
|
|
512
|
+
case "get_user_episodes": {
|
|
513
|
+
const data = await runCatalogTool(action, args);
|
|
514
|
+
return printToolResult(action, data);
|
|
515
|
+
}
|
|
516
|
+
case "qualify_leads": {
|
|
517
|
+
const data = await runCatalogTool("get_user_leads", { limit: Math.max(1, Math.min(Number(args.limit || 10), 20)) });
|
|
518
|
+
return printGuestAnalysis(data);
|
|
519
|
+
}
|
|
242
520
|
case "answer":
|
|
243
521
|
process.stdout.write(`${args.text || "I can help with MangoMagic episodes, talking cards, and MCP setup."}\n`);
|
|
244
522
|
return;
|
package/src/index.mjs
CHANGED
|
@@ -44,9 +44,10 @@ function sessionIntro() {
|
|
|
44
44
|
${BOLD}You can type naturally now.${RESET}
|
|
45
45
|
|
|
46
46
|
Try:
|
|
47
|
-
${GOLD}what episodes do I have?${RESET}
|
|
48
47
|
${GOLD}create 3 talking cards about founder-led sales${RESET}
|
|
49
|
-
${GOLD}
|
|
48
|
+
${GOLD}analyse my guests${RESET}
|
|
49
|
+
${GOLD}show me my clips${RESET}
|
|
50
|
+
${GOLD}what episodes do I have?${RESET}
|
|
50
51
|
|
|
51
52
|
${DIM}Type ${BOLD}exit${RESET}${DIM} to leave. Add ${BOLD}--home${RESET}${DIM} next time if you only want the menu.${RESET}
|
|
52
53
|
`);
|
|
@@ -98,6 +99,12 @@ ${DIM}Core tools available now:${RESET}
|
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
process.stdout.write(`
|
|
102
|
+
${DIM}Value actions that work in chat now:${RESET}
|
|
103
|
+
${GOLD}analyse my guests${RESET}
|
|
104
|
+
${GOLD}show me my clips${RESET}
|
|
105
|
+
${GOLD}show me my emails${RESET}
|
|
106
|
+
${GOLD}show me my stats${RESET}
|
|
107
|
+
|
|
101
108
|
${DIM}Catalog:${RESET} ${ALL_MCP_TOOL_CATALOG.length} tools total (${MCP_TOOL_CATALOG.length} core, ${ALL_MCP_TOOL_CATALOG.length - MCP_TOOL_CATALOG.length} business/admin/raw tools).
|
|
102
109
|
${DIM}Run:${RESET} ${GOLD}${COMMAND_PREFIX} tool get_user_stats${RESET}
|
|
103
110
|
${DIM}Run with JSON:${RESET} ${GOLD}${COMMAND_PREFIX} tool get_user_episodes '{"limit":3}'${RESET}
|
|
@@ -120,14 +127,14 @@ ${DIM}Full list:${RESET} ${GOLD}${COMMAND_PREFIX} tools --all${RESET}
|
|
|
120
127
|
}
|
|
121
128
|
|
|
122
129
|
process.stdout.write(`
|
|
123
|
-
${DIM}
|
|
130
|
+
${DIM}High-value workflows:${RESET}
|
|
124
131
|
`);
|
|
125
132
|
for (const workflow of NEXT_WORKFLOWS) {
|
|
126
133
|
process.stdout.write(` ${GOLD}${workflow.name.padEnd(28)}${RESET} ${workflow.description}\n`);
|
|
127
134
|
}
|
|
128
135
|
|
|
129
136
|
process.stdout.write(`
|
|
130
|
-
${DIM}Use
|
|
137
|
+
${DIM}Use \`${COMMAND_PREFIX} mcp-config\` to connect an MCP client.${RESET}
|
|
131
138
|
`);
|
|
132
139
|
}
|
|
133
140
|
|
package/src/tools/catalog.mjs
CHANGED
|
@@ -91,14 +91,14 @@ export const QUICK_WINS = [
|
|
|
91
91
|
export const NEXT_WORKFLOWS = [
|
|
92
92
|
{
|
|
93
93
|
name: "create_talking_cards",
|
|
94
|
-
description: "Generate a batch of branded talking cards from a short idea
|
|
94
|
+
description: "Generate a batch of branded talking cards from a short idea and save them to the user's carousel library.",
|
|
95
95
|
},
|
|
96
96
|
{
|
|
97
97
|
name: "sync_linkedin_brand_context",
|
|
98
|
-
description: "Use LinkedIn profile context to draft or refresh the user's Brand Doc.",
|
|
98
|
+
description: "Use LinkedIn profile context to draft or refresh the user's Brand Doc. Coming next.",
|
|
99
99
|
},
|
|
100
100
|
{
|
|
101
101
|
name: "publish_talking_card",
|
|
102
|
-
description: "Publish an approved card to LinkedIn or prepare the post for manual approval.",
|
|
102
|
+
description: "Publish an approved card to LinkedIn or prepare the post for manual approval. Coming next.",
|
|
103
103
|
},
|
|
104
104
|
];
|
package/src/tools/run.mjs
CHANGED
|
@@ -2,6 +2,56 @@ import { apiCall } from "../api.mjs";
|
|
|
2
2
|
import { ALL_MCP_TOOL_CATALOG } from "./catalog.mjs";
|
|
3
3
|
import { functionNameFromEdgeTool } from "./edge-functions.mjs";
|
|
4
4
|
|
|
5
|
+
function asArray(data) {
|
|
6
|
+
if (Array.isArray(data)) return data;
|
|
7
|
+
if (Array.isArray(data?.data)) return data.data;
|
|
8
|
+
if (Array.isArray(data?.items)) return data.items;
|
|
9
|
+
if (Array.isArray(data?.results)) return data.results;
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function leadName(lead) {
|
|
14
|
+
return [lead.first_name, lead.last_name].filter(Boolean).join(" ") || lead.title || lead.name || lead.email || lead.id || "Unnamed lead";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function qualifyFromLeads(result) {
|
|
18
|
+
const leads = asArray(result);
|
|
19
|
+
const recommendations = leads.map((lead) => {
|
|
20
|
+
const hasEmail = Boolean(lead.email || lead.metadata?.email);
|
|
21
|
+
const hasProfile = Boolean(lead.profile_url || lead.metadata?.profile_url);
|
|
22
|
+
const enrichmentStatus = lead.enrichment_status || lead.metadata?.enrichment_status || "unknown";
|
|
23
|
+
const score = (hasEmail ? 45 : 0) + (hasProfile ? 30 : 0) + (enrichmentStatus === "pending" ? 10 : 20);
|
|
24
|
+
return {
|
|
25
|
+
id: lead.id,
|
|
26
|
+
name: leadName(lead),
|
|
27
|
+
email: lead.email || lead.metadata?.email || null,
|
|
28
|
+
profileUrl: lead.profile_url || lead.metadata?.profile_url || null,
|
|
29
|
+
stage: lead.stage || lead.metadata?.stage || null,
|
|
30
|
+
enrichmentStatus,
|
|
31
|
+
score: Math.min(score, 100),
|
|
32
|
+
nextAction: enrichmentStatus === "pending" ? "Enrich this guest, then draft outreach." : "Draft outreach or invite to a recording.",
|
|
33
|
+
};
|
|
34
|
+
}).sort((a, b) => b.score - a.score);
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
tool: "qualify_leads",
|
|
38
|
+
data: {
|
|
39
|
+
analysed: recommendations.length,
|
|
40
|
+
contactable: recommendations.filter((lead) => lead.email || lead.profileUrl).length,
|
|
41
|
+
needsEnrichment: recommendations.filter((lead) => lead.enrichmentStatus === "pending").length,
|
|
42
|
+
recommendations,
|
|
43
|
+
},
|
|
44
|
+
items: recommendations.slice(0, 10).map((lead) => ({
|
|
45
|
+
type: "lead_recommendation",
|
|
46
|
+
id: lead.id,
|
|
47
|
+
title: `${lead.name} (${lead.score}/100)`,
|
|
48
|
+
subtitle: lead.nextAction,
|
|
49
|
+
link: lead.id ? `/leads?id=${lead.id}` : undefined,
|
|
50
|
+
metadata: lead,
|
|
51
|
+
})),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
5
55
|
const BUILTIN_TOOL_HANDLERS = {
|
|
6
56
|
list_episodes: async ({ limit = 10 }) => apiCall("cli-list-episodes", { body: { limit } }),
|
|
7
57
|
get_episode: async ({ episode }) => apiCall("cli-get-episode", { body: { episode } }),
|
|
@@ -12,6 +62,15 @@ const BUILTIN_TOOL_HANDLERS = {
|
|
|
12
62
|
count: Math.max(1, Math.min(Number(count || 3), 10)),
|
|
13
63
|
},
|
|
14
64
|
}),
|
|
65
|
+
qualify_leads: async ({ stage = "all", limit = 10 } = {}) => {
|
|
66
|
+
const leads = await apiCall("magic-assistant", {
|
|
67
|
+
body: {
|
|
68
|
+
toolName: "get_user_leads",
|
|
69
|
+
args: { stage, limit: Math.max(1, Math.min(Number(limit || 10), 20)) },
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
return qualifyFromLeads(leads);
|
|
73
|
+
},
|
|
15
74
|
};
|
|
16
75
|
|
|
17
76
|
export function findTool(name) {
|