@mangomagic/cli 0.1.5 → 0.1.6

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/README.md CHANGED
@@ -21,7 +21,8 @@ at `~/.mangomagic/credentials.json` (mode 0600) so the next runs skip auth.
21
21
  | `mangomagic chat` | Start an interactive natural-language session. |
22
22
  | `mangomagic ask "..."` | Run one natural-language request. |
23
23
  | `mangomagic tools` | Show available MCP tools and near-next workflows. Add `--json` for machine-readable output. |
24
- | `mangomagic cards` | Open the Talking Cards workspace. Add `--no-open` to print the URL only. |
24
+ | `mangomagic cards "idea"` | Create talking cards in the terminal and save them to your library. Add `--count 3`. |
25
+ | `mangomagic open cards` | Open the Talking Cards workspace in a browser. |
25
26
  | `mangomagic splash` | Show the splash once. `--anim`, `--loop` supported. |
26
27
  | `mangomagic mcp` | Run as a stdio MCP server. Wire into Claude Desktop / Cursor. |
27
28
  | `mangomagic mcp-config` | Print MCP client JSON for copy-paste. |
@@ -49,6 +50,7 @@ you sign in first.
49
50
  ```bash
50
51
  npx -y @mangomagic/cli chat
51
52
  npx -y @mangomagic/cli create talking cards about AI adoption
53
+ npx -y @mangomagic/cli cards "why founder-led sales stalls" --count 3
52
54
  ```
53
55
 
54
56
  Obvious requests are handled locally. Fuzzy routing uses MangoMagic's backend AI
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mangomagic/cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
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": {
@@ -22,7 +22,7 @@ function localPlan(text) {
22
22
  return { action: "open_brand_doc", args: {} };
23
23
  }
24
24
  if (t.includes("talking card") || t.includes("carousel") || /\bcards?\b/.test(t)) {
25
- return { action: "open_cards", args: { focus: raw } };
25
+ return { action: "create_talking_cards", args: { focus: raw, count: inferCount(raw) } };
26
26
  }
27
27
  if (t.includes("mcp") && (t.includes("config") || t.includes("connect") || t.includes("claude") || t.includes("cursor") || t.includes("codex"))) {
28
28
  return { action: "mcp_config", args: {} };
@@ -40,6 +40,12 @@ function localPlan(text) {
40
40
  return null;
41
41
  }
42
42
 
43
+ function inferCount(text) {
44
+ const match = text.match(/(?:^|\s)(?:make|create|generate)?\s*(\d{1,2})\s+(?:talking\s+)?cards?\b/i);
45
+ if (!match) return 3;
46
+ return Math.max(1, Math.min(Number(match[1]), 10));
47
+ }
48
+
43
49
  function asArray(data) {
44
50
  if (Array.isArray(data)) return data;
45
51
  if (Array.isArray(data?.episodes)) return data.episodes;
@@ -76,12 +82,63 @@ function printEpisode(data) {
76
82
  if (ep.id) process.stdout.write(`\n${DIM}${ep.id}${RESET}\n`);
77
83
  }
78
84
 
85
+ function cardUrl(card) {
86
+ return card?.id ? `https://mangomagic.live/carousels/${card.id}` : null;
87
+ }
88
+
89
+ export function printCards(result) {
90
+ const cards = Array.isArray(result?.carousels) ? result.carousels : [];
91
+ if (!cards.length) {
92
+ process.stdout.write("No cards were created.\n");
93
+ return;
94
+ }
95
+
96
+ process.stdout.write(`${BOLD}Created ${cards.length} talking card${cards.length === 1 ? "" : "s"}.${RESET}\n\n`);
97
+ for (const [idx, card] of cards.entries()) {
98
+ process.stdout.write(`${GOLD}${idx + 1}. ${card.title || "Untitled card"}${RESET}\n`);
99
+ if (card.seam) process.stdout.write(` ${DIM}${card.seam}${RESET}\n`);
100
+ const slides = Array.isArray(card?.spec?.slides) ? card.spec.slides : [];
101
+ for (const slide of slides.slice(0, 8)) {
102
+ const line = (slide.lines || []).map((l) => l.text).filter(Boolean).join(" / ");
103
+ if (line) process.stdout.write(` - ${line}\n`);
104
+ }
105
+ if (card.caption) process.stdout.write(`\n Caption: ${card.caption.replace(/\n/g, "\n ")}\n`);
106
+ const url = cardUrl(card);
107
+ if (url) process.stdout.write(` Edit: ${url}\n`);
108
+ process.stdout.write("\n");
109
+ }
110
+ }
111
+
112
+ export async function createTalkingCards({ focus, count = 3 } = {}) {
113
+ const cleanFocus = compact(focus);
114
+ if (!cleanFocus) {
115
+ process.stdout.write(`Give me an idea, for example:\n ${GOLD}mangomagic cards "why founder-led sales stalls" --count 3${RESET}\n`);
116
+ return;
117
+ }
118
+
119
+ process.stdout.write(`${DIM}Creating ${count} talking card${count === 1 ? "" : "s"}...${RESET}\n`);
120
+ try {
121
+ const result = await apiCall("cli-create-talking-cards", {
122
+ body: { focus: cleanFocus, count: Math.max(1, Math.min(Number(count || 3), 10)) },
123
+ });
124
+ printCards(result);
125
+ } catch (err) {
126
+ const message = err?.message ?? String(err);
127
+ if (message.includes("HTTP 404")) {
128
+ process.stdout.write(`${BOLD}The terminal card-creation backend is not deployed yet.${RESET}\n`);
129
+ process.stdout.write(`This CLI is ready, but MangoMagic Cloud still needs the \`cli-create-talking-cards\` function deployed.\n`);
130
+ return;
131
+ }
132
+ throw err;
133
+ }
134
+ }
135
+
79
136
  export async function handleNaturalLanguage(text, actions, { allowModel = true } = {}) {
80
137
  let plan = localPlan(text);
81
138
  if (!plan && allowModel) {
82
139
  try {
83
140
  plan = await planWithKimi(text, {
84
- availableTools: ["open_cards", "open_brand_doc", "show_tools", "mcp_config", "list_episodes", "search_episodes", "get_episode", "home"],
141
+ availableTools: ["create_talking_cards", "open_cards", "open_brand_doc", "show_tools", "mcp_config", "list_episodes", "search_episodes", "get_episode", "home"],
85
142
  });
86
143
  } catch (err) {
87
144
  process.stdout.write(`
@@ -107,6 +164,8 @@ ${DIM}${err?.message ?? err}${RESET}
107
164
  switch (action) {
108
165
  case "exit":
109
166
  return "exit";
167
+ case "create_talking_cards":
168
+ return createTalkingCards({ focus: args.focus || text, count: args.count || 3 });
110
169
  case "open_cards":
111
170
  return actions.cards({ path: "/carousels/generate", focus: args.focus });
112
171
  case "open_brand_doc":
package/src/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { deviceLogin } from "./auth/device-flow.mjs";
2
- import { chat, handleNaturalLanguage } from "./chat/natural-language.mjs";
2
+ import { chat, createTalkingCards, handleNaturalLanguage } from "./chat/natural-language.mjs";
3
3
  import { loadToken, saveToken, clearToken, credentialsPath } from "./auth/token-store.mjs";
4
4
  import { APP_ORIGIN } from "./config.mjs";
5
5
  import { openUrl } from "./system/open-url.mjs";
@@ -21,7 +21,8 @@ ${BOLD}mangomagic${RESET} ${DIM}— sign into MangoMagic and bring your account
21
21
  ${GOLD}mangomagic home${RESET} Show fast wins for creating value right now.
22
22
  ${GOLD}mangomagic chat${RESET} Talk to MangoMagic in natural language.
23
23
  ${GOLD}mangomagic tools${RESET} Show available MCP tools and next workflows.
24
- ${GOLD}mangomagic cards${RESET} Open the Talking Cards workspace.
24
+ ${GOLD}mangomagic cards "idea"${RESET} Create talking cards in the terminal.
25
+ ${GOLD}mangomagic open cards${RESET} Open the Talking Cards workspace in a browser.
25
26
  ${GOLD}mangomagic splash${RESET} Show the MangoMagic splash once. Add --anim or --loop for motion.
26
27
  ${GOLD}mangomagic mcp${RESET} Run as a stdio MCP server (for Claude Desktop / Cursor).
27
28
  ${GOLD}mangomagic mcp-config${RESET} Print MCP client config for copy-paste.
@@ -148,6 +149,25 @@ function cards({ openInBrowser = process.env.MANGOMAGIC_CLI_NO_OPEN !== "1", pat
148
149
  if (focus) process.stdout.write(`${DIM}Idea: ${focus}${RESET}\n`);
149
150
  }
150
151
 
152
+ function parseCount(argv, fallback = 3) {
153
+ const flagIndex = argv.findIndex((v) => v === "--count" || v === "-n");
154
+ if (flagIndex >= 0 && argv[flagIndex + 1]) return Math.max(1, Math.min(Number(argv[flagIndex + 1]), 10));
155
+ const inline = argv.find((v) => /^--count=/.test(v));
156
+ if (inline) return Math.max(1, Math.min(Number(inline.split("=")[1]), 10));
157
+ return fallback;
158
+ }
159
+
160
+ function stripFlags(argv) {
161
+ const out = [];
162
+ for (let i = 0; i < argv.length; i++) {
163
+ const v = argv[i];
164
+ if (v === "--count" || v === "-n") { i++; continue; }
165
+ if (v.startsWith("--count=")) continue;
166
+ out.push(v);
167
+ }
168
+ return out;
169
+ }
170
+
151
171
  async function mcpServer() {
152
172
  // Lazy-load so users who only run `login` don't pay the import cost.
153
173
  const { startMcpServer } = await import("./mcp/server.mjs");
@@ -180,7 +200,10 @@ export async function run(argv) {
180
200
  case "chat": return chat(actions());
181
201
  case "ask": return handleNaturalLanguage(argv.slice(1).join(" "), actions());
182
202
  case "tools": return tools({ json: argv.includes("--json") });
183
- case "cards": return cards({ openInBrowser: !argv.includes("--no-open") && process.env.MANGOMAGIC_CLI_NO_OPEN !== "1" });
203
+ case "cards": return createTalkingCards({ focus: stripFlags(argv.slice(1)).join(" "), count: parseCount(argv.slice(1)) });
204
+ case "open":
205
+ if (argv[1] === "cards" || argv[1] === "carousels") return cards({ openInBrowser: !argv.includes("--no-open") && process.env.MANGOMAGIC_CLI_NO_OPEN !== "1" });
206
+ return openPath(`/${argv[1] || ""}`);
184
207
  case "splash": return playSplash({ mode: argv.includes("--loop") ? "loop" : argv.includes("--anim") ? "anim" : "static" });
185
208
  case "mcp": return mcpServer();
186
209
  case "mcp-config": return mcpConfig();
@@ -42,10 +42,9 @@ export const MCP_TOOL_CATALOG = [
42
42
 
43
43
  export const QUICK_WINS = [
44
44
  {
45
- command: `${COMMAND_PREFIX} cards`,
45
+ command: `${COMMAND_PREFIX} cards "why founder-led sales stalls" --count 3`,
46
46
  title: "Create Talking Cards",
47
- description: "Open the card workspace, build a Brand Doc, choose colours, and generate LinkedIn-ready cards.",
48
- url: `${APP_ORIGIN}/carousels`,
47
+ description: "Generate LinkedIn-ready talking cards from the terminal and save them to your library.",
49
48
  status: "ready",
50
49
  },
51
50
  {
@@ -71,7 +70,7 @@ export const QUICK_WINS = [
71
70
  export const NEXT_WORKFLOWS = [
72
71
  {
73
72
  name: "create_talking_cards",
74
- description: "Generate a batch of branded cards from a short idea, saved to the user's carousel library.",
73
+ description: "Generate a batch of branded talking cards from a short idea, saved to the user's carousel library.",
75
74
  },
76
75
  {
77
76
  name: "sync_linkedin_brand_context",