@magicappdev/cli 0.0.7 → 0.0.9

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/dist/cli.d.ts CHANGED
@@ -2,7 +2,6 @@
2
2
  * MagicAppDev CLI
3
3
  */
4
4
  import { Command } from "commander";
5
- /** TEST - Test if build picks up changes */
6
5
  /** Create the CLI program */
7
6
  export declare function createProgram(): Command;
8
7
  /** Run the CLI */
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,4CAA4C;AAE5C,6BAA6B;AAC7B,wBAAgB,aAAa,IAAI,OAAO,CAiBvC;AAED,kBAAkB;AAClB,wBAAsB,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGxD"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2CpC,6BAA6B;AAC7B,wBAAgB,aAAa,IAAI,OAAO,CA2BvC;AAED,kBAAkB;AAClB,wBAAsB,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBxD"}
package/dist/cli.js CHANGED
@@ -13,14 +13,53 @@ const require = createRequire(import.meta.url);
13
13
  const pkg = require("../package.json");
14
14
  /** Package version */
15
15
  const VERSION = pkg.version;
16
- /** TEST - Test if build picks up changes */
16
+ /** Compare semver versions: returns 1 if a > b, -1 if a < b, 0 if equal */
17
+ function compareVersions(a, b) {
18
+ const partsA = a.split(".").map(Number);
19
+ const partsB = b.split(".").map(Number);
20
+ for (let i = 0; i < 3; i++) {
21
+ if (partsA[i] > partsB[i])
22
+ return 1;
23
+ if (partsA[i] < partsB[i])
24
+ return -1;
25
+ }
26
+ return 0;
27
+ }
28
+ /** Check for updates by fetching npm registry (non-blocking) */
29
+ async function checkForUpdates() {
30
+ try {
31
+ const response = await fetch(`https://registry.npmjs.org/${pkg.name}/latest`, { signal: AbortSignal.timeout(3000) });
32
+ if (response.ok) {
33
+ const data = (await response.json());
34
+ const latestVersion = data.version;
35
+ // Only show update message if npm version is newer than local
36
+ if (latestVersion && compareVersions(latestVersion, pkg.version) > 0) {
37
+ console.log(`\n\x1b[33mUpdate available:\x1b[0m ${pkg.version} → ${latestVersion}`);
38
+ console.log(`Run \x1b[36mnpm install -g ${pkg.name}\x1b[0m to update\n`);
39
+ }
40
+ }
41
+ }
42
+ catch {
43
+ // Silently ignore update check errors (network issues, timeouts, etc.)
44
+ }
45
+ }
17
46
  /** Create the CLI program */
18
47
  export function createProgram() {
19
48
  const program = new Command();
20
49
  program
21
50
  .name("magicappdev")
22
51
  .description("CLI for creating and managing MagicAppDev apps")
23
- .version(VERSION, "-v, --version", "Display version number");
52
+ .version(VERSION, "-V, --version", "Display version number")
53
+ .option("-d, --debug", "Enable debug mode for verbose logging")
54
+ .hook("preAction", thisCommand => {
55
+ const opts = thisCommand.opts();
56
+ if (opts.debug) {
57
+ process.env.DEBUG = "true";
58
+ console.log(`[DEBUG] MagicAppDev CLI v${VERSION}`);
59
+ console.log(`[DEBUG] Node.js ${process.version}`);
60
+ console.log(`[DEBUG] Platform: ${process.platform} ${process.arch}`);
61
+ }
62
+ });
24
63
  // Add commands
25
64
  program.addCommand(initCommand);
26
65
  program.addCommand(authCommand);
@@ -32,6 +71,16 @@ export function createProgram() {
32
71
  }
33
72
  /** Run the CLI */
34
73
  export async function run(argv) {
74
+ // Handle Ctrl+C gracefully
75
+ process.on("SIGINT", () => {
76
+ console.log("\n\x1b[2mInterrupted.\x1b[0m");
77
+ process.exit(0);
78
+ });
79
+ process.on("SIGTERM", () => {
80
+ process.exit(0);
81
+ });
82
+ // Check for updates (non-blocking)
83
+ checkForUpdates();
35
84
  const program = createProgram();
36
85
  await program.parseAsync(argv || process.argv);
37
86
  }
@@ -18,11 +18,34 @@ authCommand
18
18
  // Setup local callback server
19
19
  const server = http.createServer(async (req, res) => {
20
20
  const url = new URL(req.url, `http://${req.headers.host}`);
21
+ // Ignore favicon and other non-callback requests
22
+ if (url.pathname !== "/" || req.method !== "GET") {
23
+ res.writeHead(204);
24
+ res.end();
25
+ return;
26
+ }
21
27
  const accessToken = url.searchParams.get("accessToken");
22
28
  const refreshToken = url.searchParams.get("refreshToken");
23
- if (accessToken && refreshToken) {
29
+ // If no tokens yet, this is just the initial browser request - wait for callback
30
+ if (!accessToken && !refreshToken) {
31
+ res.writeHead(200, { "Content-Type": "text/html" });
32
+ res.end("<h1>Authenticating...</h1><p>Please complete the GitHub login in the popup.</p>");
33
+ return;
34
+ }
35
+ // Validate accessToken is JWT format (basic check)
36
+ const isValidJwt = (token) => typeof token === "string" &&
37
+ token.split(".").length === 3 &&
38
+ token.length > 20 &&
39
+ token.length < 2000;
40
+ // Validate refreshToken is UUID format
41
+ const isValidUuid = (token) => typeof token === "string" &&
42
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(token);
43
+ if (isValidJwt(accessToken) && isValidUuid(refreshToken)) {
24
44
  // Store tokens
25
- await saveConfig({ accessToken, refreshToken });
45
+ await saveConfig({
46
+ accessToken: accessToken,
47
+ refreshToken: refreshToken,
48
+ });
26
49
  api.setToken(accessToken);
27
50
  info(`Access Token received and saved`);
28
51
  res.writeHead(200, { "Content-Type": "text/html" });
@@ -31,8 +54,9 @@ authCommand
31
54
  process.exit(0);
32
55
  }
33
56
  else {
34
- res.writeHead(400);
35
- res.end("Login failed: Missing tokens");
57
+ error("Login failed: Invalid token format received");
58
+ res.writeHead(400, { "Content-Type": "text/html" });
59
+ res.end("<h1>Login Failed</h1><p>Invalid token format received.</p>");
36
60
  }
37
61
  });
38
62
  server.listen(0, async () => {
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * Chat command - Interactive AI App Builder
3
+ * Uses native WebSocket with partykit-compatible URL
3
4
  */
4
5
  import { Command } from "commander";
5
6
  export declare const chatCommand: Command;
@@ -1 +1 @@
1
- {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC,eAAO,MAAM,WAAW,SA8BpB,CAAC"}
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBpC,eAAO,MAAM,WAAW,SAoEpB,CAAC"}
@@ -1,95 +1,205 @@
1
1
  /**
2
2
  * Chat command - Interactive AI App Builder
3
+ * Uses native WebSocket with partykit-compatible URL
3
4
  */
4
5
  import { header, logo, info } from "../lib/ui.js";
5
- import { AgentClient } from "agents/client";
6
6
  import { AGENT_HOST } from "../lib/api.js";
7
7
  import { Command } from "commander";
8
8
  import prompts from "prompts";
9
+ import WebSocket from "ws";
9
10
  import chalk from "chalk";
10
11
  import ora from "ora";
12
+ // Handle Ctrl+C in prompts
13
+ const onCancel = () => {
14
+ console.log(chalk.dim("\nGoodbye!"));
15
+ process.exit(0);
16
+ };
11
17
  export const chatCommand = new Command("chat")
12
18
  .description("Chat with the Magic AI App Builder")
13
- .action(async () => {
19
+ .option("-d, --debug", "Enable debug logging")
20
+ .action(async (options) => {
21
+ const debug = options.debug || process.env.DEBUG === "true";
14
22
  logo();
15
23
  header("Magic AI Assistant");
16
24
  info("Connecting to agent...");
17
25
  const spinner = ora("Initializing connection").start();
18
- // Initialize Agent Client
19
- const client = new AgentClient({
20
- host: AGENT_HOST,
21
- agent: "magic-agent",
22
- name: "default",
26
+ // Build WebSocket URL with party headers embedded
27
+ // The agents SDK expects: /parties/{namespace}/{room} or /agents/{namespace}/{room}
28
+ const wsUrl = `wss://${AGENT_HOST}/agents/magic-agent/default`;
29
+ if (debug) {
30
+ console.log(chalk.dim(`\n[DEBUG] Connecting to: ${wsUrl}`));
31
+ }
32
+ const ws = new WebSocket(wsUrl, {
33
+ headers: {
34
+ "x-partykit-namespace": "magic-agent",
35
+ "x-partykit-room": "default",
36
+ },
23
37
  });
24
- client.addEventListener("open", () => {
38
+ // Connection timeout
39
+ const connectionTimeout = setTimeout(() => {
40
+ if (ws.readyState !== WebSocket.OPEN) {
41
+ spinner.fail("Connection timeout - agent not responding");
42
+ ws.terminate();
43
+ process.exit(1);
44
+ }
45
+ }, 10000);
46
+ ws.on("open", () => {
47
+ clearTimeout(connectionTimeout);
25
48
  spinner.succeed("Connected to Magic AI Assistant");
26
- startChatLoop(client);
49
+ if (debug) {
50
+ console.log(chalk.dim("[DEBUG] WebSocket connection established"));
51
+ }
52
+ startChatLoop(ws, debug);
27
53
  });
28
- client.addEventListener("close", () => {
29
- spinner.fail("Disconnected from agent");
54
+ ws.on("close", (code, reason) => {
55
+ clearTimeout(connectionTimeout);
56
+ const reasonStr = reason?.toString() || "Connection closed";
57
+ if (debug) {
58
+ console.log(chalk.dim(`\n[DEBUG] WebSocket closed: ${code} - ${reasonStr}`));
59
+ }
60
+ if (code !== 1000) {
61
+ // 1000 = normal closure
62
+ spinner.fail(`Disconnected from agent (${code})`);
63
+ }
30
64
  process.exit(0);
31
65
  });
32
- client.addEventListener("error", (event) => {
33
- spinner.fail(`Connection error: ${event.message || "Unknown error"}`);
66
+ ws.on("error", (err) => {
67
+ clearTimeout(connectionTimeout);
68
+ spinner.fail(`Connection error: ${err.message}`);
69
+ if (debug) {
70
+ console.error(chalk.red(`\n[DEBUG] WebSocket error:`), err);
71
+ }
34
72
  process.exit(1);
35
73
  });
36
74
  });
37
- async function startChatLoop(client) {
75
+ async function startChatLoop(ws, debug) {
38
76
  console.log(chalk.dim("\nType your message below (type 'exit' to quit)"));
39
- while (true) {
77
+ // Track if we're waiting for a response
78
+ let waitingForResponse = false;
79
+ let currentResponse = "";
80
+ let responseSpinner = null;
81
+ let resolveResponse = null;
82
+ // Handle incoming messages
83
+ ws.on("message", (data) => {
84
+ try {
85
+ const raw = data.toString();
86
+ const message = JSON.parse(raw);
87
+ if (debug) {
88
+ console.log(chalk.dim(`\n[DEBUG] Received: ${message.type || "unknown"}`));
89
+ console.log(chalk.dim(`[DEBUG] Raw: ${raw.substring(0, 200)}...`));
90
+ }
91
+ // Handle different message types
92
+ switch (message.type) {
93
+ case "chat_start":
94
+ if (debug) {
95
+ console.log(chalk.dim(`[DEBUG] Using model: ${message.model}`));
96
+ }
97
+ break;
98
+ case "chat_chunk":
99
+ if (message.content) {
100
+ currentResponse += message.content;
101
+ if (responseSpinner) {
102
+ // Show last line of response in spinner
103
+ const lastLine = currentResponse.split("\n").pop() || "...";
104
+ responseSpinner.text = chalk.gray(lastLine.length > 60 ? lastLine.slice(-60) + "..." : lastLine);
105
+ }
106
+ }
107
+ break;
108
+ case "chat_done":
109
+ if (responseSpinner) {
110
+ responseSpinner.stop();
111
+ }
112
+ if (currentResponse) {
113
+ console.log(chalk.green("\nMagic AI:"), currentResponse);
114
+ }
115
+ else {
116
+ console.log(chalk.yellow("\nMagic AI: (No response received)"));
117
+ }
118
+ if (message.suggestedTemplate) {
119
+ console.log(chalk.yellow("\nSuggested Template:"), chalk.bold(message.suggestedTemplate));
120
+ console.log(chalk.dim(`Run 'magicappdev init --template ${message.suggestedTemplate}' to use it.`));
121
+ }
122
+ console.log(""); // Spacing
123
+ // Reset state and resolve promise
124
+ currentResponse = "";
125
+ waitingForResponse = false;
126
+ if (resolveResponse) {
127
+ resolveResponse();
128
+ resolveResponse = null;
129
+ }
130
+ break;
131
+ case "error":
132
+ if (responseSpinner) {
133
+ responseSpinner.fail(`Error: ${message.error || message.message || "Unknown error"}`);
134
+ }
135
+ currentResponse = "";
136
+ waitingForResponse = false;
137
+ if (resolveResponse) {
138
+ resolveResponse();
139
+ resolveResponse = null;
140
+ }
141
+ break;
142
+ case "state:update":
143
+ case "cf_agent_state":
144
+ // Ignore state updates from agents SDK
145
+ break;
146
+ default:
147
+ if (debug) {
148
+ console.log(chalk.dim(`[DEBUG] Ignored message type: ${message.type}`));
149
+ }
150
+ }
151
+ }
152
+ catch (err) {
153
+ if (debug) {
154
+ console.error(chalk.red("[DEBUG] Parse error:"), err);
155
+ }
156
+ }
157
+ });
158
+ // Main chat loop
159
+ while (ws.readyState === WebSocket.OPEN) {
40
160
  const response = await prompts({
41
161
  type: "text",
42
162
  name: "message",
43
163
  message: chalk.cyan("You:"),
44
- });
45
- if (!response.message || response.message.toLowerCase() === "exit") {
46
- client.close();
164
+ }, { onCancel });
165
+ // Handle Ctrl+C or empty input
166
+ if (!response.message) {
167
+ console.log(chalk.dim("\nGoodbye!"));
168
+ ws.close(1000, "User exit");
47
169
  break;
48
170
  }
49
- const spinner = ora("Magic AI is thinking...").start();
50
- let currentResponse = "";
51
- const messageHandler = (event) => {
52
- try {
53
- const data = JSON.parse(event.data);
54
- if (data.type === "chat_chunk") {
55
- currentResponse += data.content;
56
- spinner.text = chalk.gray(currentResponse.split("\n").pop() || "...");
57
- }
58
- else if (data.type === "chat_done") {
59
- spinner.stop();
60
- console.log(chalk.green("\nMagic AI:"), currentResponse);
61
- if (data.suggestedTemplate) {
62
- console.log(chalk.yellow("\n💡 Suggested Template:"), chalk.bold(data.suggestedTemplate));
63
- console.log(chalk.dim(`Run 'magicappdev init --template ${data.suggestedTemplate}' to use it.`));
64
- }
65
- console.log(""); // Spacing
66
- client.removeEventListener("message", messageHandler);
67
- }
68
- }
69
- catch {
70
- // Ignore parse errors for chunks
71
- }
72
- };
73
- client.addEventListener("message", messageHandler);
74
- client.send(JSON.stringify({
171
+ if (response.message.toLowerCase() === "exit") {
172
+ console.log(chalk.dim("\nGoodbye!"));
173
+ ws.close(1000, "User exit");
174
+ break;
175
+ }
176
+ // Send message
177
+ waitingForResponse = true;
178
+ currentResponse = "";
179
+ responseSpinner = ora("Magic AI is thinking...").start();
180
+ ws.send(JSON.stringify({
75
181
  type: "chat",
76
182
  content: response.message,
77
183
  }));
78
- // Wait for the response to finish before next prompt
184
+ // Wait for response to complete
79
185
  await new Promise(resolve => {
80
- const doneHandler = (event) => {
81
- try {
82
- const data = JSON.parse(event.data);
83
- if (data.type === "chat_done" || data.type === "error") {
84
- client.removeEventListener("message", doneHandler);
85
- resolve();
186
+ resolveResponse = resolve;
187
+ // Timeout for response
188
+ const responseTimeout = setTimeout(() => {
189
+ if (waitingForResponse) {
190
+ if (responseSpinner) {
191
+ responseSpinner.fail("Response timeout");
86
192
  }
193
+ waitingForResponse = false;
194
+ resolve();
87
195
  }
88
- catch {
89
- // Ignore
90
- }
196
+ }, 60000); // 60 second timeout for AI response
197
+ // Clean up timeout when resolved
198
+ const originalResolve = resolveResponse;
199
+ resolveResponse = () => {
200
+ clearTimeout(responseTimeout);
201
+ originalResolve();
91
202
  };
92
- client.addEventListener("message", doneHandler);
93
203
  });
94
204
  }
95
205
  }
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;GAEG;AA0BH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,eAAO,MAAM,WAAW,SA6GpB,CAAC"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;GAEG;AAyBH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8CpC,eAAO,MAAM,WAAW,SA2JpB,CAAC"}
@@ -1,11 +1,46 @@
1
1
  /**
2
2
  * Init command - Initialize a new MagicAppDev project
3
3
  */
4
- import { promptProjectName, promptFramework, promptStyling, promptTypeScript, promptSelect, } from "../lib/prompts.js";
4
+ import { promptProjectName, promptFramework, promptStyling, promptTypeScript, promptSelect, promptPreferences, } from "../lib/prompts.js";
5
5
  import { header, logo, success, error, info, keyValue, command, newline, divider, } from "../lib/ui.js";
6
- import { generateApp, blankAppTemplate, tabsAppTemplate, } from "@magicappdev/templates";
6
+ import { builtInTemplates } from "@magicappdev/templates";
7
+ import { generateApp } from "@magicappdev/templates";
7
8
  import { withSpinner } from "../lib/spinner.js";
9
+ import { spawn } from "child_process";
8
10
  import { Command } from "commander";
11
+ import * as path from "path";
12
+ /** Detect the package manager to use */
13
+ function detectPackageManager() {
14
+ const userAgent = process.env.npm_config_user_agent || "";
15
+ if (userAgent.includes("pnpm"))
16
+ return "pnpm";
17
+ if (userAgent.includes("yarn"))
18
+ return "yarn";
19
+ if (userAgent.includes("bun"))
20
+ return "bun";
21
+ return "npm";
22
+ }
23
+ /** Run package manager install */
24
+ async function runInstall(projectDir, pm) {
25
+ return new Promise(resolve => {
26
+ const child = spawn(pm, ["install"], {
27
+ cwd: projectDir,
28
+ stdio: "inherit",
29
+ shell: true,
30
+ });
31
+ child.on("close", code => {
32
+ if (code === 0) {
33
+ resolve({ success: true });
34
+ }
35
+ else {
36
+ resolve({ success: false, error: `Exit code ${code}` });
37
+ }
38
+ });
39
+ child.on("error", err => {
40
+ resolve({ success: false, error: err.message });
41
+ });
42
+ });
43
+ }
9
44
  export const initCommand = new Command("init")
10
45
  .description("Initialize a new MagicAppDev project")
11
46
  .argument("[name]", "Project name")
@@ -13,6 +48,7 @@ export const initCommand = new Command("init")
13
48
  .option("-f, --framework <framework>", "Framework (expo, react-native, next)")
14
49
  .option("--typescript", "Use TypeScript", true)
15
50
  .option("-y, --yes", "Skip prompts and use defaults")
51
+ .option("--no-install", "Skip installing dependencies")
16
52
  .action(async (name, options) => {
17
53
  logo();
18
54
  header("Create a new project");
@@ -34,7 +70,11 @@ export const initCommand = new Command("init")
34
70
  value: "blank",
35
71
  description: "Minimal starter template",
36
72
  },
37
- { title: "Tabs", value: "tabs", description: "Tab-based navigation" },
73
+ {
74
+ title: "Tabs",
75
+ value: "tabs",
76
+ description: "Tab-based navigation",
77
+ },
38
78
  ]);
39
79
  }
40
80
  if (!templateSlug) {
@@ -67,6 +107,11 @@ export const initCommand = new Command("init")
67
107
  ? "nativewind"
68
108
  : "tailwind";
69
109
  }
110
+ // Get additional preferences
111
+ let preferences = {};
112
+ if (!options.yes) {
113
+ preferences = (await promptPreferences()) || {};
114
+ }
70
115
  newline();
71
116
  divider();
72
117
  info("Creating project with:");
@@ -75,27 +120,46 @@ export const initCommand = new Command("init")
75
120
  keyValue("Framework", framework);
76
121
  keyValue("TypeScript", typescript ? "Yes" : "No");
77
122
  keyValue("Styling", styling);
123
+ keyValue("Preferences", Object.keys(preferences).join(", ") || "None");
78
124
  divider();
79
125
  newline();
80
126
  // Find template
81
- const template = templateSlug === "tabs" ? tabsAppTemplate : blankAppTemplate;
127
+ const template = builtInTemplates.find(t => t.id === templateSlug) ||
128
+ builtInTemplates[0];
82
129
  // Generate project
83
130
  const outputDir = process.cwd();
131
+ const projectDir = path.join(outputDir, projectName);
84
132
  const result = await withSpinner(`Creating ${projectName}...`, async () => {
85
133
  return generateApp(projectName, template, outputDir, {
86
134
  typescript,
87
135
  styling,
88
136
  framework,
89
- });
137
+ }, preferences);
90
138
  }, { successText: `Created ${projectName}` });
91
139
  newline();
92
140
  success(`Project created successfully!`);
93
141
  info(`Files created: ${result.files.length}`);
142
+ // Install dependencies
143
+ const shouldInstall = options.install !== false;
144
+ if (shouldInstall) {
145
+ const pm = detectPackageManager();
146
+ newline();
147
+ const installResult = await withSpinner(`Installing dependencies with ${pm}...`, async () => runInstall(projectDir, pm), { successText: "Dependencies installed" });
148
+ if (!installResult.success) {
149
+ error(`Failed to install dependencies: ${installResult.error || "Unknown error"}`);
150
+ info("You can install them manually:");
151
+ command(`cd ${projectName}`);
152
+ command(`${pm} install`);
153
+ }
154
+ }
94
155
  newline();
95
156
  info("Next steps:");
96
157
  command(`cd ${projectName}`);
97
- command("npm install");
98
- command("npm start");
158
+ if (!shouldInstall || options.install === false) {
159
+ const pm = detectPackageManager();
160
+ command(`${pm} install`);
161
+ }
162
+ command(`${detectPackageManager()} start`);
99
163
  newline();
100
164
  }
101
165
  catch (err) {
@@ -14,6 +14,15 @@ export declare function promptSelect<T>(message: string, choices: Array<{
14
14
  }>, options?: {
15
15
  initial?: number;
16
16
  }): Promise<T | undefined>;
17
+ /** Prompt for multi-selection from a list */
18
+ export declare function promptMultiSelect<T>(message: string, choices: Array<{
19
+ title: string;
20
+ value: T;
21
+ description?: string;
22
+ }>, options?: {
23
+ min?: number;
24
+ max?: number;
25
+ }): Promise<T[]>;
17
26
  /** Prompt for confirmation (yes/no) */
18
27
  export declare function promptConfirm(message: string, options?: {
19
28
  initial?: boolean;
@@ -22,6 +31,8 @@ export declare function promptConfirm(message: string, options?: {
22
31
  export declare function promptProjectName(initial?: string): Promise<string | undefined>;
23
32
  /** Prompt for framework selection */
24
33
  export declare function promptFramework(): Promise<string | undefined>;
34
+ /** Prompt for additional preferences */
35
+ export declare function promptPreferences(): Promise<Record<string, boolean>>;
25
36
  /** Prompt for styling selection */
26
37
  export declare function promptStyling(framework: string): Promise<string | undefined>;
27
38
  /** Prompt for TypeScript */
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/lib/prompts.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,4BAA4B;AAC5B,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,MAAM,CAAC;CAC3C,GACL,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAU7B;AAED,uCAAuC;AACvC,wBAAsB,YAAY,CAAC,CAAC,EAClC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EACjE,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAC;CACb,GACL,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAUxB;AAED,uCAAuC;AACvC,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,OAAO,CAAC;CACd,GACL,OAAO,CAAC,OAAO,CAAC,CASlB;AAED,8CAA8C;AAC9C,wBAAsB,iBAAiB,CACrC,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAa7B;AAED,qCAAqC;AACrC,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAmBnE;AAED,mCAAmC;AACnC,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA0C7B;AAED,4BAA4B;AAC5B,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,CAEzD"}
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/lib/prompts.ts"],"names":[],"mappings":"AAAA;;GAEG;AAWH,4BAA4B;AAC5B,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,MAAM,CAAC;CAC3C,GACL,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAa7B;AAED,uCAAuC;AACvC,wBAAsB,YAAY,CAAC,CAAC,EAClC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EACjE,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAC;CACb,GACL,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAaxB;AAED,6CAA6C;AAC7C,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EACjE,OAAO,GAAE;IACP,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACT,GACL,OAAO,CAAC,CAAC,EAAE,CAAC,CAed;AAED,uCAAuC;AACvC,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,OAAO,CAAC;CACd,GACL,OAAO,CAAC,OAAO,CAAC,CAYlB;AAED,8CAA8C;AAC9C,wBAAsB,iBAAiB,CACrC,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAa7B;AAED,qCAAqC;AACrC,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAanE;AAED,wCAAwC;AACxC,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAuC1E;AAED,mCAAmC;AACnC,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA0C7B;AAED,4BAA4B;AAC5B,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,CAEzD"}
@@ -3,6 +3,11 @@
3
3
  */
4
4
  import prompts from "prompts";
5
5
  import chalk from "chalk";
6
+ // Handle Ctrl+C in prompts - exit cleanly
7
+ const onCancel = () => {
8
+ console.log(chalk.dim("\nCancelled."));
9
+ process.exit(0);
10
+ };
6
11
  /** Prompt for text input */
7
12
  export async function promptText(message, options = {}) {
8
13
  const response = await prompts({
@@ -11,7 +16,7 @@ export async function promptText(message, options = {}) {
11
16
  message,
12
17
  initial: options.initial,
13
18
  validate: options.validate,
14
- });
19
+ }, { onCancel });
15
20
  return response.value;
16
21
  }
17
22
  /** Prompt for selection from a list */
@@ -22,9 +27,22 @@ export async function promptSelect(message, choices, options = {}) {
22
27
  message,
23
28
  choices,
24
29
  initial: options.initial || 0,
25
- });
30
+ }, { onCancel });
26
31
  return response.value;
27
32
  }
33
+ /** Prompt for multi-selection from a list */
34
+ export async function promptMultiSelect(message, choices, options = {}) {
35
+ const response = await prompts({
36
+ type: "multiselect",
37
+ name: "value",
38
+ message,
39
+ choices,
40
+ min: options.min,
41
+ max: options.max,
42
+ hint: "- Space to select. Return to submit",
43
+ }, { onCancel });
44
+ return response.value || [];
45
+ }
28
46
  /** Prompt for confirmation (yes/no) */
29
47
  export async function promptConfirm(message, options = {}) {
30
48
  const response = await prompts({
@@ -32,7 +50,7 @@ export async function promptConfirm(message, options = {}) {
32
50
  name: "value",
33
51
  message,
34
52
  initial: options.initial ?? true,
35
- });
53
+ }, { onCancel });
36
54
  return response.value;
37
55
  }
38
56
  /** Prompt for project name with validation */
@@ -63,13 +81,41 @@ export async function promptFramework() {
63
81
  value: "react-native",
64
82
  description: "React Native without Expo",
65
83
  },
84
+ ]);
85
+ }
86
+ /** Prompt for additional preferences */
87
+ export async function promptPreferences() {
88
+ const preferences = await promptMultiSelect("Select additional preferences:", [
89
+ {
90
+ title: "ESLint (code linting)",
91
+ value: "eslint",
92
+ description: "Add ESLint for code quality",
93
+ },
94
+ {
95
+ title: "Prettier (code formatting)",
96
+ value: "prettier",
97
+ description: "Add Prettier for code formatting",
98
+ },
99
+ {
100
+ title: "Jest (testing framework)",
101
+ value: "jest",
102
+ description: "Add Jest for testing",
103
+ },
104
+ {
105
+ title: "TypeScript (recommended)",
106
+ value: "typescript",
107
+ description: "Use TypeScript",
108
+ },
66
109
  {
67
- title: "Next.js",
68
- value: "next",
69
- description: "React framework for the web",
110
+ title: "Tailwind CSS (styling)",
111
+ value: "tailwind",
112
+ description: "Add Tailwind CSS for styling",
70
113
  },
71
- { title: "Remix", value: "remix", description: "Full stack web framework" },
72
114
  ]);
115
+ return preferences.reduce((acc, pref) => {
116
+ acc[pref] = true;
117
+ return acc;
118
+ }, {});
73
119
  }
74
120
  /** Prompt for styling selection */
75
121
  export async function promptStyling(framework) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magicappdev/cli",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "CLI tool for creating and managing MagicAppDev apps",
5
5
  "keywords": [
6
6
  "cli",