@mangomagic/cli 0.1.3 → 0.1.4

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,9 @@ 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. |
23
+ | `mangomagic key set` | Store a Moonshot/Kimi API key in macOS Keychain, or a mode-0600 fallback file on other platforms. |
21
24
  | `mangomagic tools` | Show available MCP tools and near-next workflows. Add `--json` for machine-readable output. |
22
25
  | `mangomagic cards` | Open the Talking Cards workspace. Add `--no-open` to print the URL only. |
23
26
  | `mangomagic splash` | Show the splash once. `--anim`, `--loop` supported. |
@@ -42,6 +45,20 @@ Add to Claude Desktop's `claude_desktop_config.json` (or Cursor's MCP settings):
42
45
  The MCP server uses the same token cached by `mangomagic login`, so make sure
43
46
  you sign in first.
44
47
 
48
+ ## Natural language
49
+
50
+ ```bash
51
+ npx -y @mangomagic/cli chat
52
+ npx -y @mangomagic/cli create talking cards about AI adoption
53
+ ```
54
+
55
+ Obvious requests are handled locally. Fuzzy routing uses Kimi K2.6 through the
56
+ Moonshot API. Store the key securely:
57
+
58
+ ```bash
59
+ npx -y @mangomagic/cli key set
60
+ ```
61
+
45
62
  ## Requirements
46
63
 
47
64
  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.4",
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,66 @@
1
+ import { loadMoonshotKey } from "../secrets/moonshot-key-store.mjs";
2
+
3
+ export class MissingMoonshotKeyError extends Error {
4
+ constructor() {
5
+ super("Missing Moonshot API key.");
6
+ this.name = "MissingMoonshotKeyError";
7
+ }
8
+ }
9
+
10
+ export async function planWithKimi(userText, context = {}) {
11
+ const secret = loadMoonshotKey();
12
+ if (!secret?.key) throw new MissingMoonshotKeyError();
13
+
14
+ const response = await fetch("https://api.moonshot.ai/v1/chat/completions", {
15
+ method: "POST",
16
+ headers: {
17
+ "Content-Type": "application/json",
18
+ "Authorization": `Bearer ${secret.key}`,
19
+ },
20
+ body: JSON.stringify({
21
+ model: "kimi-k2.6",
22
+ messages: [
23
+ {
24
+ role: "system",
25
+ content: `You route natural-language terminal requests for MangoMagic.
26
+
27
+ Return only compact JSON. No markdown.
28
+
29
+ Allowed actions:
30
+ - open_cards: open the Talking Cards generator. Args: {"focus": optional string}
31
+ - open_brand_doc: open Brand Doc setup/review.
32
+ - show_tools: show available MCP tools.
33
+ - mcp_config: print MCP config.
34
+ - list_episodes: list recent episodes. Args: {"limit": integer 1-10}
35
+ - search_episodes: search episodes. Args: {"query": string}
36
+ - get_episode: get one episode. Args: {"episode": id or slug}
37
+ - home: show fast wins.
38
+ - answer: answer briefly without external action. Args: {"text": string}
39
+
40
+ Prefer a direct tool action over a chatty answer. If the user asks to create talking cards, choose open_cards and preserve their idea as focus.`,
41
+ },
42
+ {
43
+ role: "user",
44
+ content: JSON.stringify({ userText, context }),
45
+ },
46
+ ],
47
+ thinking: { type: "disabled" },
48
+ max_tokens: 700,
49
+ }),
50
+ });
51
+
52
+ const bodyText = await response.text();
53
+ let body;
54
+ try { body = JSON.parse(bodyText); } catch { body = null; }
55
+
56
+ if (!response.ok) {
57
+ const message = body?.error?.message || body?.error || bodyText || `HTTP ${response.status}`;
58
+ throw new Error(`Kimi request failed: ${message}`);
59
+ }
60
+
61
+ const content = body?.choices?.[0]?.message?.content;
62
+ if (!content) throw new Error("Kimi returned no routing decision.");
63
+
64
+ const jsonText = String(content).trim().replace(/^```json\s*/i, "").replace(/^```\s*/i, "").replace(/```$/i, "").trim();
65
+ return JSON.parse(jsonText);
66
+ }
@@ -0,0 +1,173 @@
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 { MissingMoonshotKeyError, planWithKimi } from "../ai/kimi.mjs";
5
+ import { moonshotKeyStorageHint } from "../secrets/moonshot-key-store.mjs";
6
+
7
+ const GOLD = "\x1b[38;2;241;171;28m";
8
+ const DIM = "\x1b[2m";
9
+ const BOLD = "\x1b[1m";
10
+ const RESET = "\x1b[0m";
11
+
12
+ function compact(s) {
13
+ return String(s || "").trim().replace(/\s+/g, " ");
14
+ }
15
+
16
+ function localPlan(text) {
17
+ const raw = compact(text);
18
+ const t = raw.toLowerCase();
19
+ if (!t) return { action: "answer", args: { text: "" } };
20
+
21
+ if (/^(exit|quit|bye)$/i.test(t)) return { action: "exit", args: {} };
22
+ if (t.includes("brand doc") || t.includes("brand colour") || t.includes("brand color")) {
23
+ return { action: "open_brand_doc", args: {} };
24
+ }
25
+ if (t.includes("talking card") || t.includes("carousel") || /\bcards?\b/.test(t)) {
26
+ return { action: "open_cards", args: { focus: raw } };
27
+ }
28
+ if (t.includes("mcp") && (t.includes("config") || t.includes("connect") || t.includes("claude") || t.includes("cursor") || t.includes("codex"))) {
29
+ return { action: "mcp_config", args: {} };
30
+ }
31
+ if (t.includes("tool") || t.includes("what can")) return { action: "show_tools", args: {} };
32
+ if (t.includes("latest episode") || t.includes("recent episode") || t === "episodes" || t.includes("list episodes")) {
33
+ return { action: "list_episodes", args: { limit: 5 } };
34
+ }
35
+ const searchMatch = raw.match(/search(?: my)? episodes? (?:for|about)\s+(.+)/i);
36
+ if (searchMatch?.[1]) return { action: "search_episodes", args: { query: searchMatch[1] } };
37
+ const getMatch = raw.match(/(?:get|show|open)(?: episode)?\s+([a-z0-9][a-z0-9_-]{5,})/i);
38
+ if (getMatch?.[1]) return { action: "get_episode", args: { episode: getMatch[1] } };
39
+ if (t.includes("home") || t.includes("help")) return { action: "home", args: {} };
40
+
41
+ return null;
42
+ }
43
+
44
+ function asArray(data) {
45
+ if (Array.isArray(data)) return data;
46
+ if (Array.isArray(data?.episodes)) return data.episodes;
47
+ if (Array.isArray(data?.results)) return data.results;
48
+ if (Array.isArray(data?.data)) return data.data;
49
+ return [];
50
+ }
51
+
52
+ function printEpisodeList(data) {
53
+ const rows = asArray(data);
54
+ if (!rows.length) {
55
+ process.stdout.write("No episodes found.\n");
56
+ return;
57
+ }
58
+ for (const ep of rows.slice(0, 10)) {
59
+ const title = ep.title || ep.name || ep.slug || ep.id || "Untitled episode";
60
+ const id = ep.id || ep.slug || "";
61
+ process.stdout.write(` ${GOLD}${title}${RESET}${id ? ` ${DIM}${id}${RESET}` : ""}\n`);
62
+ }
63
+ }
64
+
65
+ function printEpisode(data) {
66
+ const ep = data?.episode || data?.data || data;
67
+ if (!ep || typeof ep !== "object") {
68
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
69
+ return;
70
+ }
71
+ process.stdout.write(`${BOLD}${ep.title || ep.name || ep.id || "Episode"}${RESET}\n`);
72
+ if (ep.summary) process.stdout.write(`\n${ep.summary}\n`);
73
+ if (Array.isArray(ep.takeaways) && ep.takeaways.length) {
74
+ process.stdout.write("\nTakeaways:\n");
75
+ for (const item of ep.takeaways.slice(0, 8)) process.stdout.write(` - ${item}\n`);
76
+ }
77
+ if (ep.id) process.stdout.write(`\n${DIM}${ep.id}${RESET}\n`);
78
+ }
79
+
80
+ export async function handleNaturalLanguage(text, actions, { allowModel = true } = {}) {
81
+ let plan = localPlan(text);
82
+ if (!plan && allowModel) {
83
+ try {
84
+ plan = await planWithKimi(text, {
85
+ availableTools: ["open_cards", "open_brand_doc", "show_tools", "mcp_config", "list_episodes", "search_episodes", "get_episode", "home"],
86
+ });
87
+ } catch (err) {
88
+ if (err instanceof MissingMoonshotKeyError) {
89
+ process.stdout.write(`
90
+ ${BOLD}Natural-language routing needs a Kimi API key for fuzzy requests.${RESET}
91
+
92
+ Obvious commands still work, for example:
93
+ ${GOLD}npx -y @mangomagic/cli create talking cards${RESET}
94
+ ${GOLD}npx -y @mangomagic/cli search episodes for AI adoption${RESET}
95
+
96
+ Set Kimi up securely:
97
+ ${GOLD}npx -y @mangomagic/cli key set${RESET}
98
+
99
+ Storage: ${moonshotKeyStorageHint()}
100
+ `);
101
+ return;
102
+ }
103
+ throw err;
104
+ }
105
+ }
106
+
107
+ if (!plan) {
108
+ process.stdout.write("I could not map that to a MangoMagic action yet. Try `tools` or `chat`.\n");
109
+ return;
110
+ }
111
+
112
+ const action = plan.action;
113
+ const args = plan.args || {};
114
+ switch (action) {
115
+ case "exit":
116
+ return "exit";
117
+ case "open_cards":
118
+ return actions.cards({ path: "/carousels/generate", focus: args.focus });
119
+ case "open_brand_doc":
120
+ return actions.openPath("/carousels/doc");
121
+ case "show_tools":
122
+ return actions.tools();
123
+ case "mcp_config":
124
+ return actions.mcpConfig();
125
+ case "home":
126
+ return actions.home();
127
+ case "list_episodes": {
128
+ const data = await apiCall("cli-list-episodes", { body: { limit: Math.max(1, Math.min(Number(args.limit || 5), 10)) } });
129
+ return printEpisodeList(data);
130
+ }
131
+ case "search_episodes": {
132
+ if (!args.query) {
133
+ process.stdout.write("What should I search for?\n");
134
+ return;
135
+ }
136
+ const data = await apiCall("cli-search-episodes", { body: { query: args.query } });
137
+ return printEpisodeList(data);
138
+ }
139
+ case "get_episode": {
140
+ if (!args.episode) {
141
+ process.stdout.write("Which episode id or slug?\n");
142
+ return;
143
+ }
144
+ const data = await apiCall("cli-get-episode", { body: { episode: args.episode } });
145
+ return printEpisode(data);
146
+ }
147
+ case "answer":
148
+ process.stdout.write(`${args.text || "I can help with MangoMagic episodes, talking cards, and MCP setup."}\n`);
149
+ return;
150
+ default:
151
+ process.stdout.write(`I do not know how to run action "${action}" yet.\n`);
152
+ }
153
+ }
154
+
155
+ export async function chat(actions) {
156
+ const rl = readline.createInterface({ input, output });
157
+ process.stdout.write(`
158
+ ${BOLD}MangoMagic Chat${RESET}
159
+ Type what you want. Try: ${GOLD}create talking cards about AI adoption${RESET}
160
+ Type ${DIM}exit${RESET} to leave.
161
+
162
+ `);
163
+ try {
164
+ while (true) {
165
+ const text = await rl.question(`${GOLD}mango>${RESET} `);
166
+ const result = await handleNaturalLanguage(text, actions);
167
+ if (result === "exit") break;
168
+ process.stdout.write("\n");
169
+ }
170
+ } finally {
171
+ rl.close();
172
+ }
173
+ }
package/src/index.mjs CHANGED
@@ -1,8 +1,11 @@
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";
5
+ import { clearMoonshotKey, loadMoonshotKey, moonshotKeyStorageHint, saveMoonshotKey } from "./secrets/moonshot-key-store.mjs";
4
6
  import { openUrl } from "./system/open-url.mjs";
5
- import { MCP_TOOL_CATALOG, NEXT_WORKFLOWS, QUICK_WINS } from "./tools/catalog.mjs";
7
+ import { COMMAND_PREFIX, MCP_TOOL_CATALOG, NEXT_WORKFLOWS, QUICK_WINS } from "./tools/catalog.mjs";
8
+ import { promptHidden } from "./ui/prompt-hidden.mjs";
6
9
  import { playSplash } from "./ui/splash.mjs";
7
10
 
8
11
  const GOLD = "\x1b[38;2;241;171;28m";
@@ -18,6 +21,8 @@ ${BOLD}mangomagic${RESET} ${DIM}— sign into MangoMagic and bring your account
18
21
  ${GOLD}mangomagic logout${RESET} Forget the token cached on this machine.
19
22
  ${GOLD}mangomagic whoami${RESET} Show the cached identity.
20
23
  ${GOLD}mangomagic home${RESET} Show fast wins for creating value right now.
24
+ ${GOLD}mangomagic chat${RESET} Talk to MangoMagic in natural language.
25
+ ${GOLD}mangomagic key set${RESET} Store a Moonshot/Kimi API key securely.
21
26
  ${GOLD}mangomagic tools${RESET} Show available MCP tools and next workflows.
22
27
  ${GOLD}mangomagic cards${RESET} Open the Talking Cards workspace.
23
28
  ${GOLD}mangomagic splash${RESET} Show the MangoMagic splash once. Add --anim or --loop for motion.
@@ -26,6 +31,9 @@ ${BOLD}mangomagic${RESET} ${DIM}— sign into MangoMagic and bring your account
26
31
  ${GOLD}mangomagic help${RESET} This message.
27
32
 
28
33
  Token cache: ${credentialsPath()}
34
+
35
+ If \`mangomagic\` is not installed globally, use:
36
+ ${GOLD}${COMMAND_PREFIX} chat${RESET}
29
37
  `);
30
38
  }
31
39
 
@@ -44,6 +52,10 @@ ${DIM}Try this in Claude, Cursor, or Codex after MCP is connected:${RESET}
44
52
  "Use MangoMagic to search my episodes for AI adoption."
45
53
  "Use MangoMagic to get my latest episode and draft five LinkedIn post ideas."
46
54
 
55
+ ${DIM}Talk in the terminal:${RESET}
56
+ ${GOLD}${COMMAND_PREFIX} chat${RESET}
57
+ ${GOLD}${COMMAND_PREFIX} create talking cards about my LinkedIn idea${RESET}
58
+
47
59
  ${DIM}Open app:${RESET} ${APP_ORIGIN}
48
60
  ${DIM}Token cache:${RESET} ${credentialsPath()}
49
61
  `);
@@ -121,13 +133,22 @@ function whoami() {
121
133
  process.stdout.write(`Signed in (user ${t.userId ?? "?"}), approved ${t.approvedAt}\n`);
122
134
  }
123
135
 
124
- function cards({ openInBrowser = true } = {}) {
125
- const url = `${APP_ORIGIN}/carousels`;
136
+ function openPath(path) {
137
+ const url = `${APP_ORIGIN}${path}`;
138
+ const shouldOpen = process.env.MANGOMAGIC_CLI_NO_OPEN !== "1";
139
+ const opened = shouldOpen ? openUrl(url) : false;
140
+ if (shouldOpen) process.stdout.write(opened ? `Opening ${url}\n` : "Could not open a browser automatically.\n");
141
+ process.stdout.write(`${GOLD}${url}${RESET}\n`);
142
+ }
143
+
144
+ function cards({ openInBrowser = process.env.MANGOMAGIC_CLI_NO_OPEN !== "1", path = "/carousels/generate", focus = null } = {}) {
145
+ const url = `${APP_ORIGIN}${path}`;
126
146
  if (openInBrowser) {
127
147
  const opened = openUrl(url);
128
148
  process.stdout.write(opened ? "Opening Talking Cards...\n" : "Could not open a browser automatically.\n");
129
149
  }
130
150
  process.stdout.write(`${GOLD}${url}${RESET}\n`);
151
+ if (focus) process.stdout.write(`${DIM}Idea: ${focus}${RESET}\n`);
131
152
  }
132
153
 
133
154
  async function mcpServer() {
@@ -148,6 +169,33 @@ function mcpConfig() {
148
169
  process.stdout.write(JSON.stringify(cfg, null, 2) + "\n");
149
170
  }
150
171
 
172
+ async function keyCommand(argv) {
173
+ const sub = argv[0] || "status";
174
+ if (sub === "set") {
175
+ const key = await promptHidden("Moonshot API key: ");
176
+ const where = saveMoonshotKey(key);
177
+ process.stdout.write(`Saved Moonshot API key in ${where}.\n`);
178
+ return;
179
+ }
180
+ if (sub === "clear" || sub === "delete" || sub === "remove") {
181
+ const cleared = clearMoonshotKey();
182
+ process.stdout.write(cleared ? "Moonshot API key removed.\n" : "No stored Moonshot API key found.\n");
183
+ return;
184
+ }
185
+ if (sub === "status") {
186
+ const key = loadMoonshotKey();
187
+ if (key) process.stdout.write(`Moonshot API key found via ${key.source}.\n`);
188
+ else process.stdout.write(`No Moonshot API key found. Store one with \`${COMMAND_PREFIX} key set\`.\nStorage: ${moonshotKeyStorageHint()}\n`);
189
+ return;
190
+ }
191
+ process.stderr.write(`Unknown key command: ${sub}\n`);
192
+ process.exit(2);
193
+ }
194
+
195
+ function actions() {
196
+ return { cards, openPath, tools, mcpConfig, home };
197
+ }
198
+
151
199
  export async function run(argv) {
152
200
  const cmd = argv[0] ?? (loadToken() ? "home" : "login");
153
201
  switch (cmd) {
@@ -155,6 +203,10 @@ export async function run(argv) {
155
203
  case "logout": return logout();
156
204
  case "whoami": return whoami();
157
205
  case "home": return home();
206
+ case "chat": return chat(actions());
207
+ case "ask": return handleNaturalLanguage(argv.slice(1).join(" "), actions());
208
+ case "key":
209
+ case "kimi-key": return keyCommand(argv.slice(1));
158
210
  case "tools": return tools({ json: argv.includes("--json") });
159
211
  case "cards": return cards({ openInBrowser: !argv.includes("--no-open") && process.env.MANGOMAGIC_CLI_NO_OPEN !== "1" });
160
212
  case "splash": return playSplash({ mode: argv.includes("--loop") ? "loop" : argv.includes("--anim") ? "anim" : "static" });
@@ -164,8 +216,6 @@ export async function run(argv) {
164
216
  case "--help":
165
217
  case "-h": return help();
166
218
  default:
167
- process.stderr.write(`Unknown command: ${cmd}\n`);
168
- help();
169
- process.exit(2);
219
+ return handleNaturalLanguage(argv.join(" "), actions());
170
220
  }
171
221
  }
@@ -0,0 +1,110 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, chmodSync } from "node:fs";
3
+ import { homedir, userInfo } from "node:os";
4
+ import { join } from "node:path";
5
+
6
+ const SERVICE = "mangomagic.moonshot.api-key";
7
+ const dir = join(homedir(), ".mangomagic");
8
+ const fallbackFile = join(dir, "moonshot.json");
9
+
10
+ function account() {
11
+ return process.env.USER || userInfo().username || "default";
12
+ }
13
+
14
+ function canUseKeychain() {
15
+ return process.platform === "darwin" && existsSync("/usr/bin/security");
16
+ }
17
+
18
+ function readKeychain() {
19
+ if (!canUseKeychain()) return null;
20
+ try {
21
+ const key = execFileSync("/usr/bin/security", [
22
+ "find-generic-password",
23
+ "-a", account(),
24
+ "-s", SERVICE,
25
+ "-w",
26
+ ], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
27
+ return key || null;
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+
33
+ function writeKeychain(key) {
34
+ execFileSync("/usr/bin/security", [
35
+ "add-generic-password",
36
+ "-a", account(),
37
+ "-s", SERVICE,
38
+ "-w", key,
39
+ "-U",
40
+ ], { stdio: "ignore" });
41
+ }
42
+
43
+ function clearKeychain() {
44
+ if (!canUseKeychain()) return false;
45
+ try {
46
+ execFileSync("/usr/bin/security", [
47
+ "delete-generic-password",
48
+ "-a", account(),
49
+ "-s", SERVICE,
50
+ ], { stdio: "ignore" });
51
+ return true;
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+
57
+ function readFallback() {
58
+ if (!existsSync(fallbackFile)) return null;
59
+ try {
60
+ const raw = JSON.parse(readFileSync(fallbackFile, "utf8"));
61
+ return typeof raw?.apiKey === "string" ? raw.apiKey : null;
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+
67
+ function writeFallback(key) {
68
+ mkdirSync(dir, { recursive: true });
69
+ writeFileSync(fallbackFile, JSON.stringify({ apiKey: key }, null, 2));
70
+ try { chmodSync(fallbackFile, 0o600); } catch { /* windows */ }
71
+ }
72
+
73
+ export function loadMoonshotKey() {
74
+ if (process.env.MOONSHOT_API_KEY) return { key: process.env.MOONSHOT_API_KEY, source: "MOONSHOT_API_KEY" };
75
+ const keychainKey = readKeychain();
76
+ if (keychainKey) return { key: keychainKey, source: "macOS Keychain" };
77
+ const fileKey = readFallback();
78
+ if (fileKey) return { key: fileKey, source: fallbackFile };
79
+ return null;
80
+ }
81
+
82
+ export function saveMoonshotKey(key) {
83
+ if (!key || typeof key !== "string") throw new Error("Missing API key.");
84
+ const trimmed = key.trim();
85
+ if (!trimmed) throw new Error("Missing API key.");
86
+
87
+ if (canUseKeychain()) {
88
+ writeKeychain(trimmed);
89
+ return "macOS Keychain";
90
+ }
91
+
92
+ writeFallback(trimmed);
93
+ return fallbackFile;
94
+ }
95
+
96
+ export function clearMoonshotKey() {
97
+ const keychainCleared = clearKeychain();
98
+ let fileCleared = false;
99
+ if (existsSync(fallbackFile)) {
100
+ unlinkSync(fallbackFile);
101
+ fileCleared = true;
102
+ }
103
+ return keychainCleared || fileCleared;
104
+ }
105
+
106
+ export function moonshotKeyStorageHint() {
107
+ return canUseKeychain()
108
+ ? "macOS Keychain service mangomagic.moonshot.api-key"
109
+ : `${fallbackFile} (mode 0600) or MOONSHOT_API_KEY`;
110
+ }
@@ -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",
@@ -0,0 +1,45 @@
1
+ export function promptHidden(prompt) {
2
+ return new Promise((resolve, reject) => {
3
+ const stdin = process.stdin;
4
+ const stdout = process.stdout;
5
+ let value = "";
6
+ let rawWasEnabled = false;
7
+
8
+ function cleanup() {
9
+ stdin.off("data", onData);
10
+ if (rawWasEnabled && stdin.isTTY) stdin.setRawMode(false);
11
+ stdin.pause();
12
+ stdout.write("\n");
13
+ }
14
+
15
+ function onData(chunk) {
16
+ const s = String(chunk);
17
+ for (const ch of s) {
18
+ if (ch === "\u0003") {
19
+ cleanup();
20
+ reject(new Error("Cancelled."));
21
+ return;
22
+ }
23
+ if (ch === "\r" || ch === "\n") {
24
+ cleanup();
25
+ resolve(value);
26
+ return;
27
+ }
28
+ if (ch === "\u007f" || ch === "\b") {
29
+ value = value.slice(0, -1);
30
+ continue;
31
+ }
32
+ value += ch;
33
+ }
34
+ }
35
+
36
+ stdout.write(prompt);
37
+ stdin.resume();
38
+ stdin.setEncoding("utf8");
39
+ if (stdin.isTTY) {
40
+ stdin.setRawMode(true);
41
+ rawWasEnabled = true;
42
+ }
43
+ stdin.on("data", onData);
44
+ });
45
+ }