@mangomagic/cli 0.1.11 → 0.1.13

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mangomagic/cli",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "MangoMagic CLI — sign in, manage episodes, and expose MangoMagic to MCP clients (Claude Desktop, Cursor).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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, " ");
@@ -20,21 +32,66 @@ function localPlan(text) {
20
32
  if (!t) return { action: "answer", args: { text: "" } };
21
33
 
22
34
  if (/^(exit|quit|bye)$/i.test(t)) return { action: "exit", args: {} };
35
+ if (/^(hi|hey|hello|yo)\??$/i.test(t)) {
36
+ return {
37
+ action: "answer",
38
+ args: {
39
+ text: "Hey. I'm here in the terminal now. Ask for episodes, talking cards, tools, or MCP setup.",
40
+ },
41
+ };
42
+ }
43
+ if (t.includes("understand me")) {
44
+ return {
45
+ action: "answer",
46
+ args: {
47
+ text: "Yes. Type at the mango> prompt and I will route natural language to MangoMagic tools.",
48
+ },
49
+ };
50
+ }
23
51
  if (t.includes("brand doc") || t.includes("brand colour") || t.includes("brand color")) {
24
52
  return { action: "open_brand_doc", args: {} };
25
53
  }
26
54
  if (t.includes("talking card") || t.includes("carousel") || /\bcards?\b/.test(t)) {
27
55
  return { action: "create_talking_cards", args: { focus: raw, count: inferCount(raw) } };
28
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
+ }
29
78
  if (t.includes("mcp") && (t.includes("config") || t.includes("connect") || t.includes("claude") || t.includes("cursor") || t.includes("codex"))) {
30
79
  return { action: "mcp_config", args: {} };
31
80
  }
32
- if (t.includes("tool") || t.includes("what can")) return { action: "show_tools", args: {} };
33
- if (t.includes("latest episode") || t.includes("recent episode") || t === "episodes" || t.includes("list episodes")) {
34
- return { action: "list_episodes", args: { limit: 5 } };
81
+ if (t.includes("tool") || t.includes("what can")) {
82
+ return { action: "show_tools", args: { all: /\b(all|every|full|complete)\b/.test(t) } };
35
83
  }
36
84
  const searchMatch = raw.match(/search(?: my)? episodes? (?:for|about)\s+(.+)/i);
37
85
  if (searchMatch?.[1]) return { action: "search_episodes", args: { query: searchMatch[1] } };
86
+ if (
87
+ t.includes("latest episode") ||
88
+ t.includes("recent episode") ||
89
+ t === "episodes" ||
90
+ t.includes("list episodes") ||
91
+ (/\bepisodes?\b/.test(t) && /\b(what|which|have|got|my|show)\b/.test(t))
92
+ ) {
93
+ return { action: "list_episodes", args: { limit: 5 } };
94
+ }
38
95
  const getMatch = raw.match(/(?:get|show|open)(?: episode)?\s+([a-z0-9][a-z0-9_-]{5,})/i);
39
96
  if (getMatch?.[1]) return { action: "get_episode", args: { episode: getMatch[1] } };
40
97
  if (t.includes("home") || t.includes("help")) return { action: "home", args: {} };
@@ -56,6 +113,28 @@ function asArray(data) {
56
113
  return [];
57
114
  }
58
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
+
59
138
  function printEpisodeList(data) {
60
139
  const rows = asArray(data);
61
140
  if (!rows.length) {
@@ -69,6 +148,177 @@ function printEpisodeList(data) {
69
148
  }
70
149
  }
71
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
+
72
322
  function printEpisode(data) {
73
323
  const ep = data?.episode || data?.data || data;
74
324
  if (!ep || typeof ep !== "object") {
@@ -142,12 +392,17 @@ export async function handleNaturalLanguage(text, actions, { allowModel = true }
142
392
  plan = await planWithKimi(text, {
143
393
  availableTools: [
144
394
  "create_talking_cards",
145
- "open_cards",
146
- "open_brand_doc",
395
+ "list_inbox",
396
+ "list_leads",
397
+ "analyze_guests",
398
+ "list_clips",
399
+ "account_stats",
400
+ "list_meetings",
401
+ "list_bookings",
147
402
  "show_tools",
148
403
  "mcp_config",
149
404
  "home",
150
- ...ALL_MCP_TOOL_CATALOG.map((tool) => tool.name),
405
+ ...CHAT_TOOL_NAMES,
151
406
  ],
152
407
  });
153
408
  } catch (err) {
@@ -181,11 +436,39 @@ ${DIM}${err?.message ?? err}${RESET}
181
436
  case "open_brand_doc":
182
437
  return actions.openPath("/carousels/doc");
183
438
  case "show_tools":
184
- return actions.tools();
439
+ return actions.tools({ all: Boolean(args.all) });
185
440
  case "mcp_config":
186
441
  return actions.mcpConfig();
187
442
  case "home":
188
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
+ }
189
472
  case "list_episodes": {
190
473
  const data = await apiCall("cli-list-episodes", { body: { limit: Math.max(1, Math.min(Number(args.limit || 5), 10)) } });
191
474
  return printEpisodeList(data);
@@ -211,10 +494,29 @@ ${DIM}${err?.message ?? err}${RESET}
211
494
  process.stdout.write("Which tool should I run?\n");
212
495
  return;
213
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
+ }
214
502
  const data = await runCatalogTool(args.toolName, args.args || {});
215
- process.stdout.write(JSON.stringify(data, null, 2) + "\n");
503
+ printToolResult(args.toolName, data);
216
504
  return;
217
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
+ }
218
520
  case "answer":
219
521
  process.stdout.write(`${args.text || "I can help with MangoMagic episodes, talking cards, and MCP setup."}\n`);
220
522
  return;
@@ -223,17 +525,25 @@ ${DIM}${err?.message ?? err}${RESET}
223
525
  }
224
526
  }
225
527
 
226
- export async function chat(actions) {
528
+ export async function chat(actions, { showHeader = true } = {}) {
227
529
  const rl = readline.createInterface({ input, output });
228
- process.stdout.write(`
530
+ if (showHeader) {
531
+ process.stdout.write(`
229
532
  ${BOLD}MangoMagic Chat${RESET}
230
533
  Type what you want. Try: ${GOLD}create talking cards about AI adoption${RESET}
231
534
  Type ${DIM}exit${RESET} to leave.
232
535
 
233
536
  `);
537
+ }
234
538
  try {
235
539
  while (true) {
236
- const text = await rl.question(`${GOLD}mango>${RESET} `);
540
+ let text;
541
+ try {
542
+ text = await rl.question(`${GOLD}mango>${RESET} `);
543
+ } catch (err) {
544
+ if (err?.message === "readline was closed") break;
545
+ throw err;
546
+ }
237
547
  const result = await handleNaturalLanguage(text, actions);
238
548
  if (result === "exit") break;
239
549
  process.stdout.write("\n");
package/src/index.mjs CHANGED
@@ -35,7 +35,21 @@ ${BOLD}mangomagic${RESET} ${DIM}— sign into MangoMagic and bring your account
35
35
  Token cache: ${credentialsPath()}
36
36
 
37
37
  If \`mangomagic\` is not installed globally, use:
38
- ${GOLD}${COMMAND_PREFIX} chat${RESET}
38
+ ${GOLD}${COMMAND_PREFIX} login${RESET}
39
+ `);
40
+ }
41
+
42
+ function sessionIntro() {
43
+ process.stdout.write(`
44
+ ${BOLD}You can type naturally now.${RESET}
45
+
46
+ Try:
47
+ ${GOLD}create 3 talking cards about founder-led sales${RESET}
48
+ ${GOLD}analyse my guests${RESET}
49
+ ${GOLD}show me my clips${RESET}
50
+ ${GOLD}what episodes do I have?${RESET}
51
+
52
+ ${DIM}Type ${BOLD}exit${RESET}${DIM} to leave. Add ${BOLD}--home${RESET}${DIM} next time if you only want the menu.${RESET}
39
53
  `);
40
54
  }
41
55
 
@@ -46,8 +60,9 @@ ${BOLD}What can MangoMagic do now?${RESET}
46
60
  ${DIM}Fast wins:${RESET}
47
61
  `);
48
62
  for (const item of QUICK_WINS) {
49
- process.stdout.write(` ${GOLD}${item.command.padEnd(24)}${RESET} ${BOLD}${item.title}${RESET}\n`);
50
- process.stdout.write(` ${DIM}${"".padEnd(24)} ${item.description}${RESET}\n`);
63
+ process.stdout.write(` ${BOLD}${item.title}${RESET}\n`);
64
+ process.stdout.write(` ${GOLD}${item.command}${RESET}\n`);
65
+ process.stdout.write(` ${DIM}${item.description}${RESET}\n\n`);
51
66
  }
52
67
  process.stdout.write(`
53
68
  ${DIM}Try this in Claude, Cursor, or Codex after MCP is connected:${RESET}
@@ -84,10 +99,16 @@ ${DIM}Core tools available now:${RESET}
84
99
  }
85
100
 
86
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
+
87
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).
88
- ${DIM}Run:${RESET} ${GOLD}mangomagic tool get_user_stats${RESET}
89
- ${DIM}Run with JSON:${RESET} ${GOLD}mangomagic tool get_user_episodes '{"limit":3}'${RESET}
90
- ${DIM}Full list:${RESET} ${GOLD}mangomagic tools --all${RESET}
109
+ ${DIM}Run:${RESET} ${GOLD}${COMMAND_PREFIX} tool get_user_stats${RESET}
110
+ ${DIM}Run with JSON:${RESET} ${GOLD}${COMMAND_PREFIX} tool get_user_episodes '{"limit":3}'${RESET}
111
+ ${DIM}Full list:${RESET} ${GOLD}${COMMAND_PREFIX} tools --all${RESET}
91
112
  `);
92
113
 
93
114
  if (all) {
@@ -106,14 +127,14 @@ ${DIM}Full list:${RESET} ${GOLD}mangomagic tools --all${RESET}
106
127
  }
107
128
 
108
129
  process.stdout.write(`
109
- ${DIM}Next high-value workflows to wire in:${RESET}
130
+ ${DIM}High-value workflows:${RESET}
110
131
  `);
111
132
  for (const workflow of NEXT_WORKFLOWS) {
112
133
  process.stdout.write(` ${GOLD}${workflow.name.padEnd(28)}${RESET} ${workflow.description}\n`);
113
134
  }
114
135
 
115
136
  process.stdout.write(`
116
- ${DIM}Use \`mangomagic mcp-config\` to connect an MCP client.${RESET}
137
+ ${DIM}Use \`${COMMAND_PREFIX} mcp-config\` to connect an MCP client.${RESET}
117
138
  `);
118
139
  }
119
140
 
@@ -150,15 +171,15 @@ function splashMode() {
150
171
 
151
172
  async function enterChatAfterLogin(startChat) {
152
173
  if (!startChat) return;
153
- process.stdout.write(`\n${DIM}You're in chat mode now. Type ${BOLD}exit${RESET}${DIM} to leave.${RESET}\n`);
154
- await chat(actions());
174
+ sessionIntro();
175
+ await chat(actions(), { showHeader: false });
155
176
  }
156
177
 
157
178
  async function login({ openInBrowser = true, startChat = false } = {}) {
158
179
  if (loadToken()) {
159
- process.stdout.write(`${DIM}You're already signed in. Run \`mangomagic logout\` first to switch accounts.${RESET}\n`);
180
+ process.stdout.write(`${DIM}You're already signed in. Continuing in MangoMagic.${RESET}\n`);
160
181
  await playSplash({ mode: splashMode(), greetingLine: "Welcome back to MangoMagic." });
161
- home();
182
+ if (!startChat) home();
162
183
  await enterChatAfterLogin(startChat);
163
184
  return;
164
185
  }
@@ -180,7 +201,7 @@ ${BOLD}Sign in to MangoMagic${RESET}
180
201
  saveToken(creds);
181
202
  process.stdout.write("\n\n");
182
203
  await playSplash({ mode: splashMode(), greetingLine: "You're in. Welcome to MangoMagic." });
183
- home();
204
+ if (!startChat) home();
184
205
  await enterChatAfterLogin(startChat);
185
206
  });
186
207
  }
@@ -69,9 +69,9 @@ export const QUICK_WINS = [
69
69
  status: "ready",
70
70
  },
71
71
  {
72
- command: `${COMMAND_PREFIX} chat`,
72
+ command: `${COMMAND_PREFIX} login`,
73
73
  title: "Talk To MangoMagic",
74
- description: "Type natural language and let the CLI choose the right MangoMagic action.",
74
+ description: "Enter chat mode, type natural language, and let the CLI choose the right MangoMagic action.",
75
75
  status: "ready",
76
76
  },
77
77
  {
@@ -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, saved to the user's carousel library.",
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/ui/splash.mjs CHANGED
@@ -35,6 +35,7 @@ const WHITEC = [255, 255, 255];
35
35
  const DIMC = [110, 78, 12];
36
36
 
37
37
  const RESET = "\x1b[0m";
38
+ const ESC = "\x1b[";
38
39
 
39
40
  const rgb = (c) => `\x1b[38;2;${c[0]};${c[1]};${c[2]}m`;
40
41
  const lerp = (a, b, t) => [
@@ -217,9 +218,15 @@ export async function playSplash({ mode = "static", greetingLine = null } = {})
217
218
  }
218
219
 
219
220
  const fps = 14;
220
- const total = mode === "loop" ? Number.MAX_SAFE_INTEGER : Math.floor(2.8 * fps);
221
-
222
- process.stdout.write("\x1b[?25l"); // hide cursor
221
+ const total = mode === "loop" ? Number.MAX_SAFE_INTEGER : Math.floor(1.15 * fps);
222
+ const useAltScreen = process.stdout.isTTY && process.env.MANGOMAGIC_SPLASH_INLINE !== "1";
223
+ const renderFrame = (body) => {
224
+ process.stdout.write(`${ESC}H${ESC}2J`);
225
+ process.stdout.write(body);
226
+ process.stdout.write("\n\n" + tag + greet + "\n");
227
+ };
228
+
229
+ process.stdout.write(useAltScreen ? `${ESC}?1049h${ESC}?25l` : `${ESC}?25l`);
223
230
  let interrupted = false;
224
231
  const onSig = () => { interrupted = true; };
225
232
  process.on("SIGINT", onSig);
@@ -229,15 +236,17 @@ export async function playSplash({ mode = "static", greetingLine = null } = {})
229
236
  if (Math.random() < 0.08 && shooters.length < 2) shooters.push(new Shooter(width, height));
230
237
  for (const sh of shooters) sh.life += 1;
231
238
  shooters = shooters.filter(s => s.life < 6);
232
- process.stdout.write("\x1b[H\x1b[2J");
233
- process.stdout.write(render(width, height, iconTop, wordTop, stars, shooters, f));
234
- process.stdout.write("\n\n" + tag + greet + "\n");
239
+ renderFrame(render(width, height, iconTop, wordTop, stars, shooters, f));
235
240
  await sleep(1000 / fps);
236
241
  }
237
242
  } finally {
238
243
  process.off("SIGINT", onSig);
239
- process.stdout.write("\x1b[H\x1b[2J");
240
- process.stdout.write(renderSettled());
241
- process.stdout.write("\n\n" + tag + greet + "\n\x1b[?25h");
244
+ if (useAltScreen) {
245
+ process.stdout.write(`${ESC}?25h${ESC}?1049l`);
246
+ process.stdout.write(renderSettled() + "\n\n" + tag + greet + "\n");
247
+ } else {
248
+ renderFrame(renderSettled());
249
+ process.stdout.write(`${ESC}?25h`);
250
+ }
242
251
  }
243
252
  }