@mangomagic/cli 0.1.3 → 0.1.5

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
@@ -18,6 +18,8 @@ at `~/.mangomagic/credentials.json` (mode 0600) so the next runs skip auth.
18
18
  | `mangomagic logout` | Forget the cached token. |
19
19
  | `mangomagic whoami` | Show the cached identity. |
20
20
  | `mangomagic home` | Show fast wins after sign-in. |
21
+ | `mangomagic chat` | Start an interactive natural-language session. |
22
+ | `mangomagic ask "..."` | Run one natural-language request. |
21
23
  | `mangomagic tools` | Show available MCP tools and near-next workflows. Add `--json` for machine-readable output. |
22
24
  | `mangomagic cards` | Open the Talking Cards workspace. Add `--no-open` to print the URL only. |
23
25
  | `mangomagic splash` | Show the splash once. `--anim`, `--loop` supported. |
@@ -42,6 +44,16 @@ Add to Claude Desktop's `claude_desktop_config.json` (or Cursor's MCP settings):
42
44
  The MCP server uses the same token cached by `mangomagic login`, so make sure
43
45
  you sign in first.
44
46
 
47
+ ## Natural language
48
+
49
+ ```bash
50
+ npx -y @mangomagic/cli chat
51
+ npx -y @mangomagic/cli create talking cards about AI adoption
52
+ ```
53
+
54
+ Obvious requests are handled locally. Fuzzy routing uses MangoMagic's backend AI
55
+ router, so end users never need to provide a Moonshot/Kimi key.
56
+
45
57
  ## Requirements
46
58
 
47
59
  Node 18 or later. The splash uses true-color ANSI; on terminals without it,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mangomagic/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
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": {
@@ -0,0 +1,7 @@
1
+ import { apiCall } from "../api.mjs";
2
+
3
+ export async function planWithKimi(userText, context = {}) {
4
+ const result = await apiCall("cli-ai-route", { body: { text: userText, context } });
5
+ if (!result?.plan?.action) throw new Error("MangoMagic AI returned no routing decision.");
6
+ return result.plan;
7
+ }
@@ -0,0 +1,166 @@
1
+ import readline from "node:readline/promises";
2
+ import { stdin as input, stdout as output } from "node:process";
3
+ import { apiCall } from "../api.mjs";
4
+ import { planWithKimi } from "../ai/kimi.mjs";
5
+
6
+ const GOLD = "\x1b[38;2;241;171;28m";
7
+ const DIM = "\x1b[2m";
8
+ const BOLD = "\x1b[1m";
9
+ const RESET = "\x1b[0m";
10
+
11
+ function compact(s) {
12
+ return String(s || "").trim().replace(/\s+/g, " ");
13
+ }
14
+
15
+ function localPlan(text) {
16
+ const raw = compact(text);
17
+ const t = raw.toLowerCase();
18
+ if (!t) return { action: "answer", args: { text: "" } };
19
+
20
+ if (/^(exit|quit|bye)$/i.test(t)) return { action: "exit", args: {} };
21
+ if (t.includes("brand doc") || t.includes("brand colour") || t.includes("brand color")) {
22
+ return { action: "open_brand_doc", args: {} };
23
+ }
24
+ if (t.includes("talking card") || t.includes("carousel") || /\bcards?\b/.test(t)) {
25
+ return { action: "open_cards", args: { focus: raw } };
26
+ }
27
+ if (t.includes("mcp") && (t.includes("config") || t.includes("connect") || t.includes("claude") || t.includes("cursor") || t.includes("codex"))) {
28
+ return { action: "mcp_config", args: {} };
29
+ }
30
+ if (t.includes("tool") || t.includes("what can")) return { action: "show_tools", args: {} };
31
+ if (t.includes("latest episode") || t.includes("recent episode") || t === "episodes" || t.includes("list episodes")) {
32
+ return { action: "list_episodes", args: { limit: 5 } };
33
+ }
34
+ const searchMatch = raw.match(/search(?: my)? episodes? (?:for|about)\s+(.+)/i);
35
+ if (searchMatch?.[1]) return { action: "search_episodes", args: { query: searchMatch[1] } };
36
+ const getMatch = raw.match(/(?:get|show|open)(?: episode)?\s+([a-z0-9][a-z0-9_-]{5,})/i);
37
+ if (getMatch?.[1]) return { action: "get_episode", args: { episode: getMatch[1] } };
38
+ if (t.includes("home") || t.includes("help")) return { action: "home", args: {} };
39
+
40
+ return null;
41
+ }
42
+
43
+ function asArray(data) {
44
+ if (Array.isArray(data)) return data;
45
+ if (Array.isArray(data?.episodes)) return data.episodes;
46
+ if (Array.isArray(data?.results)) return data.results;
47
+ if (Array.isArray(data?.data)) return data.data;
48
+ return [];
49
+ }
50
+
51
+ function printEpisodeList(data) {
52
+ const rows = asArray(data);
53
+ if (!rows.length) {
54
+ process.stdout.write("No episodes found.\n");
55
+ return;
56
+ }
57
+ for (const ep of rows.slice(0, 10)) {
58
+ const title = ep.title || ep.name || ep.slug || ep.id || "Untitled episode";
59
+ const id = ep.id || ep.slug || "";
60
+ process.stdout.write(` ${GOLD}${title}${RESET}${id ? ` ${DIM}${id}${RESET}` : ""}\n`);
61
+ }
62
+ }
63
+
64
+ function printEpisode(data) {
65
+ const ep = data?.episode || data?.data || data;
66
+ if (!ep || typeof ep !== "object") {
67
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
68
+ return;
69
+ }
70
+ process.stdout.write(`${BOLD}${ep.title || ep.name || ep.id || "Episode"}${RESET}\n`);
71
+ if (ep.summary) process.stdout.write(`\n${ep.summary}\n`);
72
+ if (Array.isArray(ep.takeaways) && ep.takeaways.length) {
73
+ process.stdout.write("\nTakeaways:\n");
74
+ for (const item of ep.takeaways.slice(0, 8)) process.stdout.write(` - ${item}\n`);
75
+ }
76
+ if (ep.id) process.stdout.write(`\n${DIM}${ep.id}${RESET}\n`);
77
+ }
78
+
79
+ export async function handleNaturalLanguage(text, actions, { allowModel = true } = {}) {
80
+ let plan = localPlan(text);
81
+ if (!plan && allowModel) {
82
+ try {
83
+ plan = await planWithKimi(text, {
84
+ availableTools: ["open_cards", "open_brand_doc", "show_tools", "mcp_config", "list_episodes", "search_episodes", "get_episode", "home"],
85
+ });
86
+ } catch (err) {
87
+ process.stdout.write(`
88
+ ${BOLD}MangoMagic AI routing is unavailable right now.${RESET}
89
+
90
+ Obvious commands still work, for example:
91
+ ${GOLD}npx -y @mangomagic/cli create talking cards${RESET}
92
+ ${GOLD}npx -y @mangomagic/cli search episodes for AI adoption${RESET}
93
+
94
+ ${DIM}${err?.message ?? err}${RESET}
95
+ `);
96
+ return;
97
+ }
98
+ }
99
+
100
+ if (!plan) {
101
+ process.stdout.write("I could not map that to a MangoMagic action yet. Try `tools` or `chat`.\n");
102
+ return;
103
+ }
104
+
105
+ const action = plan.action;
106
+ const args = plan.args || {};
107
+ switch (action) {
108
+ case "exit":
109
+ return "exit";
110
+ case "open_cards":
111
+ return actions.cards({ path: "/carousels/generate", focus: args.focus });
112
+ case "open_brand_doc":
113
+ return actions.openPath("/carousels/doc");
114
+ case "show_tools":
115
+ return actions.tools();
116
+ case "mcp_config":
117
+ return actions.mcpConfig();
118
+ case "home":
119
+ return actions.home();
120
+ case "list_episodes": {
121
+ const data = await apiCall("cli-list-episodes", { body: { limit: Math.max(1, Math.min(Number(args.limit || 5), 10)) } });
122
+ return printEpisodeList(data);
123
+ }
124
+ case "search_episodes": {
125
+ if (!args.query) {
126
+ process.stdout.write("What should I search for?\n");
127
+ return;
128
+ }
129
+ const data = await apiCall("cli-search-episodes", { body: { query: args.query } });
130
+ return printEpisodeList(data);
131
+ }
132
+ case "get_episode": {
133
+ if (!args.episode) {
134
+ process.stdout.write("Which episode id or slug?\n");
135
+ return;
136
+ }
137
+ const data = await apiCall("cli-get-episode", { body: { episode: args.episode } });
138
+ return printEpisode(data);
139
+ }
140
+ case "answer":
141
+ process.stdout.write(`${args.text || "I can help with MangoMagic episodes, talking cards, and MCP setup."}\n`);
142
+ return;
143
+ default:
144
+ process.stdout.write(`I do not know how to run action "${action}" yet.\n`);
145
+ }
146
+ }
147
+
148
+ export async function chat(actions) {
149
+ const rl = readline.createInterface({ input, output });
150
+ process.stdout.write(`
151
+ ${BOLD}MangoMagic Chat${RESET}
152
+ Type what you want. Try: ${GOLD}create talking cards about AI adoption${RESET}
153
+ Type ${DIM}exit${RESET} to leave.
154
+
155
+ `);
156
+ try {
157
+ while (true) {
158
+ const text = await rl.question(`${GOLD}mango>${RESET} `);
159
+ const result = await handleNaturalLanguage(text, actions);
160
+ if (result === "exit") break;
161
+ process.stdout.write("\n");
162
+ }
163
+ } finally {
164
+ rl.close();
165
+ }
166
+ }
package/src/index.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  import { deviceLogin } from "./auth/device-flow.mjs";
2
+ import { chat, handleNaturalLanguage } from "./chat/natural-language.mjs";
2
3
  import { loadToken, saveToken, clearToken, credentialsPath } from "./auth/token-store.mjs";
3
4
  import { APP_ORIGIN } from "./config.mjs";
4
5
  import { openUrl } from "./system/open-url.mjs";
5
- import { MCP_TOOL_CATALOG, NEXT_WORKFLOWS, QUICK_WINS } from "./tools/catalog.mjs";
6
+ import { COMMAND_PREFIX, MCP_TOOL_CATALOG, NEXT_WORKFLOWS, QUICK_WINS } from "./tools/catalog.mjs";
6
7
  import { playSplash } from "./ui/splash.mjs";
7
8
 
8
9
  const GOLD = "\x1b[38;2;241;171;28m";
@@ -18,6 +19,7 @@ ${BOLD}mangomagic${RESET} ${DIM}— sign into MangoMagic and bring your account
18
19
  ${GOLD}mangomagic logout${RESET} Forget the token cached on this machine.
19
20
  ${GOLD}mangomagic whoami${RESET} Show the cached identity.
20
21
  ${GOLD}mangomagic home${RESET} Show fast wins for creating value right now.
22
+ ${GOLD}mangomagic chat${RESET} Talk to MangoMagic in natural language.
21
23
  ${GOLD}mangomagic tools${RESET} Show available MCP tools and next workflows.
22
24
  ${GOLD}mangomagic cards${RESET} Open the Talking Cards workspace.
23
25
  ${GOLD}mangomagic splash${RESET} Show the MangoMagic splash once. Add --anim or --loop for motion.
@@ -26,6 +28,9 @@ ${BOLD}mangomagic${RESET} ${DIM}— sign into MangoMagic and bring your account
26
28
  ${GOLD}mangomagic help${RESET} This message.
27
29
 
28
30
  Token cache: ${credentialsPath()}
31
+
32
+ If \`mangomagic\` is not installed globally, use:
33
+ ${GOLD}${COMMAND_PREFIX} chat${RESET}
29
34
  `);
30
35
  }
31
36
 
@@ -44,6 +49,10 @@ ${DIM}Try this in Claude, Cursor, or Codex after MCP is connected:${RESET}
44
49
  "Use MangoMagic to search my episodes for AI adoption."
45
50
  "Use MangoMagic to get my latest episode and draft five LinkedIn post ideas."
46
51
 
52
+ ${DIM}Talk in the terminal:${RESET}
53
+ ${GOLD}${COMMAND_PREFIX} chat${RESET}
54
+ ${GOLD}${COMMAND_PREFIX} create talking cards about my LinkedIn idea${RESET}
55
+
47
56
  ${DIM}Open app:${RESET} ${APP_ORIGIN}
48
57
  ${DIM}Token cache:${RESET} ${credentialsPath()}
49
58
  `);
@@ -121,13 +130,22 @@ function whoami() {
121
130
  process.stdout.write(`Signed in (user ${t.userId ?? "?"}), approved ${t.approvedAt}\n`);
122
131
  }
123
132
 
124
- function cards({ openInBrowser = true } = {}) {
125
- const url = `${APP_ORIGIN}/carousels`;
133
+ function openPath(path) {
134
+ const url = `${APP_ORIGIN}${path}`;
135
+ const shouldOpen = process.env.MANGOMAGIC_CLI_NO_OPEN !== "1";
136
+ const opened = shouldOpen ? openUrl(url) : false;
137
+ if (shouldOpen) process.stdout.write(opened ? `Opening ${url}\n` : "Could not open a browser automatically.\n");
138
+ process.stdout.write(`${GOLD}${url}${RESET}\n`);
139
+ }
140
+
141
+ function cards({ openInBrowser = process.env.MANGOMAGIC_CLI_NO_OPEN !== "1", path = "/carousels/generate", focus = null } = {}) {
142
+ const url = `${APP_ORIGIN}${path}`;
126
143
  if (openInBrowser) {
127
144
  const opened = openUrl(url);
128
145
  process.stdout.write(opened ? "Opening Talking Cards...\n" : "Could not open a browser automatically.\n");
129
146
  }
130
147
  process.stdout.write(`${GOLD}${url}${RESET}\n`);
148
+ if (focus) process.stdout.write(`${DIM}Idea: ${focus}${RESET}\n`);
131
149
  }
132
150
 
133
151
  async function mcpServer() {
@@ -148,6 +166,10 @@ function mcpConfig() {
148
166
  process.stdout.write(JSON.stringify(cfg, null, 2) + "\n");
149
167
  }
150
168
 
169
+ function actions() {
170
+ return { cards, openPath, tools, mcpConfig, home };
171
+ }
172
+
151
173
  export async function run(argv) {
152
174
  const cmd = argv[0] ?? (loadToken() ? "home" : "login");
153
175
  switch (cmd) {
@@ -155,6 +177,8 @@ export async function run(argv) {
155
177
  case "logout": return logout();
156
178
  case "whoami": return whoami();
157
179
  case "home": return home();
180
+ case "chat": return chat(actions());
181
+ case "ask": return handleNaturalLanguage(argv.slice(1).join(" "), actions());
158
182
  case "tools": return tools({ json: argv.includes("--json") });
159
183
  case "cards": return cards({ openInBrowser: !argv.includes("--no-open") && process.env.MANGOMAGIC_CLI_NO_OPEN !== "1" });
160
184
  case "splash": return playSplash({ mode: argv.includes("--loop") ? "loop" : argv.includes("--anim") ? "anim" : "static" });
@@ -164,8 +188,6 @@ export async function run(argv) {
164
188
  case "--help":
165
189
  case "-h": return help();
166
190
  default:
167
- process.stderr.write(`Unknown command: ${cmd}\n`);
168
- help();
169
- process.exit(2);
191
+ return handleNaturalLanguage(argv.join(" "), actions());
170
192
  }
171
193
  }
@@ -1,5 +1,7 @@
1
1
  import { APP_ORIGIN } from "../config.mjs";
2
2
 
3
+ export const COMMAND_PREFIX = "npx -y @mangomagic/cli";
4
+
3
5
  export const MCP_TOOL_CATALOG = [
4
6
  {
5
7
  name: "list_episodes",
@@ -40,20 +42,26 @@ export const MCP_TOOL_CATALOG = [
40
42
 
41
43
  export const QUICK_WINS = [
42
44
  {
43
- command: "mangomagic cards",
45
+ command: `${COMMAND_PREFIX} cards`,
44
46
  title: "Create Talking Cards",
45
47
  description: "Open the card workspace, build a Brand Doc, choose colours, and generate LinkedIn-ready cards.",
46
48
  url: `${APP_ORIGIN}/carousels`,
47
49
  status: "ready",
48
50
  },
49
51
  {
50
- command: "mangomagic mcp-config",
52
+ command: `${COMMAND_PREFIX} chat`,
53
+ title: "Talk To MangoMagic",
54
+ description: "Type natural language and let the CLI choose the right MangoMagic action.",
55
+ status: "ready",
56
+ },
57
+ {
58
+ command: `${COMMAND_PREFIX} mcp-config`,
51
59
  title: "Connect Claude, Cursor, or Codex",
52
60
  description: "Print the MCP config that lets your AI client use MangoMagic tools.",
53
61
  status: "ready",
54
62
  },
55
63
  {
56
- command: "mangomagic tools",
64
+ command: `${COMMAND_PREFIX} tools`,
57
65
  title: "See Every Tool",
58
66
  description: "Show the full MCP tool list plus the next high-value workflows to wire in.",
59
67
  status: "ready",