@mangomagic/cli 0.1.1 → 0.1.3
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 +4 -1
- package/package.json +2 -1
- package/src/auth/device-flow.mjs +2 -16
- package/src/index.mjs +75 -3
- package/src/mcp/server.mjs +11 -31
- package/src/system/open-url.mjs +16 -0
- package/src/tools/catalog.mjs +76 -0
- package/src/ui/splash.mjs +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,10 @@ at `~/.mangomagic/credentials.json` (mode 0600) so the next runs skip auth.
|
|
|
17
17
|
| `mangomagic login` | Browser-based device-code sign-in + splash. Add `--no-open` to print the URL without opening a browser. |
|
|
18
18
|
| `mangomagic logout` | Forget the cached token. |
|
|
19
19
|
| `mangomagic whoami` | Show the cached identity. |
|
|
20
|
-
| `mangomagic
|
|
20
|
+
| `mangomagic home` | Show fast wins after sign-in. |
|
|
21
|
+
| `mangomagic tools` | Show available MCP tools and near-next workflows. Add `--json` for machine-readable output. |
|
|
22
|
+
| `mangomagic cards` | Open the Talking Cards workspace. Add `--no-open` to print the URL only. |
|
|
23
|
+
| `mangomagic splash` | Show the splash once. `--anim`, `--loop` supported. |
|
|
21
24
|
| `mangomagic mcp` | Run as a stdio MCP server. Wire into Claude Desktop / Cursor. |
|
|
22
25
|
| `mangomagic mcp-config` | Print MCP client JSON for copy-paste. |
|
|
23
26
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mangomagic/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
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": {
|
|
7
|
+
"cli": "bin/mangomagic.mjs",
|
|
7
8
|
"mangomagic": "bin/mangomagic.mjs"
|
|
8
9
|
},
|
|
9
10
|
"publishConfig": {
|
package/src/auth/device-flow.mjs
CHANGED
|
@@ -1,22 +1,8 @@
|
|
|
1
1
|
// Browser-device OAuth-style flow against the cli-device-authorize / cli-device-poll edge fns.
|
|
2
2
|
import { FN_BASE, SUPABASE_ANON_KEY } from "../config.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { openUrl } from "../system/open-url.mjs";
|
|
4
4
|
import { hostname, platform } from "node:os";
|
|
5
5
|
|
|
6
|
-
function openBrowser(url) {
|
|
7
|
-
const cmd = process.platform === "darwin" ? "open"
|
|
8
|
-
: process.platform === "win32" ? "cmd"
|
|
9
|
-
: "xdg-open";
|
|
10
|
-
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
11
|
-
try {
|
|
12
|
-
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
13
|
-
child.unref();
|
|
14
|
-
return true;
|
|
15
|
-
} catch {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
6
|
async function call(path, body) {
|
|
21
7
|
const res = await fetch(`${FN_BASE}/${path}`, {
|
|
22
8
|
method: "POST",
|
|
@@ -40,7 +26,7 @@ export async function deviceLogin({ onCode, onWaiting, openInBrowser = true } =
|
|
|
40
26
|
const start = await call("cli-device-authorize", { client_name });
|
|
41
27
|
|
|
42
28
|
onCode?.(start);
|
|
43
|
-
if (openInBrowser)
|
|
29
|
+
if (openInBrowser) openUrl(start.verification_uri_complete);
|
|
44
30
|
|
|
45
31
|
const deadline = Date.now() + (start.expires_in ?? 600) * 1000;
|
|
46
32
|
let interval = (start.interval ?? 2) * 1000;
|
package/src/index.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { deviceLogin } from "./auth/device-flow.mjs";
|
|
2
2
|
import { loadToken, saveToken, clearToken, credentialsPath } from "./auth/token-store.mjs";
|
|
3
|
+
import { APP_ORIGIN } from "./config.mjs";
|
|
4
|
+
import { openUrl } from "./system/open-url.mjs";
|
|
5
|
+
import { MCP_TOOL_CATALOG, NEXT_WORKFLOWS, QUICK_WINS } from "./tools/catalog.mjs";
|
|
3
6
|
import { playSplash } from "./ui/splash.mjs";
|
|
4
7
|
|
|
5
8
|
const GOLD = "\x1b[38;2;241;171;28m";
|
|
@@ -14,7 +17,10 @@ ${BOLD}mangomagic${RESET} ${DIM}— sign into MangoMagic and bring your account
|
|
|
14
17
|
${GOLD}mangomagic login${RESET} Authorize this terminal (opens your browser; add --no-open to print only).
|
|
15
18
|
${GOLD}mangomagic logout${RESET} Forget the token cached on this machine.
|
|
16
19
|
${GOLD}mangomagic whoami${RESET} Show the cached identity.
|
|
17
|
-
${GOLD}mangomagic
|
|
20
|
+
${GOLD}mangomagic home${RESET} Show fast wins for creating value right now.
|
|
21
|
+
${GOLD}mangomagic tools${RESET} Show available MCP tools and next workflows.
|
|
22
|
+
${GOLD}mangomagic cards${RESET} Open the Talking Cards workspace.
|
|
23
|
+
${GOLD}mangomagic splash${RESET} Show the MangoMagic splash once. Add --anim or --loop for motion.
|
|
18
24
|
${GOLD}mangomagic mcp${RESET} Run as a stdio MCP server (for Claude Desktop / Cursor).
|
|
19
25
|
${GOLD}mangomagic mcp-config${RESET} Print MCP client config for copy-paste.
|
|
20
26
|
${GOLD}mangomagic help${RESET} This message.
|
|
@@ -23,10 +29,63 @@ Token cache: ${credentialsPath()}
|
|
|
23
29
|
`);
|
|
24
30
|
}
|
|
25
31
|
|
|
32
|
+
function home() {
|
|
33
|
+
process.stdout.write(`
|
|
34
|
+
${BOLD}What can MangoMagic do now?${RESET}
|
|
35
|
+
|
|
36
|
+
${DIM}Fast wins:${RESET}
|
|
37
|
+
`);
|
|
38
|
+
for (const item of QUICK_WINS) {
|
|
39
|
+
process.stdout.write(` ${GOLD}${item.command.padEnd(24)}${RESET} ${BOLD}${item.title}${RESET}\n`);
|
|
40
|
+
process.stdout.write(` ${DIM}${"".padEnd(24)} ${item.description}${RESET}\n`);
|
|
41
|
+
}
|
|
42
|
+
process.stdout.write(`
|
|
43
|
+
${DIM}Try this in Claude, Cursor, or Codex after MCP is connected:${RESET}
|
|
44
|
+
"Use MangoMagic to search my episodes for AI adoption."
|
|
45
|
+
"Use MangoMagic to get my latest episode and draft five LinkedIn post ideas."
|
|
46
|
+
|
|
47
|
+
${DIM}Open app:${RESET} ${APP_ORIGIN}
|
|
48
|
+
${DIM}Token cache:${RESET} ${credentialsPath()}
|
|
49
|
+
`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function tools({ json = false } = {}) {
|
|
53
|
+
if (json) {
|
|
54
|
+
process.stdout.write(JSON.stringify({
|
|
55
|
+
mcpTools: MCP_TOOL_CATALOG,
|
|
56
|
+
quickWins: QUICK_WINS,
|
|
57
|
+
nextWorkflows: NEXT_WORKFLOWS,
|
|
58
|
+
}, null, 2) + "\n");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
process.stdout.write(`
|
|
63
|
+
${BOLD}MangoMagic MCP Tools${RESET}
|
|
64
|
+
|
|
65
|
+
${DIM}Available now:${RESET}
|
|
66
|
+
`);
|
|
67
|
+
for (const tool of MCP_TOOL_CATALOG) {
|
|
68
|
+
process.stdout.write(` ${GOLD}${tool.name.padEnd(18)}${RESET} ${tool.description}\n`);
|
|
69
|
+
process.stdout.write(` ${DIM}${"".padEnd(18)} Try: ${tool.example}${RESET}\n`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
process.stdout.write(`
|
|
73
|
+
${DIM}Next high-value workflows to wire in:${RESET}
|
|
74
|
+
`);
|
|
75
|
+
for (const workflow of NEXT_WORKFLOWS) {
|
|
76
|
+
process.stdout.write(` ${GOLD}${workflow.name.padEnd(28)}${RESET} ${workflow.description}\n`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
process.stdout.write(`
|
|
80
|
+
${DIM}Use \`mangomagic mcp-config\` to connect an MCP client.${RESET}
|
|
81
|
+
`);
|
|
82
|
+
}
|
|
83
|
+
|
|
26
84
|
async function login({ openInBrowser = true } = {}) {
|
|
27
85
|
if (loadToken()) {
|
|
28
86
|
process.stdout.write(`${DIM}You're already signed in. Run \`mangomagic logout\` first to switch accounts.${RESET}\n`);
|
|
29
87
|
await playSplash({ greetingLine: "Welcome back to MangoMagic." });
|
|
88
|
+
home();
|
|
30
89
|
return;
|
|
31
90
|
}
|
|
32
91
|
|
|
@@ -47,6 +106,7 @@ ${BOLD}Sign in to MangoMagic${RESET}
|
|
|
47
106
|
saveToken(creds);
|
|
48
107
|
process.stdout.write("\n\n");
|
|
49
108
|
await playSplash({ greetingLine: "You're in. Welcome to MangoMagic." });
|
|
109
|
+
home();
|
|
50
110
|
});
|
|
51
111
|
}
|
|
52
112
|
|
|
@@ -61,6 +121,15 @@ function whoami() {
|
|
|
61
121
|
process.stdout.write(`Signed in (user ${t.userId ?? "?"}), approved ${t.approvedAt}\n`);
|
|
62
122
|
}
|
|
63
123
|
|
|
124
|
+
function cards({ openInBrowser = true } = {}) {
|
|
125
|
+
const url = `${APP_ORIGIN}/carousels`;
|
|
126
|
+
if (openInBrowser) {
|
|
127
|
+
const opened = openUrl(url);
|
|
128
|
+
process.stdout.write(opened ? "Opening Talking Cards...\n" : "Could not open a browser automatically.\n");
|
|
129
|
+
}
|
|
130
|
+
process.stdout.write(`${GOLD}${url}${RESET}\n`);
|
|
131
|
+
}
|
|
132
|
+
|
|
64
133
|
async function mcpServer() {
|
|
65
134
|
// Lazy-load so users who only run `login` don't pay the import cost.
|
|
66
135
|
const { startMcpServer } = await import("./mcp/server.mjs");
|
|
@@ -80,12 +149,15 @@ function mcpConfig() {
|
|
|
80
149
|
}
|
|
81
150
|
|
|
82
151
|
export async function run(argv) {
|
|
83
|
-
const cmd = argv[0] ?? (loadToken() ? "
|
|
152
|
+
const cmd = argv[0] ?? (loadToken() ? "home" : "login");
|
|
84
153
|
switch (cmd) {
|
|
85
154
|
case "login": return login({ openInBrowser: !argv.includes("--no-open") && process.env.MANGOMAGIC_CLI_NO_OPEN !== "1" });
|
|
86
155
|
case "logout": return logout();
|
|
87
156
|
case "whoami": return whoami();
|
|
88
|
-
case "
|
|
157
|
+
case "home": return home();
|
|
158
|
+
case "tools": return tools({ json: argv.includes("--json") });
|
|
159
|
+
case "cards": return cards({ openInBrowser: !argv.includes("--no-open") && process.env.MANGOMAGIC_CLI_NO_OPEN !== "1" });
|
|
160
|
+
case "splash": return playSplash({ mode: argv.includes("--loop") ? "loop" : argv.includes("--anim") ? "anim" : "static" });
|
|
89
161
|
case "mcp": return mcpServer();
|
|
90
162
|
case "mcp-config": return mcpConfig();
|
|
91
163
|
case "help":
|
package/src/mcp/server.mjs
CHANGED
|
@@ -9,38 +9,18 @@ import {
|
|
|
9
9
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
10
|
import { apiCall } from "../api.mjs";
|
|
11
11
|
import { loadToken } from "../auth/token-store.mjs";
|
|
12
|
+
import { MCP_TOOL_CATALOG } from "../tools/catalog.mjs";
|
|
12
13
|
|
|
13
|
-
const
|
|
14
|
-
{
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
{
|
|
24
|
-
name: "get_episode",
|
|
25
|
-
description: "Get full details for one episode (transcript, summary, takeaways) by ID or slug.",
|
|
26
|
-
inputSchema: {
|
|
27
|
-
type: "object",
|
|
28
|
-
properties: { episode: { type: "string", description: "Episode id or slug" } },
|
|
29
|
-
required: ["episode"],
|
|
30
|
-
},
|
|
31
|
-
handler: async ({ episode }) => apiCall("cli-get-episode", { body: { episode } }),
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
name: "search_episodes",
|
|
35
|
-
description: "Full-text search across the signed-in user's episodes.",
|
|
36
|
-
inputSchema: {
|
|
37
|
-
type: "object",
|
|
38
|
-
properties: { query: { type: "string" } },
|
|
39
|
-
required: ["query"],
|
|
40
|
-
},
|
|
41
|
-
handler: async ({ query }) => apiCall("cli-search-episodes", { body: { query } }),
|
|
42
|
-
},
|
|
43
|
-
];
|
|
14
|
+
const TOOL_HANDLERS = {
|
|
15
|
+
list_episodes: async ({ limit = 10 }) => apiCall("cli-list-episodes", { body: { limit } }),
|
|
16
|
+
get_episode: async ({ episode }) => apiCall("cli-get-episode", { body: { episode } }),
|
|
17
|
+
search_episodes: async ({ query }) => apiCall("cli-search-episodes", { body: { query } }),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const TOOLS = MCP_TOOL_CATALOG.map((tool) => ({
|
|
21
|
+
...tool,
|
|
22
|
+
handler: TOOL_HANDLERS[tool.name],
|
|
23
|
+
}));
|
|
44
24
|
|
|
45
25
|
export async function startMcpServer() {
|
|
46
26
|
if (!loadToken()) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
export function openUrl(url) {
|
|
4
|
+
const cmd = process.platform === "darwin" ? "open"
|
|
5
|
+
: process.platform === "win32" ? "cmd"
|
|
6
|
+
: "xdg-open";
|
|
7
|
+
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
11
|
+
child.unref();
|
|
12
|
+
return true;
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { APP_ORIGIN } from "../config.mjs";
|
|
2
|
+
|
|
3
|
+
export const MCP_TOOL_CATALOG = [
|
|
4
|
+
{
|
|
5
|
+
name: "list_episodes",
|
|
6
|
+
description: "List the signed-in user's MangoMagic episodes, newest first.",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
limit: { type: "integer", minimum: 1, maximum: 50, default: 10 },
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
example: "Use MangoMagic to list my three most recent episodes.",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: "get_episode",
|
|
17
|
+
description: "Get full details for one episode, including transcript, summary, and takeaways.",
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
episode: { type: "string", description: "Episode id or slug" },
|
|
22
|
+
},
|
|
23
|
+
required: ["episode"],
|
|
24
|
+
},
|
|
25
|
+
example: "Use MangoMagic to get episode <id> and turn it into LinkedIn talking points.",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "search_episodes",
|
|
29
|
+
description: "Search across the signed-in user's episodes.",
|
|
30
|
+
inputSchema: {
|
|
31
|
+
type: "object",
|
|
32
|
+
properties: {
|
|
33
|
+
query: { type: "string" },
|
|
34
|
+
},
|
|
35
|
+
required: ["query"],
|
|
36
|
+
},
|
|
37
|
+
example: "Use MangoMagic to search my episodes for customer discovery.",
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
export const QUICK_WINS = [
|
|
42
|
+
{
|
|
43
|
+
command: "mangomagic cards",
|
|
44
|
+
title: "Create Talking Cards",
|
|
45
|
+
description: "Open the card workspace, build a Brand Doc, choose colours, and generate LinkedIn-ready cards.",
|
|
46
|
+
url: `${APP_ORIGIN}/carousels`,
|
|
47
|
+
status: "ready",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
command: "mangomagic mcp-config",
|
|
51
|
+
title: "Connect Claude, Cursor, or Codex",
|
|
52
|
+
description: "Print the MCP config that lets your AI client use MangoMagic tools.",
|
|
53
|
+
status: "ready",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
command: "mangomagic tools",
|
|
57
|
+
title: "See Every Tool",
|
|
58
|
+
description: "Show the full MCP tool list plus the next high-value workflows to wire in.",
|
|
59
|
+
status: "ready",
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
export const NEXT_WORKFLOWS = [
|
|
64
|
+
{
|
|
65
|
+
name: "create_talking_cards",
|
|
66
|
+
description: "Generate a batch of branded cards from a short idea, saved to the user's carousel library.",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "sync_linkedin_brand_context",
|
|
70
|
+
description: "Use LinkedIn profile context to draft or refresh the user's Brand Doc.",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "publish_talking_card",
|
|
74
|
+
description: "Publish an approved card to LinkedIn or prepare the post for manual approval.",
|
|
75
|
+
},
|
|
76
|
+
];
|
package/src/ui/splash.mjs
CHANGED
|
@@ -191,7 +191,7 @@ function render(width, height, iconTop, wordTop, stars, shooters, frame) {
|
|
|
191
191
|
|
|
192
192
|
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
193
193
|
|
|
194
|
-
export async function playSplash({ mode = "
|
|
194
|
+
export async function playSplash({ mode = "static", greetingLine = null } = {}) {
|
|
195
195
|
if (!supportsColor()) {
|
|
196
196
|
for (const l of WORDMARK) process.stdout.write(l + "\n");
|
|
197
197
|
process.stdout.write("M A G I C\n");
|