webcake-landing-mcp 1.0.9 → 1.0.10

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
@@ -147,14 +147,14 @@ npx -y github:vuluu2k/webcake-landing-mcp
147
147
  config into your IDE. The bundled `install` subcommand does that step for you, no clone needed:
148
148
 
149
149
  ```bash
150
- # Interactive — asks for env + which IDE(s) step by step
150
+ # Interactive — pick environment, log in via browser (or paste a JWT), pick IDE(s)
151
151
  npx -y webcake-landing-mcp install
152
152
 
153
- # Non-interactive — configure every supported IDE at once
154
- npx -y webcake-landing-mcp install --ide all --jwt <your-jwt> --api-base http://localhost:5800
153
+ # Non-interactive — configure every supported IDE at once (env + token via flags)
154
+ npx -y webcake-landing-mcp install --ide all --env prod --jwt <your-jwt>
155
155
 
156
- # Just one IDE
157
- npx -y webcake-landing-mcp install --ide cursor --jwt <your-jwt>
156
+ # Local dev — point at your local stack (localhost:5800 / :5173)
157
+ npx -y webcake-landing-mcp install --ide cursor --env local --jwt <your-jwt>
158
158
 
159
159
  # Remove the server from every IDE config
160
160
  npx -y webcake-landing-mcp uninstall
@@ -162,8 +162,10 @@ npx -y webcake-landing-mcp uninstall
162
162
 
163
163
  It writes a `webcake-landing` entry (using the `npx` launch form below) into the right config file
164
164
  for each target: `claude-desktop`, `claude-code`, `cursor`, `windsurf`, `augment` (VS Code), `codex`,
165
- or `all`. Flags: `--ide`, `--api-base`, `--jwt`, `--org-id`, `--host`, `--app-base`, `--npx`/`--local`,
166
- `-y`. Run `npx -y webcake-landing-mcp --help` for the full list.
165
+ or `all`. Interactively it asks for the **environment** (`local`/`staging`/`prod`, which sets the API +
166
+ app URLs) and whether to **log in via the browser or paste a JWT**. Flags: `--ide`, `--env`, `--jwt`,
167
+ `--org-id`, `--api-base`/`--app-base`/`--host` (advanced overrides), `--npx`/`--local`, `-y`. Run
168
+ `npx -y webcake-landing-mcp install --help` for the full list.
167
169
 
168
170
  ### Manual config
169
171
 
@@ -176,7 +178,7 @@ The MCP config is the same as the local one, but `command`/`args` point at `npx`
176
178
  "command": "npx",
177
179
  "args": ["-y", "webcake-landing-mcp"],
178
180
  "env": {
179
- "WEBCAKE_API_BASE": "http://localhost:5800",
181
+ "WEBCAKE_ENV": "prod",
180
182
  "WEBCAKE_JWT": "<your-jwt>"
181
183
  }
182
184
  }
@@ -301,7 +303,11 @@ Instead of copying a JWT by hand, run:
301
303
  # Production — zero config (defaults: connect via webcake.io, API via api.webcake.io):
302
304
  npx -y webcake-landing-mcp login
303
305
 
304
- # Local dev — point at your local SPA (5173) + API (5800):
306
+ # Local dev / staging pick a named environment (see Environments below):
307
+ node dist/index.js login --env local # SPA :5173 + API :5800
308
+ node dist/index.js login --env staging # staging.webcake.io + api.staging.webcake.io
309
+
310
+ # …or point at custom URLs explicitly (these override --env):
305
311
  node dist/index.js login \
306
312
  --connect-url http://localhost:5173/mcp-connect \
307
313
  --api-base http://localhost:5800
@@ -346,7 +352,8 @@ flow can also be done entirely in the SPA, no backend route needed.)
346
352
 
347
353
  | Variable | Required | Description |
348
354
  |----------|----------|-------------|
349
- | `WEBCAKE_API_BASE` | No* | Backend base URL, e.g. `http://localhost:5800`. Required to persist. |
355
+ | `WEBCAKE_ENV` | No | Named environment: `local` \| `staging` \| `prod`. Fills in `WEBCAKE_API_BASE` + `WEBCAKE_APP_BASE` from a preset (see table below). Also settable with the `--env <name>` flag. Explicit vars win. |
356
+ | `WEBCAKE_API_BASE` | No* | Backend base URL, e.g. `http://localhost:5800`. Required to persist (or set `WEBCAKE_ENV`). |
350
357
  | `WEBCAKE_JWT` | No* | Account JWT (dashboard auth). Required to persist — expires, refresh when needed. |
351
358
  | `WEBCAKE_ORG_ID` | No | Default organization id for `create_page` (overridden by its `organization_id` arg). Omit → personal page. |
352
359
  | `WEBCAKE_HOST` | No | Optional `Host` header (Phoenix routes by host, e.g. `builder.localhost`). |
@@ -360,6 +367,28 @@ flow can also be done entirely in the SPA, no backend route needed.)
360
367
  > Persisting writes a real page to whatever `WEBCAKE_API_BASE` points at, using the JWT as that account.
361
368
  > Start against local/staging.
362
369
 
370
+ ### Environments (`--env` / `WEBCAKE_ENV`)
371
+
372
+ Instead of setting both base URLs by hand, pick a named environment — one source of
373
+ truth for the API + app bases:
374
+
375
+ | `--env` / `WEBCAKE_ENV` | API base (`WEBCAKE_API_BASE`) | App base (`WEBCAKE_APP_BASE`) |
376
+ |-------------------------|-------------------------------|-------------------------------|
377
+ | `local` | `http://localhost:5800` | `http://localhost:5173` |
378
+ | `staging` | `https://api.staging.webcake.io` | `https://staging.webcake.io` |
379
+ | `prod` | `https://api.webcake.io` | `https://webcake.io` |
380
+
381
+ ```bash
382
+ node dist/index.js serve --env staging # remote server on the staging backend
383
+ node dist/index.js login --env local # connect against your local SPA + API
384
+ WEBCAKE_ENV=prod node dist/index.js # stdio, prod (env var form)
385
+ ```
386
+
387
+ Explicit `WEBCAKE_API_BASE` / `WEBCAKE_APP_BASE` (or `--api-base`) still override the
388
+ preset, field by field. On the remote HTTP server a client can override the server's
389
+ environment per request with the **`x-webcake-env`** header or **`?env=`** query
390
+ (e.g. `…/mcp?jwt=<token>&env=staging`) — so one server can serve multiple environments.
391
+
363
392
  ### How to get `WEBCAKE_JWT`
364
393
 
365
394
  1. Open the WebCake builder dashboard and log in
package/README.vi.md CHANGED
@@ -147,23 +147,25 @@ npx -y github:vuluu2k/webcake-landing-mcp
147
147
  Lệnh con `install` đi kèm sẽ làm hộ bạn bước đó, không cần clone:
148
148
 
149
149
  ```bash
150
- # Tương tác — hỏi env + chọn IDE từng bước
150
+ # Tương tác — chọn môi trường, đăng nhập qua trình duyệt (hoặc dán JWT), chọn IDE
151
151
  npx -y webcake-landing-mcp install
152
152
 
153
- # Không tương tác — cấu hình mọi IDE hỗ trợ cùng lúc
154
- npx -y webcake-landing-mcp install --ide all --jwt <your-jwt> --api-base http://localhost:5800
153
+ # Không tương tác — cấu hình mọi IDE hỗ trợ cùng lúc (env + token qua cờ)
154
+ npx -y webcake-landing-mcp install --ide all --env prod --jwt <your-jwt>
155
155
 
156
- # Chỉ một IDE
157
- npx -y webcake-landing-mcp install --ide cursor --jwt <your-jwt>
156
+ # Local dev — trỏ vào stack local của bạn (localhost:5800 / :5173)
157
+ npx -y webcake-landing-mcp install --ide cursor --env local --jwt <your-jwt>
158
158
 
159
159
  # Gỡ server khỏi mọi cấu hình IDE
160
160
  npx -y webcake-landing-mcp uninstall
161
161
  ```
162
162
 
163
163
  Nó ghi entry `webcake-landing` (dùng dạng khởi chạy `npx` bên dưới) vào đúng file cấu hình của từng IDE:
164
- `claude-desktop`, `claude-code`, `cursor`, `windsurf`, `augment` (VS Code), `codex`, hoặc `all`. Cờ:
165
- `--ide`, `--api-base`, `--jwt`, `--org-id`, `--host`, `--app-base`, `--npx`/`--local`, `-y`. Chạy
166
- `npx -y webcake-landing-mcp --help` để xem đầy đủ.
164
+ `claude-desktop`, `claude-code`, `cursor`, `windsurf`, `augment` (VS Code), `codex`, hoặc `all`. Khi tương
165
+ tác, hỏi **môi trường** (`local`/`staging`/`prod` — mặc định `prod`, dùng để đặt API + app URL) cho
166
+ chọn **đăng nhập qua trình duyệt hay dán JWT**. Cờ: `--ide`, `--env`, `--jwt`, `--org-id`,
167
+ `--api-base`/`--app-base`/`--host` (ghi đè nâng cao), `--npx`/`--local`, `-y`. Chạy
168
+ `npx -y webcake-landing-mcp install --help` để xem đầy đủ.
167
169
 
168
170
  ### Cấu hình thủ công
169
171
 
@@ -176,7 +178,7 @@ Cấu hình MCP giống bản local, chỉ khác `command`/`args` trỏ tới `n
176
178
  "command": "npx",
177
179
  "args": ["-y", "webcake-landing-mcp"],
178
180
  "env": {
179
- "WEBCAKE_API_BASE": "http://localhost:5800",
181
+ "WEBCAKE_ENV": "prod",
180
182
  "WEBCAKE_JWT": "<your-jwt>"
181
183
  }
182
184
  }
@@ -344,7 +346,8 @@ SPA cũng được, khỏi cần route backend.)
344
346
 
345
347
  | Biến | Bắt buộc | Mô tả |
346
348
  |----------|----------|-------------|
347
- | `WEBCAKE_API_BASE` | Không* | Base URL backend, dụ `http://localhost:5800`. Cần để lưu trang. |
349
+ | `WEBCAKE_ENV` | Không | Môi trường tên: `local` \| `staging` \| `prod`. Điền sẵn `WEBCAKE_API_BASE` + `WEBCAKE_APP_BASE` từ preset (xem bảng bên dưới). Cũng đặt được qua cờ `--env <name>`. Biến tường minh sẽ thắng. |
350
+ | `WEBCAKE_API_BASE` | Không* | Base URL backend, ví dụ `http://localhost:5800`. Cần để lưu trang (hoặc đặt `WEBCAKE_ENV`). |
348
351
  | `WEBCAKE_JWT` | Không* | JWT tài khoản (auth dashboard). Cần để lưu trang — sẽ hết hạn, làm mới khi cần. |
349
352
  | `WEBCAKE_ORG_ID` | Không | Organization mặc định cho `create_page` (bị ghi đè bởi tham số `organization_id`). Bỏ trống → trang cá nhân. |
350
353
  | `WEBCAKE_HOST` | Không | Header `Host` tuỳ chọn (Phoenix route theo host, ví dụ `builder.localhost`). |
@@ -358,6 +361,28 @@ SPA cũng được, khỏi cần route backend.)
358
361
  > Lưu trang sẽ ghi một trang thật vào nơi `WEBCAKE_API_BASE` trỏ tới, dùng JWT làm tài khoản đó.
359
362
  > Hãy bắt đầu với local/staging.
360
363
 
364
+ ### Môi trường (`--env` / `WEBCAKE_ENV`)
365
+
366
+ Thay vì đặt thủ công cả hai base URL, hãy chọn một môi trường có tên — một nguồn sự thật duy nhất
367
+ cho API + app base (mặc định là `prod`):
368
+
369
+ | `--env` / `WEBCAKE_ENV` | API base (`WEBCAKE_API_BASE`) | App base (`WEBCAKE_APP_BASE`) |
370
+ |-------------------------|-------------------------------|-------------------------------|
371
+ | `local` | `http://localhost:5800` | `http://localhost:5173` |
372
+ | `staging` | `https://api.staging.webcake.io` | `https://staging.webcake.io` |
373
+ | `prod` *(mặc định)* | `https://api.webcake.io` | `https://webcake.io` |
374
+
375
+ ```bash
376
+ node dist/index.js serve --env staging # server remote trỏ backend staging
377
+ node dist/index.js login --env local # đăng nhập vào SPA + API local
378
+ WEBCAKE_ENV=prod node dist/index.js # stdio, prod (dạng biến môi trường)
379
+ ```
380
+
381
+ `WEBCAKE_API_BASE` / `WEBCAKE_APP_BASE` (hoặc `--api-base`) tường minh vẫn ghi đè preset theo từng
382
+ trường. Trên server HTTP remote, client có thể ghi đè môi trường của server theo từng request bằng
383
+ header **`x-webcake-env`** hoặc query **`?env=`** (ví dụ `…/mcp?jwt=<token>&env=staging`) — nên một
384
+ server phục vụ được nhiều môi trường.
385
+
361
386
  ### Cách lấy `WEBCAKE_JWT`
362
387
 
363
388
  1. Mở dashboard builder WebCake và đăng nhập
@@ -19,13 +19,7 @@
19
19
  import { createServer } from "node:http";
20
20
  import { randomBytes } from "node:crypto";
21
21
  import { spawn } from "node:child_process";
22
- import { saveSavedConfig } from "../persistence/config.js";
23
- // Production defaults — the connect page lives on the SPA (webcake.io), the API
24
- // lives on api.webcake.io. For local dev override with --connect-url / --api-base
25
- // (e.g. http://localhost:5173/mcp-connect and http://localhost:5800) or the
26
- // WEBCAKE_APP_BASE / WEBCAKE_API_BASE env vars.
27
- const DEFAULT_CONNECT_URL = "https://webcake.io/mcp-connect";
28
- const DEFAULT_API_BASE = "https://api.webcake.io";
22
+ import { saveSavedConfig, resolveEnv, ENVIRONMENTS } from "../persistence/config.js";
29
23
  function parseArgs(argv) {
30
24
  const get = (name) => {
31
25
  const i = argv.indexOf(name);
@@ -53,21 +47,22 @@ function openBrowser(url) {
53
47
  const SUCCESS_HTML = `<!doctype html><meta charset="utf-8"><title>Connected</title>
54
48
  <body style="font-family:system-ui;text-align:center;padding:48px">
55
49
  <h2>✓ Connected to Webcake</h2><p>You can close this tab and return to your terminal.</p></body>`;
56
- function resolveConnectUrl(opts) {
50
+ function resolveConnectUrl(opts, appBase) {
57
51
  if (opts.connectUrl)
58
52
  return opts.connectUrl;
59
53
  if (process.env.WEBCAKE_CONNECT_URL)
60
54
  return process.env.WEBCAKE_CONNECT_URL;
61
- // The connect page is on the SPA (WEBCAKE_APP_BASE), NOT the API base.
62
- const appBase = process.env.WEBCAKE_APP_BASE;
63
- if (appBase)
64
- return `${appBase.replace(/\/+$/, "")}/mcp-connect`;
65
- return DEFAULT_CONNECT_URL;
55
+ // The connect page is on the SPA (appBase, from the env preset), NOT the API base.
56
+ return `${appBase.replace(/\/+$/, "")}/mcp-connect`;
66
57
  }
67
58
  export async function runLogin(argv) {
68
59
  const opts = parseArgs(argv);
69
- const connectUrl = resolveConnectUrl(opts);
70
- const apiBase = opts.apiBase || process.env.WEBCAKE_API_BASE || DEFAULT_API_BASE;
60
+ // Named environment (set by the global --env flag / WEBCAKE_ENV); prod is the
61
+ // zero-config default. Explicit --api-base / WEBCAKE_APP_BASE still win per field.
62
+ const preset = resolveEnv(process.env.WEBCAKE_ENV) ?? ENVIRONMENTS.prod;
63
+ const apiBase = opts.apiBase || process.env.WEBCAKE_API_BASE || preset.apiBase;
64
+ const appBase = process.env.WEBCAKE_APP_BASE || preset.appBase;
65
+ const connectUrl = resolveConnectUrl(opts, appBase);
71
66
  const state = randomBytes(16).toString("hex");
72
67
  await new Promise((resolve, reject) => {
73
68
  const server = createServer((req, res) => {
@@ -83,15 +78,13 @@ export async function runLogin(argv) {
83
78
  }
84
79
  const path = saveSavedConfig({
85
80
  jwt: token,
86
- ...(apiBase ? { base: apiBase.replace(/\/+$/, "") } : {}),
81
+ base: apiBase.replace(/\/+$/, ""),
82
+ appBase: appBase.replace(/\/+$/, ""),
87
83
  ...(opts.orgId ? { orgId: opts.orgId } : {}),
88
84
  savedAt: new Date().toISOString(),
89
85
  });
90
86
  res.writeHead(200, { "content-type": "text/html" }).end(SUCCESS_HTML);
91
- console.error(`\n✓ Connected. Token saved to ${path}`);
92
- if (!apiBase) {
93
- console.error(" tip: also set WEBCAKE_API_BASE (or pass --api-base) so the server knows the backend URL.");
94
- }
87
+ console.error(`\n✓ Connected. Token saved to ${path} (api ${apiBase}).`);
95
88
  server.close();
96
89
  resolve();
97
90
  });
package/dist/http.js CHANGED
@@ -39,6 +39,7 @@ async function readBody(req) {
39
39
  // require HTTPS, and disable query-string logging on your reverse proxy.
40
40
  const QUERY_AUTH = {
41
41
  jwt: "x-webcake-jwt",
42
+ env: "x-webcake-env",
42
43
  api_base: "x-webcake-api-base",
43
44
  org_id: "x-webcake-org-id",
44
45
  host: "x-webcake-host",
package/dist/index.js CHANGED
@@ -17,7 +17,40 @@
17
17
  */
18
18
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
19
19
  import { createServer } from "./server.js";
20
+ import { ENVIRONMENTS, ENV_NAMES, isEnvName } from "./persistence/config.js";
21
+ /**
22
+ * Global `--env <local|staging|prod>` flag (or `--env=<name>`): selects the API +
23
+ * app base URLs from a named preset by setting WEBCAKE_ENV, which readConfig + login
24
+ * then pick up. Explicit WEBCAKE_API_BASE / WEBCAKE_APP_BASE still win. An unknown
25
+ * value from the flag fails fast; an unknown WEBCAKE_ENV is dropped so explicit
26
+ * bases (or per-request headers) still resolve. stderr only — stdout is the MCP channel.
27
+ */
28
+ function applyEnvFlag(argv) {
29
+ let fromFlag;
30
+ for (let i = 2; i < argv.length; i++) {
31
+ const a = argv[i];
32
+ if (a === "--env")
33
+ fromFlag = argv[i + 1];
34
+ else if (a.startsWith("--env="))
35
+ fromFlag = a.slice("--env=".length);
36
+ }
37
+ const name = fromFlag ?? process.env.WEBCAKE_ENV;
38
+ if (!name)
39
+ return;
40
+ if (!isEnvName(name)) {
41
+ console.error(`[webcake] unknown environment "${name}". Valid: ${ENV_NAMES.join(", ")}.`);
42
+ if (fromFlag)
43
+ process.exit(1); // explicit flag typo → fail fast
44
+ delete process.env.WEBCAKE_ENV; // bad WEBCAKE_ENV → ignore, fall through to explicit bases
45
+ return;
46
+ }
47
+ process.env.WEBCAKE_ENV = name;
48
+ const p = ENVIRONMENTS[name];
49
+ console.error(`[webcake] environment "${name}" — api ${p.apiBase}, app ${p.appBase}`);
50
+ }
20
51
  async function main() {
52
+ // Resolve the named environment (--env / WEBCAKE_ENV) before any config is read.
53
+ applyEnvFlag(process.argv);
21
54
  // Subcommand dispatch: `webcake-landing-mcp install|uninstall` runs the
22
55
  // bundled IDE installer instead of starting the MCP server. Default (no
23
56
  // subcommand) starts the stdio server as usual.
package/dist/install.js CHANGED
@@ -22,6 +22,8 @@ import { dirname, join } from "node:path";
22
22
  import { fileURLToPath } from "node:url";
23
23
  import { spawnSync } from "node:child_process";
24
24
  import { createInterface } from "node:readline";
25
+ import { ENVIRONMENTS, isEnvName } from "./persistence/config.js";
26
+ import { runLogin } from "./auth/login.js";
25
27
  const NAME = "webcake-landing";
26
28
  const PKG = "webcake-landing-mcp";
27
29
  const HOME = homedir();
@@ -290,18 +292,19 @@ function printHelp() {
290
292
  ${c.bold}webcake-landing-mcp install${c.reset} — configure the MCP server in your IDE(s)
291
293
 
292
294
  ${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
295
+ npx -y ${PKG} install # interactive: pick environment, log in (or paste a JWT), pick IDEs
296
+ npx -y ${PKG} install --ide all # non-interactive, all IDEs (defaults to --env prod)
297
+ npx -y ${PKG} install --ide claude-code --env local --jwt <JWT>
296
298
  npx -y ${PKG} uninstall # remove from every IDE config
297
299
 
298
300
  ${c.bold}Flags${c.reset}
299
301
  --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)
302
+ --env <name> WEBCAKE_ENV: local | staging | prod (default prod) — sets the API + app base URLs
303
+ --jwt <token> WEBCAKE_JWT (account token; optional or log in via the browser interactively)
304
+ --org-id <id> WEBCAKE_ORG_ID (optional default organization)
305
+ --api-base <url> override the --env API base (advanced)
306
+ --app-base <url> override the --env app base (advanced)
307
+ --host <host> WEBCAKE_HOST (advanced; Phoenix host-routing header)
305
308
  --npx | --local force the launch command form (default: auto-detect)
306
309
  -y, --yes accept defaults, skip confirmations
307
310
  --uninstall remove the server from all IDE configs
@@ -317,36 +320,72 @@ export async function runInstaller(argv) {
317
320
  log(`\n${c.cyan}${c.bold}Webcake Landing MCP — installer${c.reset}`);
318
321
  log(`${c.gray}Build & edit Webcake landing pages from a prompt. 12 tools.${c.reset}`);
319
322
  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;
323
+ // 1) environment — one choice sets the API + app base URLs (replaces the old
324
+ // separate WEBCAKE_API_BASE / WEBCAKE_APP_BASE prompts). The global --env flag
325
+ // / WEBCAKE_ENV is already validated in index.ts (applyEnvFlag).
326
+ let envName = isEnvName(process.env.WEBCAKE_ENV) ? process.env.WEBCAKE_ENV : "prod";
327
+ if (interactive && !isEnvName(process.env.WEBCAKE_ENV)) {
328
+ log(`\n${c.bold}1) Environment${c.reset} ${c.gray}(sets the Webcake API + app URLs)${c.reset}`);
329
+ log(` 1) prod ${c.gray}${ENVIRONMENTS.prod.apiBase}${c.reset} ${c.gray}(default)${c.reset}`);
330
+ log(` 2) staging ${c.gray}${ENVIRONMENTS.staging.apiBase}${c.reset}`);
331
+ log(` 3) local ${c.gray}${ENVIRONMENTS.local.apiBase}${c.reset}`);
332
+ const pick = (await ask(" Select [1=prod, Enter to accept]: ")).trim();
333
+ envName = { "1": "prod", "2": "staging", "3": "local" }[pick] ?? "prod";
334
+ }
335
+ process.env.WEBCAKE_ENV = envName; // so `login` below connects to the same environment
336
+ const preset = ENVIRONMENTS[envName];
337
+ // 2) authentication interactively ASK how to authenticate: browser login (token
338
+ // saved to ~/.webcake-landing-mcp/auth.json) or a pasted JWT. An explicit --jwt
339
+ // skips the prompt; a non-TTY falls back to the WEBCAKE_JWT env var (for scripted
340
+ // installs). Reference/validation tools work with no auth at all.
341
+ let jwt = o.jwt ?? "";
342
+ let authNote = jwt ? "JWT (from --jwt)" : "";
343
+ if (interactive && !jwt) {
344
+ log(`\n${c.bold}2) Authentication${c.reset} ${c.gray}(only needed to save pages to your account)${c.reset}`);
345
+ log(` 1) Log in via browser ${c.gray}(recommended — opens Webcake, saves a token)${c.reset}`);
346
+ log(` 2) Paste a JWT token`);
347
+ log(` 3) Skip for now ${c.gray}(reference/validation tools still work)${c.reset}`);
348
+ const pick = (await ask(" Select [1]: ")).trim() || "1";
349
+ if (pick === "1") {
350
+ info(`Connecting to ${preset.appBase} …`);
351
+ try {
352
+ await runLogin([]); // reads WEBCAKE_ENV for the connect URL + API base; saves auth.json
353
+ authNote = "browser login (saved to auth.json)";
354
+ }
355
+ catch (e) {
356
+ warn(`Login didn't complete (${e?.message ?? e}). Paste a JWT now, or run \`${PKG} login\` later.`);
357
+ jwt = (await ask(" WEBCAKE_JWT (or Enter to skip): ")).trim();
358
+ }
359
+ }
360
+ else if (pick === "2") {
361
+ jwt = (await ask(" WEBCAKE_JWT: ")).trim();
362
+ }
335
363
  }
336
- else if (!apiBase) {
337
- apiBase = "http://localhost:5800";
364
+ else if (!interactive) {
365
+ jwt = jwt || process.env.WEBCAKE_JWT || ""; // scripted / CI: fall back to ambient env
366
+ }
367
+ if (!authNote)
368
+ authNote = jwt ? "JWT" : "none — reference tools only";
369
+ // 3) optional default organization for create_page
370
+ let orgId = o.orgId ?? process.env.WEBCAKE_ORG_ID ?? "";
371
+ if (interactive && !orgId) {
372
+ orgId = (await ask(`\n${c.bold}3) WEBCAKE_ORG_ID${c.reset} ${c.gray}(optional, Enter to skip): ${c.reset}`)).trim();
338
373
  }
339
- if (apiBase)
340
- env.WEBCAKE_API_BASE = apiBase;
374
+ // env block written into IDE configs: WEBCAKE_ENV drives the URLs. A pasted JWT is
375
+ // written too; a browser login lives in auth.json instead. --api-base/--app-base/
376
+ // --host stay as advanced overrides for non-standard setups.
377
+ const env = { WEBCAKE_ENV: envName };
341
378
  if (jwt)
342
379
  env.WEBCAKE_JWT = jwt;
343
380
  if (orgId)
344
381
  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
382
+ if (o.apiBase)
383
+ env.WEBCAKE_API_BASE = o.apiBase;
384
+ if (o.appBase)
385
+ env.WEBCAKE_APP_BASE = o.appBase;
386
+ if (o.host)
387
+ env.WEBCAKE_HOST = o.host;
388
+ // 4) which IDEs
350
389
  let ides = [];
351
390
  if (o.ide) {
352
391
  ides = o.ide
@@ -355,7 +394,7 @@ export async function runInstaller(argv) {
355
394
  .filter(Boolean);
356
395
  }
357
396
  else if (interactive) {
358
- log(`\n${c.bold}2) Which IDE(s) to configure?${c.reset}`);
397
+ log(`\n${c.bold}4) Which IDE(s) to configure?${c.reset}`);
359
398
  log(" 1) Claude Desktop 2) Claude Code (CLI) 3) Cursor");
360
399
  log(" 4) Windsurf 5) Augment (VS Code) 6) Codex");
361
400
  log(" 7) All 0) Skip");
@@ -383,13 +422,13 @@ export async function runInstaller(argv) {
383
422
  warn("No IDE selected — skipping configuration.");
384
423
  return;
385
424
  }
386
- // 3) write
425
+ // 5) write
387
426
  const launch = resolveLaunch(o);
388
- log(`\n${c.bold}3) Writing config${c.reset} ${c.gray}(launch: ${launch.command} ${launch.args.join(" ")})${c.reset}`);
427
+ log(`\n${c.bold}5) Writing config${c.reset} ${c.gray}(launch: ${launch.command} ${launch.args.join(" ")})${c.reset}`);
389
428
  runConfigure(ides, launch, env);
390
- // 4) summary
429
+ // 6) summary
391
430
  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}`);
431
+ log(` ${c.gray}Environment : ${envName} (api ${o.apiBase || preset.apiBase})${c.reset}`);
432
+ log(` ${c.gray}Auth : ${authNote}${c.reset}`);
394
433
  log(` Restart your IDE, then ask the AI: “Build a Webcake landing page”.\n`);
395
434
  }
@@ -12,6 +12,9 @@
12
12
  * { config: null, missing } when required values are absent so the persistence
13
13
  * tools can report exactly what to provide.
14
14
  *
15
+ * WEBCAKE_ENV optional named environment (local|staging|prod) — fills in the
16
+ * API + app base URLs from a preset (see ENVIRONMENTS below). An
17
+ * explicit WEBCAKE_API_BASE / WEBCAKE_APP_BASE still wins over it.
15
18
  * WEBCAKE_API_BASE e.g. http://localhost:5800 (required to call the backend)
16
19
  * WEBCAKE_JWT the account JWT (required to call the backend)
17
20
  * WEBCAKE_ORG_ID optional default organization id for create_page
@@ -22,9 +25,32 @@
22
25
  import { homedir } from "node:os";
23
26
  import { join } from "node:path";
24
27
  import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
28
+ /**
29
+ * Named deployment environments — the single source of truth for the API + app
30
+ * base URLs. Selecting one (via the `--env` flag, WEBCAKE_ENV, the `x-webcake-env`
31
+ * header, or `?env=` in the URL) fills in both bases so callers don't repeat them.
32
+ * Explicit WEBCAKE_API_BASE / WEBCAKE_APP_BASE (or per-request overrides) win over
33
+ * the preset. `apiBase` is the backend; `appBase` is the SPA (editor/preview/connect).
34
+ */
35
+ export const ENVIRONMENTS = {
36
+ local: { apiBase: "http://localhost:5800", appBase: "http://localhost:5173" },
37
+ staging: { apiBase: "https://api.staging.webcake.io", appBase: "https://staging.webcake.io" },
38
+ prod: { apiBase: "https://api.webcake.io", appBase: "https://webcake.io" },
39
+ };
40
+ export const ENV_NAMES = Object.keys(ENVIRONMENTS);
41
+ /** True when `v` names a known environment (local|staging|prod). */
42
+ export function isEnvName(v) {
43
+ return typeof v === "string" && Object.prototype.hasOwnProperty.call(ENVIRONMENTS, v);
44
+ }
45
+ /** The base URLs for a named environment, or undefined when the name is absent/unknown. */
46
+ export function resolveEnv(name) {
47
+ return isEnvName(name) ? ENVIRONMENTS[name] : undefined;
48
+ }
25
49
  export function readConfig(overrides = {}) {
26
50
  const saved = readSavedConfig();
27
- const base = overrides.base ?? process.env.WEBCAKE_API_BASE ?? saved.base;
51
+ // A named environment supplies default base URLs; explicit values still win.
52
+ const preset = resolveEnv(overrides.env ?? process.env.WEBCAKE_ENV);
53
+ const base = overrides.base ?? process.env.WEBCAKE_API_BASE ?? preset?.apiBase ?? saved.base;
28
54
  const jwt = overrides.jwt ?? process.env.WEBCAKE_JWT ?? saved.jwt;
29
55
  const missing = [];
30
56
  if (!base)
@@ -39,7 +65,7 @@ export function readConfig(overrides = {}) {
39
65
  jwt: jwt,
40
66
  orgId: overrides.orgId ?? process.env.WEBCAKE_ORG_ID ?? saved.orgId,
41
67
  host: overrides.host ?? process.env.WEBCAKE_HOST ?? saved.host,
42
- appBase: (overrides.appBase ?? process.env.WEBCAKE_APP_BASE ?? saved.appBase)?.replace(/\/+$/, ""),
68
+ appBase: (overrides.appBase ?? process.env.WEBCAKE_APP_BASE ?? preset?.appBase ?? saved.appBase)?.replace(/\/+$/, ""),
43
69
  },
44
70
  missing: [],
45
71
  };
@@ -53,9 +79,10 @@ function header(headers, name) {
53
79
  * send its own credentials per request instead of a server-wide env token:
54
80
  * x-webcake-jwt the account JWT (or `Authorization: Bearer <jwt>`)
55
81
  * x-webcake-org-id organization id
56
- * x-webcake-api-base backend base URL (usually set once via env instead)
82
+ * x-webcake-env named environment (local|staging|prod) for the base URLs
83
+ * x-webcake-api-base backend base URL (overrides the env preset)
57
84
  * x-webcake-host Host header override
58
- * x-webcake-app-base editor/preview URL base
85
+ * x-webcake-app-base editor/preview URL base (overrides the env preset)
59
86
  * Any header that is absent falls back to the corresponding env var in readConfig.
60
87
  */
61
88
  export function configFromHeaders(headers) {
@@ -67,6 +94,7 @@ export function configFromHeaders(headers) {
67
94
  orgId: header(headers, "x-webcake-org-id"),
68
95
  host: header(headers, "x-webcake-host"),
69
96
  appBase: header(headers, "x-webcake-app-base"),
97
+ env: header(headers, "x-webcake-env"),
70
98
  };
71
99
  }
72
100
  /** Directory for the saved auth file (override with WEBCAKE_CONFIG_DIR). */
package/dist/smoke.js CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { createElement, CONTAINER_TYPES, FIELD_TYPES, LIBRARY, ELEMENT_TYPES, ELEMENTS, } from "./domains/landing/elements/index.js";
6
6
  import { validatePage, pageSchema } from "./domains/landing/validate.js";
7
+ import { readConfig, resolveEnv, ENV_NAMES } from "./persistence/config.js";
7
8
  let failures = 0;
8
9
  const check = (name, cond, extra) => {
9
10
  if (cond) {
@@ -208,5 +209,21 @@ const bindingsGood = {
208
209
  };
209
210
  const rbg = validatePage(bindingsGood);
210
211
  check("clean form has no binding warnings", rbg.warnings.length === 0, rbg.warnings);
212
+ console.log("== config: named environment presets (local/staging/prod) ==");
213
+ {
214
+ // Deterministic: isolate from any ambient WEBCAKE_* and the saved auth.json on the dev box.
215
+ for (const k of ["WEBCAKE_API_BASE", "WEBCAKE_APP_BASE", "WEBCAKE_ENV", "WEBCAKE_JWT", "WEBCAKE_ORG_ID", "WEBCAKE_HOST"])
216
+ delete process.env[k];
217
+ process.env.WEBCAKE_CONFIG_DIR = "/nonexistent/webcake-smoke";
218
+ check("env names are local/staging/prod", setEq(new Set(ENV_NAMES), ["local", "staging", "prod"]), ENV_NAMES);
219
+ check("staging preset resolves to api+app bases", resolveEnv("staging")?.apiBase === "https://api.staging.webcake.io" && resolveEnv("staging")?.appBase === "https://staging.webcake.io");
220
+ check("unknown env name → undefined", resolveEnv("bogus") === undefined);
221
+ const prod = readConfig({ env: "prod", jwt: "t" }).config;
222
+ check("readConfig(env=prod) fills api+app base", prod?.base === "https://api.webcake.io" && prod?.appBase === "https://webcake.io", prod);
223
+ const local = readConfig({ env: "local", jwt: "t" }).config;
224
+ check("readConfig(env=local) fills api+app base", local?.base === "http://localhost:5800" && local?.appBase === "http://localhost:5173", local);
225
+ check("explicit base overrides the preset", readConfig({ env: "prod", base: "http://x:1", jwt: "t" }).config?.base === "http://x:1");
226
+ check("unknown env leaves base missing", readConfig({ env: "bogus", jwt: "t" }).missing.includes("WEBCAKE_API_BASE"));
227
+ }
211
228
  console.log(`\n${failures === 0 ? "ALL GOOD" : failures + " FAILURE(S)"}`);
212
229
  process.exit(failures === 0 ? 0 : 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webcake-landing-mcp",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "MCP server exposing Webcake landing-page element schemas + AI usage hints, and persisting LLM-generated page sources to a Webcake backend.",
5
5
  "type": "module",
6
6
  "bin": {