@mangomagic/cli 0.1.4 → 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
@@ -20,9 +20,9 @@ at `~/.mangomagic/credentials.json` (mode 0600) so the next runs skip auth.
20
20
  | `mangomagic home` | Show fast wins after sign-in. |
21
21
  | `mangomagic chat` | Start an interactive natural-language session. |
22
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. |
24
23
  | `mangomagic tools` | Show available MCP tools and near-next workflows. Add `--json` for machine-readable output. |
25
- | `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. |
26
26
  | `mangomagic splash` | Show the splash once. `--anim`, `--loop` supported. |
27
27
  | `mangomagic mcp` | Run as a stdio MCP server. Wire into Claude Desktop / Cursor. |
28
28
  | `mangomagic mcp-config` | Print MCP client JSON for copy-paste. |
@@ -50,14 +50,11 @@ you sign in first.
50
50
  ```bash
51
51
  npx -y @mangomagic/cli chat
52
52
  npx -y @mangomagic/cli create talking cards about AI adoption
53
+ npx -y @mangomagic/cli cards "why founder-led sales stalls" --count 3
53
54
  ```
54
55
 
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
- ```
56
+ Obvious requests are handled locally. Fuzzy routing uses MangoMagic's backend AI
57
+ router, so end users never need to provide a Moonshot/Kimi key.
61
58
 
62
59
  ## Requirements
63
60
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mangomagic/cli",
3
- "version": "0.1.4",
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": {
package/src/ai/kimi.mjs CHANGED
@@ -1,66 +1,7 @@
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
- }
1
+ import { apiCall } from "../api.mjs";
9
2
 
10
3
  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);
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;
66
7
  }
@@ -1,8 +1,7 @@
1
1
  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
- import { MissingMoonshotKeyError, planWithKimi } from "../ai/kimi.mjs";
5
- import { moonshotKeyStorageHint } from "../secrets/moonshot-key-store.mjs";
4
+ import { planWithKimi } from "../ai/kimi.mjs";
6
5
 
7
6
  const GOLD = "\x1b[38;2;241;171;28m";
8
7
  const DIM = "\x1b[2m";
@@ -23,7 +22,7 @@ function localPlan(text) {
23
22
  return { action: "open_brand_doc", args: {} };
24
23
  }
25
24
  if (t.includes("talking card") || t.includes("carousel") || /\bcards?\b/.test(t)) {
26
- return { action: "open_cards", args: { focus: raw } };
25
+ return { action: "create_talking_cards", args: { focus: raw, count: inferCount(raw) } };
27
26
  }
28
27
  if (t.includes("mcp") && (t.includes("config") || t.includes("connect") || t.includes("claude") || t.includes("cursor") || t.includes("codex"))) {
29
28
  return { action: "mcp_config", args: {} };
@@ -41,6 +40,12 @@ function localPlan(text) {
41
40
  return null;
42
41
  }
43
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
+
44
49
  function asArray(data) {
45
50
  if (Array.isArray(data)) return data;
46
51
  if (Array.isArray(data?.episodes)) return data.episodes;
@@ -77,30 +82,75 @@ function printEpisode(data) {
77
82
  if (ep.id) process.stdout.write(`\n${DIM}${ep.id}${RESET}\n`);
78
83
  }
79
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
+
80
136
  export async function handleNaturalLanguage(text, actions, { allowModel = true } = {}) {
81
137
  let plan = localPlan(text);
82
138
  if (!plan && allowModel) {
83
139
  try {
84
140
  plan = await planWithKimi(text, {
85
- 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"],
86
142
  });
87
143
  } 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}
144
+ process.stdout.write(`
145
+ ${BOLD}MangoMagic AI routing is unavailable right now.${RESET}
91
146
 
92
147
  Obvious commands still work, for example:
93
148
  ${GOLD}npx -y @mangomagic/cli create talking cards${RESET}
94
149
  ${GOLD}npx -y @mangomagic/cli search episodes for AI adoption${RESET}
95
150
 
96
- Set Kimi up securely:
97
- ${GOLD}npx -y @mangomagic/cli key set${RESET}
98
-
99
- Storage: ${moonshotKeyStorageHint()}
151
+ ${DIM}${err?.message ?? err}${RESET}
100
152
  `);
101
- return;
102
- }
103
- throw err;
153
+ return;
104
154
  }
105
155
  }
106
156
 
@@ -114,6 +164,8 @@ Storage: ${moonshotKeyStorageHint()}
114
164
  switch (action) {
115
165
  case "exit":
116
166
  return "exit";
167
+ case "create_talking_cards":
168
+ return createTalkingCards({ focus: args.focus || text, count: args.count || 3 });
117
169
  case "open_cards":
118
170
  return actions.cards({ path: "/carousels/generate", focus: args.focus });
119
171
  case "open_brand_doc":
package/src/index.mjs CHANGED
@@ -1,11 +1,9 @@
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
- import { clearMoonshotKey, loadMoonshotKey, moonshotKeyStorageHint, saveMoonshotKey } from "./secrets/moonshot-key-store.mjs";
6
5
  import { openUrl } from "./system/open-url.mjs";
7
6
  import { COMMAND_PREFIX, MCP_TOOL_CATALOG, NEXT_WORKFLOWS, QUICK_WINS } from "./tools/catalog.mjs";
8
- import { promptHidden } from "./ui/prompt-hidden.mjs";
9
7
  import { playSplash } from "./ui/splash.mjs";
10
8
 
11
9
  const GOLD = "\x1b[38;2;241;171;28m";
@@ -22,9 +20,9 @@ ${BOLD}mangomagic${RESET} ${DIM}— sign into MangoMagic and bring your account
22
20
  ${GOLD}mangomagic whoami${RESET} Show the cached identity.
23
21
  ${GOLD}mangomagic home${RESET} Show fast wins for creating value right now.
24
22
  ${GOLD}mangomagic chat${RESET} Talk to MangoMagic in natural language.
25
- ${GOLD}mangomagic key set${RESET} Store a Moonshot/Kimi API key securely.
26
23
  ${GOLD}mangomagic tools${RESET} Show available MCP tools and next workflows.
27
- ${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.
28
26
  ${GOLD}mangomagic splash${RESET} Show the MangoMagic splash once. Add --anim or --loop for motion.
29
27
  ${GOLD}mangomagic mcp${RESET} Run as a stdio MCP server (for Claude Desktop / Cursor).
30
28
  ${GOLD}mangomagic mcp-config${RESET} Print MCP client config for copy-paste.
@@ -151,6 +149,25 @@ function cards({ openInBrowser = process.env.MANGOMAGIC_CLI_NO_OPEN !== "1", pat
151
149
  if (focus) process.stdout.write(`${DIM}Idea: ${focus}${RESET}\n`);
152
150
  }
153
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
+
154
171
  async function mcpServer() {
155
172
  // Lazy-load so users who only run `login` don't pay the import cost.
156
173
  const { startMcpServer } = await import("./mcp/server.mjs");
@@ -169,29 +186,6 @@ function mcpConfig() {
169
186
  process.stdout.write(JSON.stringify(cfg, null, 2) + "\n");
170
187
  }
171
188
 
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
189
  function actions() {
196
190
  return { cards, openPath, tools, mcpConfig, home };
197
191
  }
@@ -205,10 +199,11 @@ export async function run(argv) {
205
199
  case "home": return home();
206
200
  case "chat": return chat(actions());
207
201
  case "ask": return handleNaturalLanguage(argv.slice(1).join(" "), actions());
208
- case "key":
209
- case "kimi-key": return keyCommand(argv.slice(1));
210
202
  case "tools": return tools({ json: argv.includes("--json") });
211
- 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] || ""}`);
212
207
  case "splash": return playSplash({ mode: argv.includes("--loop") ? "loop" : argv.includes("--anim") ? "anim" : "static" });
213
208
  case "mcp": return mcpServer();
214
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",
@@ -1,110 +0,0 @@
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,45 +0,0 @@
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
- }