webcake-landing-mcp 1.0.1 → 1.0.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 CHANGED
@@ -92,6 +92,32 @@ Or run the latest from GitHub (npx clones + builds via the `prepare` script on t
92
92
  npx -y github:vuluu2k/webcake-landing-mcp
93
93
  ```
94
94
 
95
+ ### Auto-configure your IDE (`install` subcommand)
96
+
97
+ `npx` only **runs** the server — unlike `install.sh`/`install.ps1`, it does not write the MCP
98
+ config into your IDE. The bundled `install` subcommand does that step for you, no clone needed:
99
+
100
+ ```bash
101
+ # Interactive — asks for env + which IDE(s) step by step
102
+ npx -y webcake-landing-mcp install
103
+
104
+ # Non-interactive — configure every supported IDE at once
105
+ npx -y webcake-landing-mcp install --ide all --jwt <your-jwt> --api-base http://localhost:5800
106
+
107
+ # Just one IDE
108
+ npx -y webcake-landing-mcp install --ide cursor --jwt <your-jwt>
109
+
110
+ # Remove the server from every IDE config
111
+ npx -y webcake-landing-mcp uninstall
112
+ ```
113
+
114
+ It writes a `webcake-landing` entry (using the `npx` launch form below) into the right config file
115
+ for each target: `claude-desktop`, `claude-code`, `cursor`, `windsurf`, `augment` (VS Code), `codex`,
116
+ or `all`. Flags: `--ide`, `--api-base`, `--jwt`, `--org-id`, `--host`, `--app-base`, `--npx`/`--local`,
117
+ `-y`. Run `npx -y webcake-landing-mcp --help` for the full list.
118
+
119
+ ### Manual config
120
+
95
121
  The MCP config is the same as the local one, but `command`/`args` point at `npx` instead of a built file:
96
122
 
97
123
  ```json
package/dist/factory.js CHANGED
@@ -30,7 +30,8 @@ export const CONTAINER_TYPES = new Set([
30
30
  "slide",
31
31
  "popup",
32
32
  "form",
33
- "gallery",
33
+ // NOTE: "gallery" is intentionally NOT here — gallery.js reads specials.media only,
34
+ // it never reads vm.children. gallery is a leaf element.
34
35
  "checkbox-group",
35
36
  "radio",
36
37
  "group-select",
@@ -158,7 +159,7 @@ export function createElement(type, overrides = {}) {
158
159
  seedPosition(el);
159
160
  setBox(el, 350, 400);
160
161
  el.specials.media = [imgPlaceholder(600, 400, "1"), imgPlaceholder(600, 400, "2"), imgPlaceholder(600, 400, "3")];
161
- el.children = [];
162
+ // gallery has NO children content comes entirely from specials.media (gallery.js never reads vm.children)
162
163
  break;
163
164
  case "popup":
164
165
  el.properties.movable = false;
@@ -188,6 +189,9 @@ export function createElement(type, overrides = {}) {
188
189
  setBox(el, 150, 36);
189
190
  if (FIELD_TYPES.has(type))
190
191
  el.specials.field_name = `${type.replace(/-/g, "_")}_${el.id}`;
192
+ // cart-quantity is NOT in FIELD_TYPES but its renderer requires field_name — seed it explicitly.
193
+ if (type === "cart-quantity")
194
+ el.specials.field_name = `cart_quantity_${el.id}`;
191
195
  break;
192
196
  case "signature":
193
197
  seedPosition(el);
@@ -225,7 +229,7 @@ export function createElement(type, overrides = {}) {
225
229
  setStyle(el, "color", "rgba(255, 255, 255, 1)");
226
230
  setStyle(el, "background", "rgba(0, 0, 0, 1)");
227
231
  setStyle(el, "fontSize", 20);
228
- el.specials = { type: "minute", duration: "60", showDay: true, showSecond: true, showText: true };
232
+ el.specials = { type: "minute", duration: "60", showDay: true, showSecond: true, showText: true, repeat: false, customize: false, customMessage: "", dailyStart: "", dailyEnd: "" };
229
233
  break;
230
234
  case "timegroup":
231
235
  seedPosition(el);
@@ -245,10 +249,12 @@ export function createElement(type, overrides = {}) {
245
249
  el.responsive.mobile.styles.left = 0;
246
250
  el.responsive.desktop.styles.top = 0;
247
251
  el.responsive.desktop.styles.left = 0;
252
+ el.specials.html = "";
248
253
  break;
249
254
  case "html-box":
250
255
  seedPosition(el);
251
256
  setBox(el, 280, 310);
257
+ el.specials.html = "";
252
258
  break;
253
259
  case "spin-wheel":
254
260
  seedPosition(el);
@@ -316,6 +322,7 @@ export function createElement(type, overrides = {}) {
316
322
  imageWidth: 100,
317
323
  multiOption: false,
318
324
  alignment: "center",
325
+ hoveredBorder: "rgba(28,0,194,1)",
319
326
  options: [
320
327
  { id: randomId(), image: "", title: "Option 1", value: "value1", field_name: `sv_${el.id}_1` },
321
328
  { id: randomId(), image: "", title: "Option 2", value: "value2", field_name: `sv_${el.id}_2` },
package/dist/index.js CHANGED
@@ -233,6 +233,20 @@ server.tool("update_page", "Overwrite an EXISTING page's source with an edited t
233
233
  return text({ updated: outcome.ok, ...outcome, warnings: result.warnings });
234
234
  });
235
235
  async function main() {
236
+ // Subcommand dispatch: `webcake-landing-mcp install|uninstall` runs the
237
+ // bundled IDE installer instead of starting the MCP server. Default (no
238
+ // subcommand) starts the stdio server as usual.
239
+ const sub = process.argv[2];
240
+ if (sub === "install" || sub === "uninstall" || sub === "--help" || sub === "-h") {
241
+ const { runInstaller } = await import("./install.js");
242
+ const rest = sub === "uninstall"
243
+ ? ["--uninstall", ...process.argv.slice(3)]
244
+ : sub === "--help" || sub === "-h"
245
+ ? ["--help"]
246
+ : process.argv.slice(3);
247
+ await runInstaller(rest);
248
+ return;
249
+ }
236
250
  const transport = new StdioServerTransport();
237
251
  await server.connect(transport);
238
252
  // stderr only — stdout is the MCP channel.
@@ -0,0 +1,395 @@
1
+ /**
2
+ * Self-installer for webcake-landing-mcp.
3
+ *
4
+ * Runs when the server is invoked as `webcake-landing-mcp install` (e.g. via
5
+ * `npx -y webcake-landing-mcp install`). It collects env config and writes the
6
+ * MCP server block into each selected IDE/agent config file — the same job the
7
+ * standalone install.sh / install.ps1 do, but bundled so npx users don't need
8
+ * to clone anything.
9
+ *
10
+ * Two modes:
11
+ * - Interactive (a TTY and no --ide flag): asks step by step.
12
+ * - Flag-driven (--ide present, or no TTY): non-interactive.
13
+ *
14
+ * The launch command written into configs is `npx -y webcake-landing-mcp` when
15
+ * this installer itself was run via npx (path-independent), or
16
+ * `node <abs path>/dist/index.js` when run from a local clone. Override with
17
+ * --npx / --local.
18
+ */
19
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
20
+ import { homedir, platform } from "node:os";
21
+ import { dirname, join } from "node:path";
22
+ import { fileURLToPath } from "node:url";
23
+ import { spawnSync } from "node:child_process";
24
+ import { createInterface } from "node:readline";
25
+ const NAME = "webcake-landing";
26
+ const PKG = "webcake-landing-mcp";
27
+ const HOME = homedir();
28
+ const PLAT = platform(); // 'darwin' | 'linux' | 'win32'
29
+ const APPDATA = process.env.APPDATA || join(HOME, "AppData", "Roaming");
30
+ const c = {
31
+ reset: "\x1b[0m",
32
+ bold: "\x1b[1m",
33
+ red: "\x1b[31m",
34
+ green: "\x1b[32m",
35
+ yellow: "\x1b[33m",
36
+ cyan: "\x1b[36m",
37
+ gray: "\x1b[90m",
38
+ };
39
+ const log = (m = "", color = "") => console.log(`${color}${m}${c.reset}`);
40
+ const info = (m) => log(` ${c.cyan}›${c.reset} ${m}`);
41
+ const ok = (m) => log(` ${c.green}✓${c.reset} ${m}`);
42
+ const warn = (m) => log(` ${c.yellow}!${c.reset} ${m}`);
43
+ // ── arg parsing ──────────────────────────────────────────────────────────────
44
+ function parseArgs(argv) {
45
+ const o = { yes: false, uninstall: false };
46
+ const val = (a, i) => a.includes("=") ? a.slice(a.indexOf("=") + 1) : argv[++i];
47
+ for (let i = 0; i < argv.length; i++) {
48
+ const a = argv[i];
49
+ const next = () => (a.includes("=") ? a.slice(a.indexOf("=") + 1) : argv[++i]);
50
+ if (a === "--uninstall" || a === "uninstall")
51
+ o.uninstall = true;
52
+ else if (a === "-y" || a === "--yes")
53
+ o.yes = true;
54
+ else if (a === "--npx")
55
+ o.npx = true;
56
+ else if (a === "--local")
57
+ o.local = true;
58
+ else if (a.startsWith("--ide"))
59
+ o.ide = next();
60
+ else if (a.startsWith("--api-base"))
61
+ o.apiBase = next();
62
+ else if (a.startsWith("--jwt") || a.startsWith("--token"))
63
+ o.jwt = next();
64
+ else if (a.startsWith("--org-id") || a.startsWith("--org"))
65
+ o.orgId = next();
66
+ else if (a.startsWith("--host"))
67
+ o.host = next();
68
+ else if (a.startsWith("--app-base"))
69
+ o.appBase = next();
70
+ else if (a === "--help" || a === "-h")
71
+ o.ide = "__help__";
72
+ void val;
73
+ }
74
+ return o;
75
+ }
76
+ // ── readline prompt ──────────────────────────────────────────────────────────
77
+ function ask(question) {
78
+ return new Promise((resolve) => {
79
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
80
+ rl.question(question, (answer) => {
81
+ rl.close();
82
+ resolve(answer.trim());
83
+ });
84
+ });
85
+ }
86
+ // ── launch command (npx vs local node) ───────────────────────────────────────
87
+ function resolveLaunch(o) {
88
+ const self = fileURLToPath(import.meta.url); // .../dist/install.js
89
+ const ranViaNpx = self.includes(`${"/"}_npx${"/"}`) || self.includes("\\_npx\\");
90
+ const useLocal = o.local ?? (!o.npx && !ranViaNpx);
91
+ if (useLocal) {
92
+ const indexPath = join(dirname(self), "index.js");
93
+ return { command: process.execPath, args: [indexPath] };
94
+ }
95
+ return { command: "npx", args: ["-y", PKG] };
96
+ }
97
+ // ── JSON config (Claude Desktop, Claude Code, Cursor, Windsurf, VS Code) ──────
98
+ function mergeJson(file, launch, env) {
99
+ mkdirSync(dirname(file), { recursive: true });
100
+ let cfg = {};
101
+ if (existsSync(file)) {
102
+ const raw = readFileSync(file, "utf8").trim();
103
+ if (raw) {
104
+ try {
105
+ cfg = JSON.parse(raw);
106
+ }
107
+ catch (e) {
108
+ warn(`Skip ${file} (invalid JSON: ${e.message})`);
109
+ return false;
110
+ }
111
+ }
112
+ }
113
+ if (typeof cfg.mcpServers !== "object" || !cfg.mcpServers)
114
+ cfg.mcpServers = {};
115
+ cfg.mcpServers[NAME] = {
116
+ command: launch.command,
117
+ args: launch.args,
118
+ ...(Object.keys(env).length ? { env } : {}),
119
+ };
120
+ writeFileSync(file, JSON.stringify(cfg, null, 2) + "\n");
121
+ return true;
122
+ }
123
+ // ── TOML config (Codex) ──────────────────────────────────────────────────────
124
+ function configureCodex(launch, env) {
125
+ const dir = join(HOME, ".codex");
126
+ const cfg = join(dir, "config.toml");
127
+ mkdirSync(dir, { recursive: true });
128
+ const argsToml = launch.args.map((a) => `"${a}"`).join(", ");
129
+ const envParts = Object.entries(env)
130
+ .map(([k, v]) => `"${k}" = "${v}"`)
131
+ .join(", ");
132
+ const envLine = envParts ? `env = { ${envParts} }\n` : "";
133
+ const block = `\n[mcp_servers.${NAME}]\ncommand = "${launch.command}"\nargs = [${argsToml}]\n${envLine}`;
134
+ let content = existsSync(cfg) ? readFileSync(cfg, "utf8") : "# Webcake Landing MCP\n";
135
+ content = content.replace(new RegExp(`\\n?\\[mcp_servers\\.${NAME}\\][\\s\\S]*?(?=\\n\\[|$)`), "");
136
+ content = content.trimEnd() + "\n" + block;
137
+ writeFileSync(cfg, content);
138
+ }
139
+ // ── IDE config-file locations ────────────────────────────────────────────────
140
+ function claudeDesktopPath() {
141
+ if (PLAT === "win32")
142
+ return join(APPDATA, "Claude", "claude_desktop_config.json");
143
+ const mac = join(HOME, "Library", "Application Support", "Claude");
144
+ const dir = existsSync(mac) ? mac : join(HOME, ".config", "Claude");
145
+ return join(dir, "claude_desktop_config.json");
146
+ }
147
+ function vscodeUserPath() {
148
+ if (PLAT === "win32")
149
+ return join(APPDATA, "Code", "User", "mcp.json");
150
+ const mac = join(HOME, "Library", "Application Support", "Code", "User");
151
+ if (existsSync(mac))
152
+ return join(mac, "mcp.json");
153
+ const lin = join(HOME, ".config", "Code", "User");
154
+ if (existsSync(lin))
155
+ return join(lin, "mcp.json");
156
+ return join(HOME, ".vscode", "mcp.json");
157
+ }
158
+ const cursorPath = () => join(HOME, ".cursor", "mcp.json");
159
+ const windsurfPath = () => join(HOME, ".codeium", "windsurf", "mcp_config.json");
160
+ const claudeJsonPath = () => join(HOME, ".claude.json");
161
+ function hasClaudeCli() {
162
+ const probe = spawnSync(PLAT === "win32" ? "where" : "which", ["claude"], {
163
+ stdio: "ignore",
164
+ });
165
+ return probe.status === 0;
166
+ }
167
+ // ── per-IDE configure ────────────────────────────────────────────────────────
168
+ function configureClaudeCode(launch, env) {
169
+ info("Claude Code…");
170
+ if (hasClaudeCli()) {
171
+ spawnSync("claude", ["mcp", "remove", NAME], { stdio: "ignore" });
172
+ const envFlags = Object.entries(env).flatMap(([k, v]) => ["-e", `${k}=${v}`]);
173
+ const r = spawnSync("claude", ["mcp", "add", NAME, ...envFlags, "--", launch.command, ...launch.args], { stdio: "inherit" });
174
+ if (r.status === 0) {
175
+ ok("Claude Code configured via CLI — verify: claude mcp list");
176
+ return;
177
+ }
178
+ warn("claude CLI failed — falling back to ~/.claude.json");
179
+ }
180
+ if (mergeJson(claudeJsonPath(), launch, env))
181
+ ok(`Claude Code configured (${claudeJsonPath()})`);
182
+ }
183
+ function configureClaudeDesktop(launch, env) {
184
+ info("Claude Desktop…");
185
+ if (mergeJson(claudeDesktopPath(), launch, env)) {
186
+ ok(`Claude Desktop configured (${claudeDesktopPath()})`);
187
+ warn("Restart Claude Desktop to load the server.");
188
+ }
189
+ }
190
+ function configureCursor(launch, env) {
191
+ info("Cursor…");
192
+ if (mergeJson(cursorPath(), launch, env))
193
+ ok(`Cursor configured (${cursorPath()})`);
194
+ }
195
+ function configureWindsurf(launch, env) {
196
+ info("Windsurf…");
197
+ if (mergeJson(windsurfPath(), launch, env))
198
+ ok(`Windsurf configured (${windsurfPath()})`);
199
+ }
200
+ function configureAugment(launch, env) {
201
+ info("Augment / VS Code…");
202
+ if (mergeJson(vscodeUserPath(), launch, env))
203
+ ok(`VS Code configured (${vscodeUserPath()})`);
204
+ }
205
+ function configureCodexIde(launch, env) {
206
+ info("Codex…");
207
+ configureCodex(launch, env);
208
+ ok(`Codex configured (${join(HOME, ".codex", "config.toml")}) — restart Codex.`);
209
+ }
210
+ const IDE_ALIASES = {
211
+ "claude-desktop": "claude-desktop",
212
+ desktop: "claude-desktop",
213
+ "claude-code": "claude-code",
214
+ claude: "claude-code",
215
+ code: "claude-code",
216
+ cursor: "cursor",
217
+ windsurf: "windsurf",
218
+ augment: "augment",
219
+ vscode: "augment",
220
+ codex: "codex",
221
+ all: "all",
222
+ };
223
+ function runConfigure(ides, launch, env) {
224
+ const set = new Set(ides);
225
+ if (set.has("all")) {
226
+ configureClaudeDesktop(launch, env);
227
+ configureClaudeCode(launch, env);
228
+ configureCursor(launch, env);
229
+ configureWindsurf(launch, env);
230
+ configureAugment(launch, env);
231
+ configureCodexIde(launch, env);
232
+ return;
233
+ }
234
+ for (const id of set) {
235
+ if (id === "claude-desktop")
236
+ configureClaudeDesktop(launch, env);
237
+ else if (id === "claude-code")
238
+ configureClaudeCode(launch, env);
239
+ else if (id === "cursor")
240
+ configureCursor(launch, env);
241
+ else if (id === "windsurf")
242
+ configureWindsurf(launch, env);
243
+ else if (id === "augment")
244
+ configureAugment(launch, env);
245
+ else if (id === "codex")
246
+ configureCodexIde(launch, env);
247
+ else
248
+ warn(`Unknown IDE: ${id}`);
249
+ }
250
+ }
251
+ // ── uninstall ────────────────────────────────────────────────────────────────
252
+ function removeFromJson(file) {
253
+ if (!existsSync(file))
254
+ return;
255
+ try {
256
+ const cfg = JSON.parse(readFileSync(file, "utf8"));
257
+ if (cfg.mcpServers && cfg.mcpServers[NAME]) {
258
+ delete cfg.mcpServers[NAME];
259
+ writeFileSync(file, JSON.stringify(cfg, null, 2) + "\n");
260
+ ok(`Cleaned ${file}`);
261
+ }
262
+ }
263
+ catch {
264
+ /* ignore unparseable files */
265
+ }
266
+ }
267
+ function uninstall() {
268
+ log(`\n${c.bold}Uninstalling ${PKG}${c.reset}\n`);
269
+ if (hasClaudeCli())
270
+ spawnSync("claude", ["mcp", "remove", NAME], { stdio: "ignore" });
271
+ [
272
+ claudeJsonPath(),
273
+ claudeDesktopPath(),
274
+ join(HOME, ".config", "Claude", "claude_desktop_config.json"),
275
+ cursorPath(),
276
+ windsurfPath(),
277
+ vscodeUserPath(),
278
+ ].forEach(removeFromJson);
279
+ const codex = join(HOME, ".codex", "config.toml");
280
+ if (existsSync(codex)) {
281
+ let content = readFileSync(codex, "utf8");
282
+ content = content.replace(new RegExp(`\\n?\\[mcp_servers\\.${NAME}\\][\\s\\S]*?(?=\\n\\[|$)`), "");
283
+ writeFileSync(codex, content.trimEnd() + "\n");
284
+ ok("Cleaned Codex config.toml");
285
+ }
286
+ log(`\n${c.green}Done. Restart your IDE.${c.reset}\n`);
287
+ }
288
+ function printHelp() {
289
+ log(`
290
+ ${c.bold}webcake-landing-mcp install${c.reset} — configure the MCP server in your IDE(s)
291
+
292
+ ${c.bold}Usage${c.reset}
293
+ npx -y ${PKG} install # interactive (asks step by step)
294
+ npx -y ${PKG} install --ide all # non-interactive, all IDEs
295
+ npx -y ${PKG} install --ide claude-code --jwt <JWT> --api-base http://localhost:5800
296
+ npx -y ${PKG} uninstall # remove from every IDE config
297
+
298
+ ${c.bold}Flags${c.reset}
299
+ --ide <list> comma list: claude-desktop, claude-code, cursor, windsurf, augment, codex, all
300
+ --api-base <url> WEBCAKE_API_BASE (default http://localhost:5800)
301
+ --jwt <token> WEBCAKE_JWT (account token; optional, needed to persist)
302
+ --org-id <id> WEBCAKE_ORG_ID (optional)
303
+ --host <host> WEBCAKE_HOST (optional)
304
+ --app-base <url> WEBCAKE_APP_BASE (optional)
305
+ --npx | --local force the launch command form (default: auto-detect)
306
+ -y, --yes accept defaults, skip confirmations
307
+ --uninstall remove the server from all IDE configs
308
+ `);
309
+ }
310
+ // ── main ─────────────────────────────────────────────────────────────────────
311
+ export async function runInstaller(argv) {
312
+ const o = parseArgs(argv);
313
+ if (o.ide === "__help__")
314
+ return printHelp();
315
+ if (o.uninstall)
316
+ return uninstall();
317
+ log(`\n${c.cyan}${c.bold}Webcake Landing MCP — installer${c.reset}`);
318
+ log(`${c.gray}Build & edit Webcake landing pages from a prompt. 12 tools.${c.reset}`);
319
+ const interactive = !o.ide && process.stdin.isTTY && process.stdout.isTTY;
320
+ // 1) env
321
+ const env = {};
322
+ let apiBase = o.apiBase ?? process.env.WEBCAKE_API_BASE ?? "";
323
+ let jwt = o.jwt ?? process.env.WEBCAKE_JWT ?? "";
324
+ let orgId = o.orgId ?? process.env.WEBCAKE_ORG_ID ?? "";
325
+ const host = o.host ?? process.env.WEBCAKE_HOST ?? "";
326
+ const appBase = o.appBase ?? process.env.WEBCAKE_APP_BASE ?? "";
327
+ if (interactive) {
328
+ log(`\n${c.bold}1) Config${c.reset} ${c.gray}(Enter to skip — reference tools work with no creds)${c.reset}`);
329
+ apiBase =
330
+ (await ask(` WEBCAKE_API_BASE [${apiBase || "http://localhost:5800"}]: `)) ||
331
+ apiBase ||
332
+ "http://localhost:5800";
333
+ jwt = (await ask(` WEBCAKE_JWT (account token, optional): `)) || jwt;
334
+ orgId = (await ask(` WEBCAKE_ORG_ID (optional): `)) || orgId;
335
+ }
336
+ else if (!apiBase) {
337
+ apiBase = "http://localhost:5800";
338
+ }
339
+ if (apiBase)
340
+ env.WEBCAKE_API_BASE = apiBase;
341
+ if (jwt)
342
+ env.WEBCAKE_JWT = jwt;
343
+ if (orgId)
344
+ env.WEBCAKE_ORG_ID = orgId;
345
+ if (host)
346
+ env.WEBCAKE_HOST = host;
347
+ if (appBase)
348
+ env.WEBCAKE_APP_BASE = appBase;
349
+ // 2) which IDEs
350
+ let ides = [];
351
+ if (o.ide) {
352
+ ides = o.ide
353
+ .split(",")
354
+ .map((s) => IDE_ALIASES[s.trim().toLowerCase()])
355
+ .filter(Boolean);
356
+ }
357
+ else if (interactive) {
358
+ log(`\n${c.bold}2) Which IDE(s) to configure?${c.reset}`);
359
+ log(" 1) Claude Desktop 2) Claude Code (CLI) 3) Cursor");
360
+ log(" 4) Windsurf 5) Augment (VS Code) 6) Codex");
361
+ log(" 7) All 0) Skip");
362
+ const pick = await ask(" Select (comma-separated, e.g. 1,2): ");
363
+ const map = {
364
+ "1": "claude-desktop",
365
+ "2": "claude-code",
366
+ "3": "cursor",
367
+ "4": "windsurf",
368
+ "5": "augment",
369
+ "6": "codex",
370
+ "7": "all",
371
+ };
372
+ ides = pick
373
+ .split(",")
374
+ .map((s) => map[s.trim()])
375
+ .filter(Boolean);
376
+ }
377
+ else {
378
+ warn("No --ide given and not a TTY. Nothing to configure.");
379
+ printHelp();
380
+ return;
381
+ }
382
+ if (!ides.length) {
383
+ warn("No IDE selected — skipping configuration.");
384
+ return;
385
+ }
386
+ // 3) write
387
+ const launch = resolveLaunch(o);
388
+ log(`\n${c.bold}3) Writing config${c.reset} ${c.gray}(launch: ${launch.command} ${launch.args.join(" ")})${c.reset}`);
389
+ runConfigure(ides, launch, env);
390
+ // 4) summary
391
+ log(`\n${c.green}${c.bold}✓ Done.${c.reset}`);
392
+ log(` ${c.gray}API base : ${apiBase || "(unset)"}${c.reset}`);
393
+ log(` ${c.gray}JWT : ${jwt ? jwt.slice(0, 8) + "…" : "(unset — reference tools still work)"}${c.reset}`);
394
+ log(` Restart your IDE, then ask the AI: “Build a Webcake landing page”.\n`);
395
+ }