@iterable/mcp 1.2.2 → 1.3.0

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/install.js CHANGED
@@ -7,12 +7,11 @@ import path from "path";
7
7
  import { fileURLToPath } from "url";
8
8
  import { promisify } from "util";
9
9
  import { getKeyManager } from "./key-manager.js";
10
+ import { displayKeyDetails, saveKeyInteractive } from "./keys-cli.js";
11
+ import { COMMAND_NAME, KEYS_COMMAND_TABLE, NPX_PACKAGE_NAME, } from "./utils/command-info.js";
10
12
  import { getKeyStorageMessage } from "./utils/formatting.js";
11
13
  const { dirname, join } = path;
12
14
  // IMPORTANT: UI imports are loaded lazily inside functions to avoid ESM issues in Jest/CommonJS.
13
- // Executable/package names
14
- const LOCAL_BINARY_NAME = "iterable-mcp";
15
- const NPX_PACKAGE_NAME = "@iterable/mcp";
16
15
  // Get package version
17
16
  const packageJson = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf-8"));
18
17
  const TOOL_NAMES = {
@@ -20,6 +19,7 @@ const TOOL_NAMES = {
20
19
  "claude-desktop": "Claude Desktop",
21
20
  "claude-code": "Claude Code",
22
21
  "gemini-cli": "Gemini CLI",
22
+ windsurf: "Windsurf",
23
23
  manual: "Manual Setup",
24
24
  };
25
25
  const TOOL_CONFIGS = {
@@ -34,6 +34,7 @@ const TOOL_CONFIGS = {
34
34
  }
35
35
  })(),
36
36
  cursor: path.join(os.homedir(), ".cursor", "mcp.json"),
37
+ windsurf: path.join(os.homedir(), ".codeium", "windsurf", "mcp_config.json"),
37
38
  "gemini-cli": path.join(os.homedir(), ".gemini", "settings.json"),
38
39
  };
39
40
  const execFileAsync = promisify(execFile);
@@ -128,16 +129,13 @@ export const setupMcpServer = async () => {
128
129
  const advanced = args.includes("--advanced");
129
130
  const autoUpdate = args.includes("--auto-update");
130
131
  let tools = [
131
- ...(args.includes("--claude-desktop") ? ["claude-desktop"] : []),
132
132
  ...(args.includes("--cursor") ? ["cursor"] : []),
133
+ ...(args.includes("--claude-desktop") ? ["claude-desktop"] : []),
133
134
  ...(args.includes("--claude-code") ? ["claude-code"] : []),
134
135
  ...(args.includes("--gemini-cli") ? ["gemini-cli"] : []),
136
+ ...(args.includes("--windsurf") ? ["windsurf"] : []),
135
137
  ...(args.includes("--manual") ? ["manual"] : []),
136
138
  ];
137
- // Detect how the command was invoked
138
- const isNpx = process.argv[1]?.includes("npx") ||
139
- process.env.npm_execpath?.includes("npx");
140
- const commandName = isNpx ? `npx ${NPX_PACKAGE_NAME}` : LOCAL_BINARY_NAME;
141
139
  if (showHelp) {
142
140
  const { createTable, icons, showBox, showIterableLogo, showSection } = await import("./utils/ui.js");
143
141
  const chalk = (await import("chalk")).default;
@@ -150,11 +148,14 @@ export const setupMcpServer = async () => {
150
148
  colWidths: [45, 40],
151
149
  style: "normal",
152
150
  });
153
- setupTable.push([`${commandName} setup --claude-desktop`, "Configure for Claude Desktop"], [`${commandName} setup --cursor`, "Configure for Cursor"], [`${commandName} setup --claude-code`, "Configure for Claude Code"], [`${commandName} setup --gemini-cli`, "Configure for Gemini CLI"], [`${commandName} setup --manual`, "Show manual config instructions"], [
154
- `${commandName} setup --cursor --claude-desktop`,
151
+ setupTable.push([`${COMMAND_NAME} setup --cursor`, "Configure for Cursor"], [
152
+ `${COMMAND_NAME} setup --claude-desktop`,
153
+ "Configure for Claude Desktop",
154
+ ], [`${COMMAND_NAME} setup --claude-code`, "Configure for Claude Code"], [`${COMMAND_NAME} setup --gemini-cli`, "Configure for Gemini CLI"], [`${COMMAND_NAME} setup --windsurf`, "Configure for Windsurf"], [`${COMMAND_NAME} setup --manual`, "Show manual config instructions"], [
155
+ `${COMMAND_NAME} setup --cursor --claude-desktop`,
155
156
  "Configure multiple tools",
156
- ], [`${commandName} setup --cursor --debug`, "Enable debug logging"], [
157
- `${commandName} setup --cursor --auto-update`,
157
+ ], [`${COMMAND_NAME} setup --cursor --debug`, "Enable debug logging"], [
158
+ `${COMMAND_NAME} setup --cursor --auto-update`,
158
159
  "Always use latest version",
159
160
  ]);
160
161
  console.log(setupTable.toString());
@@ -167,7 +168,7 @@ export const setupMcpServer = async () => {
167
168
  colWidths: [45, 40],
168
169
  style: "normal",
169
170
  });
170
- keysTable.push([`${commandName} keys list`, "View all stored API keys"], [`${commandName} keys add`, "Add a new API key"], [`${commandName} keys activate <name>`, "Switch to a different key"], [`${commandName} keys delete <id>`, "Remove a key by ID"]);
171
+ keysTable.push(...KEYS_COMMAND_TABLE);
171
172
  console.log(keysTable.toString());
172
173
  console.log();
173
174
  console.log();
@@ -195,7 +196,7 @@ export const setupMcpServer = async () => {
195
196
  console.log();
196
197
  showBox("Quick Start", [
197
198
  chalk.white.bold("1. Run setup for your AI tool:"),
198
- chalk.cyan(` ${commandName} setup --cursor`),
199
+ chalk.cyan(` ${COMMAND_NAME} setup --cursor`),
199
200
  "",
200
201
  chalk.white.bold("2. Restart your AI tool"),
201
202
  "",
@@ -209,38 +210,11 @@ export const setupMcpServer = async () => {
209
210
  ], { icon: icons.lock, theme: "info", padding: 1 });
210
211
  return;
211
212
  }
212
- // New behavior: if no tools were provided as flags, prompt user to select one or more
213
- if (tools.length === 0) {
214
- // Show logo first when no flags provided
215
- const { showIterableLogo } = await import("./utils/ui.js");
216
- console.clear();
217
- showIterableLogo(packageJson.version);
218
- const { selectedTools } = await inquirer.prompt([
219
- {
220
- type: "checkbox",
221
- name: "selectedTools",
222
- message: "Select your AI tools to configure:",
223
- choices: [
224
- { name: "Cursor", value: "cursor" },
225
- { name: "Claude Desktop", value: "claude-desktop" },
226
- { name: "Claude Code (CLI)", value: "claude-code" },
227
- { name: "Gemini CLI", value: "gemini-cli" },
228
- { name: "Other / Manual Setup", value: "manual" },
229
- ],
230
- validate: (arr) => Array.isArray(arr) && arr.length > 0
231
- ? true
232
- : "Please select at least one tool",
233
- },
234
- ]);
235
- tools = selectedTools;
236
- }
237
- // Start the setup flow
213
+ // Start the setup flow - show logo and load UI
238
214
  console.clear();
239
- const { formatKeyValue, icons, showCompletion, showError, showInfo, showIterableLogo, showSection, showSuccess, showWarning, linkColor, valueColor, } = await import("./utils/ui.js");
215
+ const { formatKeyValue, icons, showCompletion, showError, showInfo, showIterableLogo, showSection, showSuccess, showWarning, linkColor, } = await import("./utils/ui.js");
240
216
  const chalk = (await import("chalk")).default;
241
217
  showIterableLogo(packageJson.version);
242
- // Succinct overview of what will be configured
243
- console.log(chalk.gray(`Running setup to configure: ${tools.map((t) => TOOL_NAMES[t]).join(", ")}`));
244
218
  console.log();
245
219
  const ora = (await import("ora")).default;
246
220
  const spinner = ora();
@@ -250,9 +224,6 @@ export const setupMcpServer = async () => {
250
224
  showSection("API Key Configuration", icons.key);
251
225
  console.log();
252
226
  let apiKey;
253
- let keyName;
254
- let baseUrl;
255
- let usedKeyName;
256
227
  let usedExistingKey = false;
257
228
  let selectedExistingMeta;
258
229
  // 1) Offer using API key from environment
@@ -285,6 +256,8 @@ export const setupMcpServer = async () => {
285
256
  ]);
286
257
  if (useExisting) {
287
258
  const { formatKeychainChoiceLabel } = await import("./utils/ui.js");
259
+ // Find the active key to use as default
260
+ const activeKey = keys.find((k) => k.isActive);
288
261
  const { chosenId } = await inquirer.prompt([
289
262
  {
290
263
  type: "list",
@@ -295,6 +268,7 @@ export const setupMcpServer = async () => {
295
268
  value: k.id,
296
269
  short: k.name,
297
270
  })),
271
+ default: activeKey?.id,
298
272
  pageSize: Math.min(10, keys.length),
299
273
  },
300
274
  ]);
@@ -302,11 +276,8 @@ export const setupMcpServer = async () => {
302
276
  const meta = keys.find((k) => k.id === chosenId);
303
277
  if (!meta)
304
278
  throw new Error("Selected key metadata not found");
305
- baseUrl = meta.baseUrl;
306
- usedKeyName = meta.name;
307
279
  usedExistingKey = true;
308
280
  selectedExistingMeta = meta;
309
- showSuccess(`Using existing key "${meta.name}"`);
310
281
  }
311
282
  catch (e) {
312
283
  showError(e instanceof Error
@@ -315,265 +286,128 @@ export const setupMcpServer = async () => {
315
286
  process.exit(1);
316
287
  }
317
288
  }
289
+ else {
290
+ console.log();
291
+ }
318
292
  }
319
293
  }
320
- // 3) If neither env nor existing key chosen, prompt for a new key
321
294
  if (!apiKey && !usedExistingKey) {
322
- const { newApiKey } = await inquirer.prompt([
323
- {
324
- type: "password",
325
- name: "newApiKey",
326
- message: "Enter your Iterable API key:",
327
- validate: (input) => {
328
- if (!input)
329
- return "API key is required";
330
- if (!/^[a-f0-9]{32}$/.test(input))
331
- return "API key must be a 32-character lowercase hexadecimal string";
332
- return true;
333
- },
334
- mask: "*",
335
- },
336
- ]);
337
- apiKey = newApiKey;
338
- }
339
- // Step 2: API Endpoint Selection
340
- console.log();
341
- showSection("API Endpoint Configuration", icons.globe);
342
- console.log();
343
- if (!baseUrl) {
344
- if (process.env.ITERABLE_BASE_URL) {
345
- baseUrl = process.env.ITERABLE_BASE_URL;
346
- showSuccess(`Using API endpoint from environment: ${baseUrl}`);
295
+ const keyManager = getKeyManager();
296
+ await keyManager.initialize();
297
+ const { getSpinner } = await import("./utils/cli-env.js");
298
+ const keySpinner = await getSpinner();
299
+ try {
300
+ const newKeyId = await saveKeyInteractive(keyManager, null, {
301
+ chalk,
302
+ icons,
303
+ showError,
304
+ showSuccess,
305
+ showInfo,
306
+ formatKeyValue,
307
+ linkColor,
308
+ showBox: (await import("./utils/ui.js")).showBox,
309
+ }, keySpinner, {
310
+ skipRestartNotice: true,
311
+ advanced,
312
+ autoActivate: true,
313
+ });
314
+ selectedExistingMeta =
315
+ (await keyManager.getKeyMetadata(newKeyId)) ?? undefined;
347
316
  }
348
- else {
349
- const { promptForIterableBaseUrl } = await import("./utils/endpoint-prompt.js");
350
- try {
351
- baseUrl = await promptForIterableBaseUrl({
352
- inquirer,
353
- icons,
354
- chalk,
355
- showError,
356
- });
357
- }
358
- catch {
359
- process.exit(1);
360
- }
317
+ catch (error) {
318
+ showError(error instanceof Error ? error.message : "Failed to add key");
319
+ process.exit(1);
361
320
  }
362
321
  }
363
- else {
364
- showSuccess(`Using API endpoint from selected key: ${baseUrl}`);
365
- }
366
- console.log();
367
- console.log(formatKeyValue("Selected endpoint", baseUrl, linkColor()));
368
- console.log();
369
- // Step 3: Store the key securely
370
- let existingKeyWithValue = null;
371
- const keyManager = getKeyManager();
372
- spinner.start("Initializing secure key storage...");
373
- await keyManager.initialize();
374
- spinner.succeed("Key storage ready");
375
- // Check if key already exists
376
- if (!usedExistingKey && apiKey) {
377
- spinner.start("Checking for existing keys...");
378
- existingKeyWithValue = await keyManager.findKeyByValue(apiKey);
379
- spinner.stop();
380
- }
381
- if (usedExistingKey) {
382
- // Offer to activate the selected existing key if not already active
322
+ else if (usedExistingKey) {
323
+ const keyManager = getKeyManager();
324
+ await keyManager.initialize();
325
+ console.log();
383
326
  if (!selectedExistingMeta?.isActive) {
384
- const { activateExisting } = await inquirer.prompt([
385
- {
386
- type: "confirm",
387
- name: "activateExisting",
388
- message: `Set "${selectedExistingMeta?.name}" as your active API key?`,
389
- default: true,
390
- },
391
- ]);
392
- if (activateExisting) {
393
- await keyManager.setActiveKey(selectedExistingMeta.id);
394
- showSuccess(`"${selectedExistingMeta?.name}" is now your active key`);
395
- }
396
- else {
397
- showInfo("Keeping your current active key");
398
- }
327
+ const { getSpinner } = await import("./utils/cli-env.js");
328
+ const activateSpinner = await getSpinner();
329
+ activateSpinner.start("Activating key...");
330
+ await keyManager.setActiveKey(selectedExistingMeta.id);
331
+ activateSpinner.succeed("Key activated successfully");
332
+ showSuccess(`"${selectedExistingMeta?.name}" is now your active API key`);
399
333
  }
400
334
  else {
401
335
  showSuccess(`"${selectedExistingMeta?.name}" is already your active key`);
402
336
  }
403
- }
404
- else if (existingKeyWithValue) {
405
- showInfo(`This API key is already stored as "${existingKeyWithValue.name}"`);
406
- usedKeyName = existingKeyWithValue.name;
407
- if (!existingKeyWithValue.isActive) {
408
- const { activateExisting } = await inquirer.prompt([
409
- {
410
- type: "confirm",
411
- name: "activateExisting",
412
- message: `Set "${existingKeyWithValue.name}" as your active API key?`,
413
- default: true,
414
- },
415
- ]);
416
- if (activateExisting) {
417
- await keyManager.setActiveKey(existingKeyWithValue.id);
418
- showSuccess(`"${existingKeyWithValue.name}" is now your active key`);
419
- }
420
- else {
421
- showInfo("Keeping your current active key");
422
- }
423
- }
424
- else {
425
- showSuccess(`"${existingKeyWithValue.name}" is already your active key`);
426
- }
337
+ console.log();
338
+ displayKeyDetails(selectedExistingMeta, {
339
+ chalk,
340
+ formatKeyValue,
341
+ linkColor,
342
+ });
343
+ console.log();
344
+ showInfo(`To modify permissions, run: ${chalk.cyan(`${COMMAND_NAME} keys update "${selectedExistingMeta?.name}"`)}`);
427
345
  }
428
346
  else {
429
- // New key - prompt for name
430
- const { newKeyName } = await inquirer.prompt([
431
- {
432
- type: "input",
433
- name: "newKeyName",
434
- message: "Enter a name for this API key:",
435
- default: "default",
436
- validate: (input) => {
437
- if (input && input.length > 50)
438
- return "Key name must be 50 characters or less";
439
- return true;
440
- },
441
- },
442
- ]);
443
- keyName = newKeyName || "default";
444
- spinner.start("Storing API key securely...");
347
+ const keyManager = getKeyManager();
348
+ await keyManager.initialize();
349
+ const { getSpinner } = await import("./utils/cli-env.js");
350
+ const keySpinner = await getSpinner();
445
351
  try {
446
- const newKeyId = await keyManager.addKey(keyName, apiKey, baseUrl);
447
- spinner.succeed("API key stored successfully!");
448
- console.log();
449
- console.log(formatKeyValue("Name", keyName, chalk.white.bold));
450
- console.log(formatKeyValue("ID", newKeyId, chalk.gray));
451
- console.log(formatKeyValue("Endpoint", baseUrl, chalk.cyan));
452
- console.log();
453
- usedKeyName = keyName;
454
- // Check if we should activate this key
455
- const allKeys = await keyManager.listKeys();
456
- if (allKeys.length > 1) {
457
- const { activateNew } = await inquirer.prompt([
458
- {
459
- type: "confirm",
460
- name: "activateNew",
461
- message: `Set "${keyName}" as your active API key?`,
462
- default: true,
463
- },
464
- ]);
465
- if (activateNew) {
466
- await keyManager.setActiveKey(newKeyId);
467
- showSuccess(`"${keyName}" is now your active key`);
468
- }
469
- else {
470
- showInfo(`Keeping your current active key. Run 'iterable-mcp keys activate "${keyName}"' to switch later.`);
471
- }
472
- }
352
+ const newKeyId = await saveKeyInteractive(keyManager, null, {
353
+ chalk,
354
+ icons,
355
+ showError,
356
+ showSuccess,
357
+ showInfo,
358
+ formatKeyValue,
359
+ linkColor,
360
+ showBox: (await import("./utils/ui.js")).showBox,
361
+ }, keySpinner, {
362
+ skipRestartNotice: true,
363
+ advanced,
364
+ prefilledApiKey: apiKey,
365
+ autoActivate: true,
366
+ });
367
+ selectedExistingMeta =
368
+ (await keyManager.getKeyMetadata(newKeyId)) ?? undefined;
473
369
  }
474
370
  catch (error) {
475
- const { sanitizeString } = await import("./utils/sanitize.js");
476
- spinner.fail("Failed to store API key");
477
- console.log();
478
- const msg = error instanceof Error
479
- ? sanitizeString(error.message)
480
- : "Unknown error";
481
- showError(msg);
482
- console.log();
483
- showInfo("Your API key was not stored. You can re-run 'iterable-mcp setup' to try again.");
484
- showInfo("If the problem persists, verify storage access and disk permissions.");
371
+ showError(error instanceof Error ? error.message : "Failed to add key");
485
372
  process.exit(1);
486
373
  }
487
374
  }
488
- // Step 4: Privacy & Security Settings (Basic by default; Advanced via --advanced)
375
+ if (!selectedExistingMeta) {
376
+ showError("Failed to get key metadata after setup");
377
+ process.exit(1);
378
+ }
379
+ let mcpEnv = pickPersistablePermissionEnv(selectedExistingMeta.env || {});
380
+ if (args.includes("--debug")) {
381
+ mcpEnv.ITERABLE_DEBUG = "true";
382
+ mcpEnv.LOG_LEVEL = "debug";
383
+ }
384
+ mcpEnv = enforceSendsRequiresWrites(mcpEnv, (msg) => showWarning(msg));
385
+ // Step 5: Configure AI Tools
489
386
  console.log();
490
- showSection("Privacy & Security Settings", icons.lock);
387
+ showSection("Configure AI Tools", icons.zap);
491
388
  console.log();
492
- // Default conservative env flags
493
- let selectedEnv = {
494
- ITERABLE_USER_PII: "false",
495
- ITERABLE_ENABLE_WRITES: "false",
496
- ITERABLE_ENABLE_SENDS: "false",
497
- };
498
- if (advanced) {
499
- const { permFlags } = await inquirer.prompt([
389
+ if (tools.length === 0) {
390
+ const { selectedTools } = await inquirer.prompt([
500
391
  {
501
392
  type: "checkbox",
502
- name: "permFlags",
503
- message: "Select permissions to enable (default: none):",
393
+ name: "selectedTools",
394
+ message: "Select your AI tools to configure:",
504
395
  choices: [
505
- { name: "View PII (access user data)", value: "pii" },
506
- {
507
- name: "Write operations (create/update/delete)",
508
- value: "writes",
509
- },
510
- { name: "Sends (campaigns/journeys/events)", value: "sends" },
396
+ { name: "Cursor", value: "cursor" },
397
+ { name: "Claude Desktop", value: "claude-desktop" },
398
+ { name: "Claude Code (CLI)", value: "claude-code" },
399
+ { name: "Gemini CLI", value: "gemini-cli" },
400
+ { name: "Windsurf", value: "windsurf" },
401
+ { name: "Other / Manual Setup", value: "manual" },
511
402
  ],
512
- pageSize: 6,
513
- default: [],
403
+ validate: (arr) => Array.isArray(arr) && arr.length > 0
404
+ ? true
405
+ : "Please select at least one tool",
514
406
  },
515
407
  ]);
516
- selectedEnv = {
517
- ITERABLE_USER_PII: permFlags.includes("pii")
518
- ? "true"
519
- : "false",
520
- ITERABLE_ENABLE_WRITES: permFlags.includes("writes")
521
- ? "true"
522
- : "false",
523
- ITERABLE_ENABLE_SENDS: permFlags.includes("sends")
524
- ? "true"
525
- : "false",
526
- };
527
- }
528
- let mcpEnv = {
529
- ITERABLE_USER_PII: selectedEnv.ITERABLE_USER_PII,
530
- ITERABLE_ENABLE_WRITES: selectedEnv.ITERABLE_ENABLE_WRITES,
531
- ITERABLE_ENABLE_SENDS: selectedEnv.ITERABLE_ENABLE_SENDS,
532
- };
533
- if (args.includes("--debug")) {
534
- mcpEnv.ITERABLE_DEBUG = "true";
535
- mcpEnv.LOG_LEVEL = "debug";
536
- }
537
- // Enforce permission invariants before displaying
538
- mcpEnv = enforceSendsRequiresWrites(mcpEnv, (msg) => showWarning(msg));
539
- console.log();
540
- console.log(formatKeyValue("User PII Access", mcpEnv.ITERABLE_USER_PII === "true"
541
- ? chalk.green("Enabled")
542
- : chalk.gray("Disabled")));
543
- console.log(formatKeyValue("Write Operations", mcpEnv.ITERABLE_ENABLE_WRITES === "true"
544
- ? chalk.green("Enabled")
545
- : chalk.gray("Disabled")));
546
- console.log(formatKeyValue("Sends", mcpEnv.ITERABLE_ENABLE_SENDS === "true"
547
- ? chalk.green("Enabled")
548
- : chalk.gray("Disabled")));
549
- // Only show the default note when all flags are disabled
550
- if (mcpEnv.ITERABLE_USER_PII !== "true" &&
551
- mcpEnv.ITERABLE_ENABLE_WRITES !== "true" &&
552
- mcpEnv.ITERABLE_ENABLE_SENDS !== "true") {
553
- console.log();
554
- console.log(chalk.gray("Note: These are disabled by default. If you absolutely need to enable these, run 'iterable-mcp setup --advanced'"));
555
- }
556
- else {
408
+ tools = selectedTools;
557
409
  console.log();
558
- showWarning("Warning: Elevated capabilities enabled. Sends, writes, and/or PII access may now occur.");
559
- }
560
- console.log();
561
- // If we used an existing key, persist the chosen settings to it
562
- if (usedKeyName) {
563
- try {
564
- await keyManager.updateKeyEnv(usedKeyName, pickPersistablePermissionEnv(mcpEnv));
565
- }
566
- catch (err) {
567
- if (process.env.ITERABLE_DEBUG === "true") {
568
- console.warn("Non-fatal: failed to persist per-key env settings", err);
569
- }
570
- }
571
410
  }
572
- // Step 5: Configure AI Tools
573
- console.log();
574
- showSection("Configuring AI Tools", icons.zap);
575
- console.log();
576
- // Ask about auto-update unless flag was provided
577
411
  let useAutoUpdate = autoUpdate;
578
412
  if (!autoUpdate) {
579
413
  const { enableAutoUpdate } = await inquirer.prompt([
@@ -584,6 +418,7 @@ export const setupMcpServer = async () => {
584
418
  default: false,
585
419
  },
586
420
  ]);
421
+ console.log();
587
422
  useAutoUpdate = enableAutoUpdate;
588
423
  }
589
424
  const iterableMcpConfig = buildMcpConfig({
@@ -591,35 +426,6 @@ export const setupMcpServer = async () => {
591
426
  env: omitPersistablePermissionEnv(mcpEnv),
592
427
  autoUpdate: useAutoUpdate,
593
428
  });
594
- // Preflight confirmation summary before applying changes
595
- console.log(chalk.gray("Summary:"));
596
- console.log(formatKeyValue("Tools", tools.map((t) => TOOL_NAMES[t]).join(", "), valueColor()));
597
- if (usedKeyName) {
598
- console.log(formatKeyValue("API Key", usedKeyName, valueColor()));
599
- }
600
- console.log(formatKeyValue("Endpoint", baseUrl, valueColor()));
601
- console.log(formatKeyValue("User PII", mcpEnv.ITERABLE_USER_PII === "true"
602
- ? chalk.green("Enabled")
603
- : chalk.gray("Disabled")));
604
- console.log(formatKeyValue("Writes", mcpEnv.ITERABLE_ENABLE_WRITES === "true"
605
- ? chalk.green("Enabled")
606
- : chalk.gray("Disabled")));
607
- console.log(formatKeyValue("Sends", mcpEnv.ITERABLE_ENABLE_SENDS === "true"
608
- ? chalk.green("Enabled")
609
- : chalk.gray("Disabled")));
610
- console.log();
611
- const { proceed } = await inquirer.prompt([
612
- {
613
- type: "confirm",
614
- name: "proceed",
615
- message: "Proceed with configuration?",
616
- default: true,
617
- },
618
- ]);
619
- if (!proceed) {
620
- showInfo("Setup cancelled by user. No changes were made.");
621
- return;
622
- }
623
429
  const fileBasedTools = tools.filter((tool) => tool in TOOL_CONFIGS);
624
430
  const needsClaudeCode = tools.includes("claude-code");
625
431
  const needsManual = tools.includes("manual");
@@ -649,11 +455,10 @@ export const setupMcpServer = async () => {
649
455
  spinner.fail("Claude CLI not found");
650
456
  showError("Please install Claude Code first: https://docs.claude.com/en/docs/claude-code/overview");
651
457
  console.log();
652
- showInfo("After installing, re-run: iterable-mcp setup --claude-code");
458
+ showInfo(`After installing, re-run: ${COMMAND_NAME} setup --claude-code`);
653
459
  process.exit(1);
654
460
  }
655
461
  const configJson = JSON.stringify(iterableMcpConfig);
656
- // Remove existing config (ignore errors)
657
462
  await execFileAsync("claude", ["mcp", "remove", "iterable"]).catch(() => { });
658
463
  spinner.start("Configuring Claude Code...");
659
464
  await new Promise((resolve, reject) => {
@@ -673,11 +478,9 @@ export const setupMcpServer = async () => {
673
478
  spinner.succeed("Claude Code configured successfully");
674
479
  }
675
480
  if (needsManual) {
676
- console.log();
677
481
  showSection("Manual Configuration", icons.rocket);
678
482
  console.log();
679
483
  const { type, command, args, env } = iterableMcpConfig;
680
- showInfo("Your API key has been stored.");
681
484
  showInfo("Add the MCP server to your AI tool with these settings:");
682
485
  console.log();
683
486
  console.log(chalk.white.bold(" Type:") + ` ${type}`);
@@ -693,16 +496,17 @@ export const setupMcpServer = async () => {
693
496
  console.log();
694
497
  showInfo("Refer to your AI tool's documentation for configuration instructions.");
695
498
  }
696
- // Build configured tools list
697
499
  const configuredTools = [];
698
500
  if (fileBasedTools.includes("cursor"))
699
501
  configuredTools.push("Cursor");
700
502
  if (fileBasedTools.includes("claude-desktop"))
701
503
  configuredTools.push("Claude Desktop");
702
- if (fileBasedTools.includes("gemini-cli"))
703
- configuredTools.push("Gemini CLI");
704
504
  if (needsClaudeCode)
705
505
  configuredTools.push("Claude Code");
506
+ if (fileBasedTools.includes("gemini-cli"))
507
+ configuredTools.push("Gemini CLI");
508
+ if (fileBasedTools.includes("windsurf"))
509
+ configuredTools.push("Windsurf");
706
510
  if (needsManual)
707
511
  configuredTools.push("your AI tool");
708
512
  const toolsList = configuredTools.length === 1
@@ -712,7 +516,6 @@ export const setupMcpServer = async () => {
712
516
  : configuredTools.slice(0, -1).join(", ") +
713
517
  ", and " +
714
518
  configuredTools[configuredTools.length - 1];
715
- // Success!
716
519
  console.log();
717
520
  const nextSteps = [
718
521
  ...(needsManual ? ["Configure your AI tool as described above"] : []),
@@ -724,8 +527,8 @@ export const setupMcpServer = async () => {
724
527
  ];
725
528
  const tips = [
726
529
  `Try: 'list my Iterable campaigns' in ${toolsList}`,
727
- "Manage keys with 'iterable-mcp keys list'",
728
- "Switch keys with 'iterable-mcp keys activate <name>'",
530
+ `Manage keys with '${COMMAND_NAME} keys list'`,
531
+ `Switch keys with '${COMMAND_NAME} keys activate <name>'`,
729
532
  ];
730
533
  showCompletion("Setup Complete!", nextSteps, tips);
731
534
  }